diff --git a/image/03.png b/image/03.png index e3949c4b..e311e8d0 100644 Binary files a/image/03.png and b/image/03.png differ diff --git a/image/04.png b/image/04.png index 65f5e0c7..da499563 100644 Binary files a/image/04.png and b/image/04.png differ diff --git a/image/05.png b/image/05.png index 1414c46a..a3d8a562 100644 Binary files a/image/05.png and b/image/05.png differ diff --git a/image/06.png b/image/06.png index da499563..ec7847ed 100644 Binary files a/image/06.png and b/image/06.png differ diff --git a/image/07.png b/image/07.png index e311e8d0..44f5d512 100644 Binary files a/image/07.png and b/image/07.png differ diff --git a/image/08.png b/image/08.png index abf6c32a..a2479edc 100644 Binary files a/image/08.png and b/image/08.png differ diff --git a/image/09.png b/image/09.png index 62ff7ca4..eb4aa788 100644 Binary files a/image/09.png and b/image/09.png differ diff --git a/image/10.png b/image/10.png index 2bf98505..558cf0d7 100644 Binary files a/image/10.png and b/image/10.png differ diff --git a/image/11.png b/image/11.png index a301cafb..1ab97ab2 100644 Binary files a/image/11.png and b/image/11.png differ diff --git a/image/12.png b/image/12.png new file mode 100644 index 00000000..7c4480ae Binary files /dev/null and b/image/12.png differ diff --git a/pom.xml b/pom.xml index ab2aa174..00f24f9d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.xmzs + org.ruoyi ruoyi-ai ${revision} @@ -58,6 +58,7 @@ 3.0.0 1.3.0 4.5.0 + 4.6.0 @@ -114,7 +115,7 @@ - com.xmzs + org.ruoyi ruoyi-common-bom ${revision} pom @@ -307,33 +308,21 @@ - com.xmzs + org.ruoyi ruoyi-system ${revision} - - com.xmzs - ruoyi-job - ${revision} - - com.xmzs - ruoyi-midjourney + org.ruoyi + ruoyi-fusion ${revision} - - com.xmzs - ruoyi-generator - ${revision} - - - - com.xmzs + org.ruoyi ruoyi-demo ${revision} diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 223ff6c4..acec2196 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ruoyi-ai - com.xmzs + org.ruoyi ${revision} ../pom.xml @@ -23,16 +23,19 @@ com.mysql mysql-connector-j + com.oracle.database.jdbc ojdbc8 + org.postgresql postgresql + com.microsoft.sqlserver @@ -40,68 +43,35 @@ - com.xmzs + org.ruoyi ruoyi-common-doc - com.xmzs + org.ruoyi ruoyi-system - com.xmzs - ruoyi-common-chat - - - - com.xmzs - ruoyi-job - - - - com.xmzs - ruoyi-midjourney + org.ruoyi + ruoyi-fusion - - - com.xmzs - ruoyi-generator - - - com.xmzs + org.ruoyi ruoyi-demo - - - - - org.springframework.boot spring-boot-starter-test test - - - - - - - - - - - - net.coobird diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiApplication.java new file mode 100644 index 00000000..9c7d56ed --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiApplication.java @@ -0,0 +1,21 @@ +package org.ruoyi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; + +/** + * 启动程序 + * + * @author Lion Li + */ +@SpringBootApplication +public class RuoYiApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(RuoYiApplication.class); + application.setApplicationStartup(new BufferingApplicationStartup(2048)); + application.run(args); + System.out.println("(♥◠‿◠)ノ゙ RuoYiAi启动成功 ლ(´ڡ`ლ)゙"); + } +} diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiServletInitializer.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiServletInitializer.java new file mode 100644 index 00000000..082315fc --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +package org.ruoyi; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author Lion Li + */ +public class RuoYiServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(RuoYiApplication.class); + } + +} diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java new file mode 100644 index 00000000..e78d09c0 --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java @@ -0,0 +1,162 @@ +package org.ruoyi.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.collection.CollUtil; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.domain.model.EmailLoginBody; +import org.ruoyi.common.core.domain.model.LoginBody; +import org.ruoyi.common.core.domain.model.RegisterBody; +import org.ruoyi.common.core.domain.model.SmsLoginBody; +import org.ruoyi.common.core.domain.model.VisitorLoginBody; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.system.domain.bo.SysTenantBo; +import org.ruoyi.system.domain.vo.LoginTenantVo; +import org.ruoyi.system.domain.vo.SysTenantVo; +import org.ruoyi.system.domain.vo.TenantListVo; +import org.ruoyi.system.service.ISysTenantService; + +import org.ruoyi.system.service.SysLoginService; +import org.ruoyi.system.service.SysRegisterService; +import org.ruoyi.system.domain.vo.LoginVo; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.net.URL; +import java.util.List; + +/** + * 认证 + * + * @author Lion Li + */ +@SaIgnore +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/auth") +public class AuthController { + + private final SysLoginService loginService; + private final SysRegisterService registerService; + private final ISysTenantService tenantService; + + /** + * 登录方法 + * + * @param body 登录信息 + * @return 结果 + */ + @PostMapping("/login") + public R login(@Validated @RequestBody LoginBody body) { + body.setTenantId(Constants.TENANT_ID); + LoginVo loginVo = new LoginVo(); + // 生成令牌 + String token = loginService.login( + body.getTenantId(), + body.getUsername(), body.getPassword(), + body.getCode(), body.getUuid()); + loginVo.setToken(token); + loginVo.setUserInfo(LoginHelper.getLoginUser()); + return R.ok(loginVo); + } + + /** + * 短信登录 + * + * @param body 登录信息 + * @return 结果 + */ + @PostMapping("/smsLogin") + public R smsLogin(@Validated @RequestBody SmsLoginBody body) { + LoginVo loginVo = new LoginVo(); + // 生成令牌 + String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode()); + loginVo.setToken(token); + return R.ok(loginVo); + } + + /** + * 访客登录 + * @param loginBody 登录信息 + * @return token信息 + */ + @PostMapping("/visitorLogin") + public R visitorLogin(@RequestBody VisitorLoginBody loginBody) { + LoginVo loginVo = new LoginVo(); + return R.ok(loginVo); + } + + /** + * 邮件登录 + * + * @param body 登录信息 + * @return 结果 + */ + @PostMapping("/emailLogin") + public R emailLogin(@Validated @RequestBody EmailLoginBody body) { + LoginVo loginVo = new LoginVo(); + // 生成令牌 + String token = loginService.emailLogin(body.getTenantId(), body.getEmail(), body.getEmailCode()); + loginVo.setToken(token); + return R.ok(loginVo); + } + + /** + * 退出登录 + */ + @PostMapping("/logout") + public R logout() { + loginService.logout(); + return R.ok("退出成功"); + } + + /** + * 用户注册 + */ + @PostMapping("/register") + public R register(@Validated @RequestBody RegisterBody user, HttpServletRequest request) { + String domainName = request.getServerName(); + user.setDomainName(domainName); + registerService.register(user); + return R.ok(); + } + + /** + * 重置密码 + */ + @PostMapping("/reset/password") + @SaIgnore + public R resetPassWord(@Validated @RequestBody RegisterBody user) { + registerService.resetPassWord(user); + return R.ok(); + } + + /** + * 登录页面租户下拉框 + * + * @return 租户列表 + */ + @GetMapping("/tenant/list") + public R tenantList(HttpServletRequest request) throws Exception { + List tenantList = tenantService.queryList(new SysTenantBo()); + List voList = MapstructUtils.convert(tenantList, TenantListVo.class); + // 获取域名 + String host = new URL(request.getRequestURL().toString()).getHost(); + // 根据域名进行筛选 + List list = StreamUtils.filter(voList, vo -> StringUtils.equals(vo.getDomain(), host)); + // 返回对象 + LoginTenantVo vo = new LoginTenantVo(); + vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList); + vo.setTenantEnabled(TenantHelper.isEnable()); + return R.ok(vo); + } + +} diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/CaptchaController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/CaptchaController.java new file mode 100644 index 00000000..89cf2437 --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/CaptchaController.java @@ -0,0 +1,152 @@ +package org.ruoyi.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; +import org.ruoyi.common.mail.utils.MailUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.sms.config.properties.SmsProperties; +import org.ruoyi.common.sms.core.SmsTemplate; +import org.ruoyi.common.sms.entity.SmsResult; +import org.ruoyi.common.web.config.properties.CaptchaProperties; +import org.ruoyi.common.web.enums.CaptchaType; +import org.ruoyi.system.domain.request.EmailRequest; +import org.ruoyi.system.domain.vo.CaptchaVo; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.time.Duration; +import java.util.*; + +/** + * 验证码操作处理 + * + * @author Lion Li + */ +@SaIgnore +@Slf4j +@Validated +@RequiredArgsConstructor +@RestController +public class CaptchaController { + + private final CaptchaProperties captchaProperties; + private final SmsProperties smsProperties; + private final ConfigService configService; + + /** + * 短信验证码 + * + * @param phonenumber 用户手机号 + */ + @GetMapping("/resource/sms/code") + public R smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { + if (!smsProperties.getEnabled()) { + return R.fail("当前系统没有开启短信功能!"); + } + String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber; + String code = RandomUtil.randomNumbers(4); + RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); + // 验证码模板id 自行处理 (查数据库或写死均可) + String templateId = ""; + Map map = new HashMap<>(1); + map.put("code", code); + SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class); + SmsResult result = smsTemplate.send(phonenumber, templateId, map); + if (!result.isSuccess()) { + log.error("验证码短信发送异常 => {}", result); + return R.fail(result.getMessage()); + } + return R.ok(); + } + + /** + * 邮箱验证码 + * + * @param emailRequest 用户邮箱 + */ + @PostMapping("/resource/email/code") + public R emailCode(@RequestBody @Valid EmailRequest emailRequest) { + String key = GlobalConstants.CAPTCHA_CODE_KEY + emailRequest.getUsername(); + String code = RandomUtil.randomNumbers(4); + RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); + // 检验邮箱后缀 + String suffix = configService.getConfigValue("mail", "suffix"); + String prompt = configService.getConfigValue("mail", "prompt"); + if(StringUtils.isNotEmpty(suffix)){ + // 动态的域名列表 + String[] invalidDomains = suffix.split(","); + for (String domain : invalidDomains) { + if (emailRequest.getUsername().endsWith(domain)) { + throw new ServiceException(prompt); + } + } + } + // 自定义邮箱模板 + String model = configService.getConfigValue("mail", "mailModel"); + String mailTitle = configService.getConfigValue("mail", "mailTitle"); + String replacedModel = model.replace("{code}", code); + try { + MailUtils.sendHtml(emailRequest.getUsername(), mailTitle, replacedModel); + } catch (Exception e) { + log.error("邮箱验证码发送异常 => {}", e.getMessage()); + return R.fail(e.getMessage()); + } + return R.ok(); + } + + /** + * 生成验证码 + */ + @GetMapping("/auth/code") + public R getCode() { + CaptchaVo captchaVo = new CaptchaVo(); + boolean captchaEnabled = captchaProperties.getEnable(); + if (!captchaEnabled) { + captchaVo.setCaptchaEnabled(false); + return R.ok(captchaVo); + } + // 保存验证码信息 + String uuid = IdUtil.simpleUUID(); + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; + // 生成验证码 + CaptchaType captchaType = captchaProperties.getType(); + boolean isMath = CaptchaType.MATH == captchaType; + Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength(); + CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length); + AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); + captcha.setGenerator(codeGenerator); + captcha.createCode(); + String code = captcha.getCode(); + if (isMath) { + ExpressionParser parser = new SpelExpressionParser(); + Expression exp = parser.parseExpression(StringUtils.remove(code, "=")); + code = exp.getValue(String.class); + } + RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); + captchaVo.setUuid(uuid); + captchaVo.setImg(captcha.getImageBase64()); + return R.ok(captchaVo); + } + +} diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java new file mode 100644 index 00000000..4da12bc8 --- /dev/null +++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java @@ -0,0 +1,36 @@ +package org.ruoyi.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 首页 + * + * @author Lion Li + */ +@SaIgnore +@RequiredArgsConstructor +@Controller +public class IndexController { + + /** + * 访问首页,提示语 + */ + @GetMapping("/") + public String index() { + return "index.html"; + } + + @GetMapping("/success") + public String success(){ + return "paySuccess.html"; + } + + @GetMapping("/cancel") + public String cancel(){ + return "cancel"; + } + +} diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 60520190..06e1b90b 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -47,41 +47,9 @@ spring: master: type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver - # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 - # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - - - # 从库数据源 - # slave: - # lazy: true - # type: ${spring.datasource.type} - # driverClassName: com.mysql.cj.jdbc.Driver - # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true - # username: - # password: - # oracle: - # type: ${spring.datasource.type} - # driverClassName: oracle.jdbc.OracleDriver - # url: jdbc:oracle:thin:@//localhost:1521/XE - # username: ROOT - # password: root - # hikari: - # connectionTestQuery: SELECT 1 FROM DUAL - # postgres: - # type: ${spring.datasource.type} - # driverClassName: org.postgresql.Driver - # url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true - # username: root - # password: root - # sqlserver: - # type: ${spring.datasource.type} - # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver - # url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true - # username: SA - # password: root + url: jdbc:mysql://43.139.70.230:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true + username: ruoyi-ai + password: TZ7yaGtSRWeeBaBJ hikari: # 最大连接池数量 maxPoolSize: 20 @@ -104,15 +72,15 @@ spring: spring.data: redis: # 地址 - host: ${REDIS_HOST} + host: 127.0.0.1 # 端口,默认为6379 - port: ${REDIS_PORT} + port: 6379 # 数据库索引 database: 0 # 密码(如没有密码请注释掉) - # password: + #password: # 连接超时时间 - timeout: 10s + timeout: 10S # 是否开启ssl ssl: false diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index cded7aa3..491dea56 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -1,7 +1,8 @@ + # 项目相关配置 ruoyi: # 名称 - name: "xmzs" + name: "ruoyi" # 版本 version: ${revision} # 版权年份 @@ -48,7 +49,7 @@ server: # 日志配置 logging: level: - com.xmzs: '@logging.level@' + org.ruoyi: @logging.level@ org.springframework: warn config: classpath:logback-plus.xml @@ -69,14 +70,14 @@ spring: # 国际化资源文件路径 basename: i18n/messages profiles: - active: '@profiles.active@' + active: @profiles.active@ # 文件上传 servlet: multipart: # 单个文件大小 - max-file-size: 10MB + max-file-size: 50MB # 设置总上传的文件大小 - max-request-size: 20MB + max-request-size: 200MB mvc: format: date-time: yyyy-MM-dd HH:mm:ss @@ -117,8 +118,7 @@ sa-token: security: # 排除路径 excludes: - # 修改用户头像 - - /system/user/edit/avatar + # 支付回调 - /pay/returnUrl - /pay/notifyUrl # 上传文件 @@ -160,11 +160,11 @@ tenant: mybatis-plus: # 不支持多包, 如有需要可在注解配置 或 提升扫包等级 # 例如 com.**.**.mapper - mapperPackage: com.xmzs.**.mapper + mapperPackage: org.ruoyi.**.mapper # 对应的 XML 文件位置 mapperLocations: classpath*:mapper/**/*Mapper.xml # 实体扫描,多个package用逗号或者分号分隔 - typeAliasesPackage: com.xmzs.**.domain + typeAliasesPackage: org.ruoyi.**.domain # 启动时是否检查 MyBatis XML 文件的存在,默认不检查 checkConfigLocation: false configuration: @@ -245,13 +245,13 @@ springdoc: #这里定义了两个分组,可定义多个,也可以不定义 group-configs: - group: 1.演示模块 - packages-to-scan: com.xmzs.demo + packages-to-scan: org.ruoyi.demo - group: 2.通用模块 - packages-to-scan: com.xmzs.web + packages-to-scan: org.ruoyi.web - group: 3.系统模块 - packages-to-scan: com.xmzs.system + packages-to-scan: org.ruoyi.system - group: 4.代码生成模块 - packages-to-scan: com.xmzs.generator + packages-to-scan: org.ruoyi.generator # 防止XSS攻击 xss: @@ -290,9 +290,9 @@ management: logfile: external-file: ./logs/sys-console.log ---- # websocket +# websocket websocket: - enabled: false + enabled: true # 路径 path: '' # 设置访问源地址 @@ -307,83 +307,72 @@ wx: token: #微信小程序消息服务器配置的token aesKey: #微信小程序消息服务器配置的EncodingAESKey msgDataFormat: JSON -baidu: - # 是否开启文本审核 - enabled: false - # 文本审核 - textReview: - apiKey: '' # apiKey - secretKey: '' # secretKey - appKey: xxxxxxxxxxxxxxxxx - secretKey: xxxxxxxxxxxxxxxxxxxxxxx -wechat: - # 是否使用微信 true/false - enable: true - # 生成的登录二维码路径 默认与项目同级 - qrPath: "./" +# 知识库配置 +chain: + split: + chunk: + endspliter: "" + # 分块文本大小 + size: 500 + overlay: 0 + qaspliter: "######" + # 知识库中检索的条数 + limits: 5 + vectorization: + type: openai + openai: + model: 'text-embedding-3-small' + baidu: + model: bge-large-zh + zhipu: + model: embedding-2 + # 智普API KEY + token: xx + vector: + store: + type: weaviate + weaviate: + protocol: http + host: 127.0.0.1:6038 + classname: LocalKnowledge + milvus: + host: 127.0.0.1 + port: 19530 + dimension: 1536 + collection: LocalKnowledge + llm: + openai: + token: sk-xx + model: gpt-4-1106-preview + chatglm: + baseurl: http://127.0.0.1:8000/ + model: chatglm2-6b + baidu: + appKey: xx + secretKey: xx + model: ernie_bot + zhipu: + model: glm-4 + audio: + type: openai + text: + type: openai + function: + type: baidu + vision: + type: openai + image: + type: openai -keyword: - # 重置会话指令 - reset: "重置会话" - # ai画图指令(DALL·E模型 https://platform.openai.com/docs/models/dall-e) - # generation 根据关键词生成图片(https://platform.openai.com/docs/guides/images/generations) - image: "ai画图" - # ai语音指令(TTS模型 https://platform.openai.com/docs/api-reference/audio) - audio: "ai语音" +upload: + path: /data/upload -#绘画价格配置(元) -mj: - # 放大 - upsample: 0.1 - # 变化 - change: 0.3 - # 图生图 - blend: 0.3 - # 图生文 - describe: 0.1 - # 文生图 - imagine: 0.3 - # 局部重绘 - inpaint: 0.3 - # 提示词分析 - shorten: 0.1 - # 换脸 - faceSwapping: 0.3 +proxy: + socket: + host: 127.0.0.1 + port: 7890 ---- # mail 邮件发送 -mail: - enabled: true - host: smtp.163.com - port: 465 - # 是否需要用户名密码验证 - auth: true - # 发送方,遵循RFC-822标准 - from: ageerle@163.com - # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) - user: ageerle@163.com - # 密码(填写授权码) - pass: ${MAIL_PASS} - # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。 - starttlsEnable: true - # 使用SSL安全连接 - sslEnable: true - # SMTP超时时长,单位毫秒,缺省值不超时 - timeout: 0 - # Socket连接超时值,单位毫秒,缺省值不超时 - connectionTimeout: 0 +resource: + domain: http://127.0.0.1:${server.port}/resources -# chatgpt和mj共用一个key -chat: - apiKey: ${CHAT_API_KEY} - apiHost: ${CHAT_API_HOST} -# 支付配置信息 -pay: - pid: ${PAY_PID} - key: ${PAY_KEY} - payUrl: 'https://pay.pandarobot.chat/mapi.php' - notify_url: 'https://www.pandarobot.chat/pay/returnUrl' - return_url: 'https://www.pandarobot.chat/pay/notifyUrl' - type: 'wxpay' - device: 'pc' - sign_type: 'MD5' diff --git a/ruoyi-admin/src/main/resources/banner.txt b/ruoyi-admin/src/main/resources/banner.txt index 4a9538ea..1a0b58fd 100644 --- a/ruoyi-admin/src/main/resources/banner.txt +++ b/ruoyi-admin/src/main/resources/banner.txt @@ -1,9 +1,2 @@ Application Version: ${revision} Spring Boot Version: ${spring-boot.version} - ██ ██ ██ ██ - ██████ ░██ ░██ ░██ █████ ██████ ░██ -░██░░░██ ██████ ███████ ░██ ██████ █████ ░██ ██████ ██████ ██░░░██░██░░░██ ██████ -░██ ░██ ░░░░░░██ ░░██░░░██ ██████ ░░░░░░██ █████ ██░░░██░██████ ░░░░░░██ ░░░██░ ░██ ░██░██ ░██░░░██░ -░██████ ███████ ░██ ░██ ██░░░██ ███████ ░░░░░ ░██ ░░ ░██░░░██ ███████ ░██ ░░██████░██████ ░██ -░██░░░ ██░░░░██ ░██ ░██░██ ░██ ██░░░░██ ░██ ██░██ ░██ ██░░░░██ ░██ ░░░░░██░██░░░ ░██ -░██ ░░████████ ███ ░██░░██████░░████████ ░░█████ ░██ ░██░░████████ ░░██ █████ ░██ ░░██ diff --git a/ruoyi-admin/src/main/resources/static/.gitignore b/ruoyi-admin/src/main/resources/static/.gitignore new file mode 100644 index 00000000..9e339689 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/ruoyi-admin/src/main/resources/static/.nojekyll b/ruoyi-admin/src/main/resources/static/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/ruoyi-admin/src/main/resources/static/CNAME b/ruoyi-admin/src/main/resources/static/CNAME new file mode 100644 index 00000000..3a194c70 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/CNAME @@ -0,0 +1 @@ +plus-doc.dromara.org \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/README.md b/ruoyi-admin/src/main/resources/static/README.md new file mode 100644 index 00000000..b50e2523 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/README.md @@ -0,0 +1,74 @@ +# 框架介绍 +- - - +- `RuoYi-Vue-Plus` 分布式集群框架 [文档跳转](/ruoyi-vue-plus/home.md) +- `RuoYi-Cloud-Plus` 微服务框架 [文档跳转](/ruoyi-cloud-plus/home.md) +- `plus-ui` 统一 Vue3 前端项目 [文档跳转](/plus-ui/home.md) +- `plus-doc` 统一文档项目 + +## 特别赞助 + + + +
+ + +
+ + +[如何成为赞助商 加群联系作者详谈](/common/add_group.md) + +## 代码地址 + +| 介绍 | 项目名 | 项目地址 | 注意事项 | +|------------|:-----------------|------------------------------------------------------------------------------------------------------------------------|----------------------------| +| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [GitHub](https://github.com/dromara/RuoYi-Vue-Plus) | 重写RuoYi-Vue全方位升级(不兼容原框架) | +| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)
- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus) | 重写RuoYi-Cloud全方位升级(不兼容原框架) | +| 🔥 统一前端项目 | plus-ui | - [Gitee](https://gitee.com/JavaLionLi/plus-ui)
- [GitHub](https://github.com/JavaLionLi/plus-ui) | Vue与Cloud项目通用前端 | +| 🔥 统一文档项目 | plus-doc | - [Gitee](https://gitee.com/dromara/plus-doc)
- [GitHub](https://github.com/dromara/plus-doc) | 通用文档 | + + +## 业务功能 + +| 功能 | 介绍 | +|-------|---------------------------------------| +| 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能。 | +| 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置。 | +| 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 | +| 岗位管理 | 配置系统用户所属担任职务。 | +| 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等。 | +| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分。 | +| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护。 | +| 参数管理 | 对系统动态配置常用参数。 | +| 通知公告 | 系统通知公告信息发布维护。 | +| 操作日志 | 系统正常操作日志记录和查询;系统异常信息日志记录和查询。 | +| 登录日志 | 系统登录日志记录查询包含登录异常。 | +| 文件管理 | 系统文件上传、下载等管理。 | +| 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志。 | +| 代码生成 | 前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 | +| 系统接口 | 根据业务代码自动生成相关的api接口文档。 | +| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等。 | +| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | +| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | +| 使用案例 | 系统的一些功能案例 | + +## 关注作者 + +作者博客: [https://lionli.blog.csdn.net/?type=blog](https://lionli.blog.csdn.net/?type=blog) + +公众号: **<狮子领域 程序圈>** +
+![输入图片说明](https://foruda.gitee.com/images/1678975769377570440/507062df_1766278.png "屏幕截图") + +## 捐献作者 + +**作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭** +
+ + +## Dromara 全家福 + +社区仓库地址: [dromara开源社区](https://gitee.com/organizations/dromara/projects) + +![输入图片说明](https://foruda.gitee.com/images/1706071866226295002/68cffcf6_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/_coverpage.md b/ruoyi-admin/src/main/resources/static/_coverpage.md new file mode 100644 index 00000000..d84bf0dc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/_coverpage.md @@ -0,0 +1,32 @@ + + + +
+
+
百搭AI
+ + +[![码云Gitee](https://gitee.com/ageerle/ruoyi-ai/badge/star.svg?theme=blue)](https://gitee.com/ageerle/ruoyi-ai) +[![GitHub](https://img.shields.io/github/stars/ageerle/ruoyi-ai.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/ageerle/ruoyi-ai/blob/master/LICENSE) +
+[![ruoyi-ai](https://img.shields.io/badge/ruoyi-ai-5.2.2-success.svg)](https://gitee.com/ageerle/ruoyi-ai) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]() + +
+
+ +
+ +
+ + +> 百搭AI是一个整合了多种大语言模型API的开源平台,实现了AI对话、绘图、声音克隆和私有知识库等功能。 +> +> 平台配备管理后台,支持微信支付、微信公众号、微信多开、Stripe国际支付和百度文本审核等运营功能。 +> +> 项目采用Java+Vue+Vben5技术栈构建,遵循MIT License,允许二次开发并用于商业销售。 + +Copyright © 2023-2024 版权所有:ageerle@163.com 备案号:鄂ICP备2023007672号 + +[开始使用 Let's Go](/README.md) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/_footer.md b/ruoyi-admin/src/main/resources/static/_footer.md new file mode 100644 index 00000000..7f5f9a3f --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/_footer.md @@ -0,0 +1,2 @@ + +对文档有疑问?欢迎您帮助我们 [完善此文档](https://gitee.com/JavaLionLi/plus-doc) ! \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/_navbar.md b/ruoyi-admin/src/main/resources/static/_navbar.md new file mode 100644 index 00000000..e893825f --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/_navbar.md @@ -0,0 +1,9 @@ + + +* [文档导航](/README.md) +* [Vue版本](/ruoyi-vue-plus/home.md) +* [Cloud版本](/ruoyi-cloud-plus/home.md) +* [前端文档](/plus-ui/home.md) +* [常见问题](/questions/lombok.md) +* [视频教程](/common/video.md) +* [演示系统](/common/demo_system.md) diff --git a/ruoyi-admin/src/main/resources/static/_sidebar.md b/ruoyi-admin/src/main/resources/static/_sidebar.md new file mode 100644 index 00000000..0446edc6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/_sidebar.md @@ -0,0 +1,16 @@ + +- **特别赞助** +- [![输入图片说明](https://foruda.gitee.com/images/1704162419429172656/d0521e59_1766278.png "2024-01-02=>2028-01-02")](http://ccflow.org/?frm=ryPlus) +- [![输入图片说明](https://foruda.gitee.com/images/1705569347386939952/3f187980_1766278.jpeg "2024-01-18=>2025-01-18")](http://www.shuduokeji.com) +- [![输入图片说明](https://foruda.gitee.com/images/1711681233267310022/2ffbcff2_1766278.png "2024-03-29=>2025-03-29")](https://www.jnpfsoft.com/index.html?from=plus-doc) + +- **开始** + - [框架介绍](/README.md) + - [演示系统](/common/demo_system.md) + - [官方视频教程](/common/video.md) + - [粉丝专栏](/common/column.md) + - [参与贡献项目](/common/contribution.md) + - [如何提交PR](/common/pr.md) + - [如何加群](/common/add_group.md) + - [使用者登记](/common/user_register.md) + - [黑名单](/common/blacklist.md) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/common/add_group.md b/ruoyi-admin/src/main/resources/static/common/add_group.md new file mode 100644 index 00000000..f6957bc4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/add_group.md @@ -0,0 +1,27 @@ +# 加群方式 +- - - +### 交流群(不提供任何问题解答 纯交流) + +**加 <小助手> 微信备注 <加群>**
+**视频课程咨询或其他问题咨询请查看下方信息(小助手是机器人)** + + + +### VIP群(付费加群 提供问题解答、技术支持、技术分享) + +首先感谢 `RuoYi` 提供分享开源 框架基于 `RuoYi` 重写大部分功能实现
+项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可
+VIP群是作者提供的私人服务 不代表着项目收费 + +> 问问题等于做习题 听作者解答问题等于习题讲解
+> 一个人接触的问题有限 一群人接触的问题无限 早进群早接触更多的问题(每天99+)
+> 承诺: 看见必回复 让你感受作者有多话痨
+ +两种途径: +1. 购买官方视频进群 [官方视频](/common/video.md) +2. 扫描下方二维码付款进群(无视频) + +支付后申请加群即可 QQ群号 : **<637757165>**
+ +**加群扫码**
+ diff --git a/ruoyi-admin/src/main/resources/static/common/blacklist.md b/ruoyi-admin/src/main/resources/static/common/blacklist.md new file mode 100644 index 00000000..67594bd3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/blacklist.md @@ -0,0 +1,7 @@ +# 黑名单 +- - - + +地址: https://github.com/QNAV/RuoYi-X-Plus +
+上榜缘由 使用本框架二次开源并未有任何声明与标注 将所有代码的作者名全都改成了自己 剽窃本框架代码 + diff --git a/ruoyi-admin/src/main/resources/static/common/column.md b/ruoyi-admin/src/main/resources/static/common/column.md new file mode 100644 index 00000000..b2155a3c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/column.md @@ -0,0 +1,18 @@ +# 粉丝专栏 +- - - +**由上到下 从易到难** + +> 粉丝整理 欢迎投稿 + +| 作者 | 文档地址 | 说明 | +|---------------|---------------------------------------------------------------|--------------------| +| 抓蛙师 | https://www.bilibili.com/video/BV1TG41157Ef/ | 学会问问题(小白必看) | +| 抓蛙师 | https://www.bilibili.com/video/BV1mr4y1j75M | Vue框架基础视频专栏(新人必看) | +| 抓蛙师 | https://www.bilibili.com/video/BV1Na411u7eC | Vue框架改造视频专栏(新人必看) | +| 抓蛙师 | https://www.bilibili.com/video/BV1te4y1D7hi | 小程序鉴权与uniapp联动 | +| 抓蛙师 | https://www.bilibili.com/video/BV1zt4y137UP | 公众号集成 | +| mayuanfei | https://note.youdao.com/s/XpvKnxAb | 入门专栏(新人必看) | +| 程序猿一枚_ | https://blog.csdn.net/zhaozhiqiang1981/category_12221291.html | 玩转RuoYi-Cloud-Plus | +| 程序猿一枚_ | https://www.bilibili.com/video/BV1yA411r7ji/ | Cloud环境搭建以及进阶开发 | +| MichelleChung | https://blog.csdn.net/michelle_zhong/category_11109741.html | 源码解析专栏(进阶必看) | +| MichelleChung | https://blog.csdn.net/michelle_zhong/category_12058476.html | Cloud源码解析专栏 | diff --git a/ruoyi-admin/src/main/resources/static/common/contribution.md b/ruoyi-admin/src/main/resources/static/common/contribution.md new file mode 100644 index 00000000..6b5909e6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/contribution.md @@ -0,0 +1,69 @@ +# 参与贡献的方式 +- - - +参与贡献开源的方式有很多种 听作者来介绍 + +## 为开源项目点一个Star + +> Star的多少关系到项目能否被更多人看到 +
+同时Star也是作者前进的动力(作者每天都在盯着Star 涨了会开心 跌了会失落) +
+
+> 大家在寻找开源项目的时候, 大多数情况也是会先看Star比较多的项目 +
+所以请给您觉得好的开源项目点一个小小的Star, 让好的项目能够被更多人看到 +
+ +![输入图片说明](https://foruda.gitee.com/images/1678934493115487351/0c45121e_1766278.png "屏幕截图") +
+Vue版本: [Gitee我要点Star](https://gitee.com/dromara/RuoYi-Vue-Plus/stargazers) [Github我要点Star](https://github.com/dromara/RuoYi-Vue-Plus) +
+Cloud版本: [Gitee我要点Star](https://gitee.com/dromara/RuoYi-Cloud-Plus/stargazers) [Github我要点Star](https://github.com/dromara/RuoYi-Cloud-Plus) + +## 为社区处理问题 + +> Issue是社区的交流地 大家会在这里提出自己的问题 或者是项目的功能异常 + +> 提问的规范在Issue的模板里已经写好了 按照模板填写有助于作者或者其他社区人员快速有效的回答问题 +![输入图片说明](https://foruda.gitee.com/images/1678935068341532603/4b9d7af9_1766278.png "屏幕截图") + +> 为提出问题的小伙伴答疑 可以有效降的帮助别人
+> 而且可以降低社区人员的精力分散 使精力全部投入到项目设计研发中 +![输入图片说明](https://foruda.gitee.com/images/1678935380481365514/dddc9ce9_1766278.png "屏幕截图") + +## 改进社区文档 + +> 大家都知道 我们程序员都不擅长写作
+> 有时候作者把文档写完了也不知道用户是什么感觉 是否能看懂
+ +> 所以参与社区文档建设绝对是一件意义重大的事情
+> 大家可以在Issue提出观后感 觉得哪看不懂 觉得哪应该详细说明
+> 当然了 大家也可以对文档进行改进后提交PR修改申请 + +文档仓库: [plus-doc](https://gitee.com/JavaLionLi/plus-doc) 👈点他点他 +![输入图片说明](https://foruda.gitee.com/images/1678935992827063291/d7c4dc5b_1766278.png "屏幕截图") + +## 贡献代码 + +> 想参与贡献代码的小伙伴 重点来了: 作者会经常在Issue里发布需求认领
+> 觉得自己能做的可以在Issue里跟作者讨论 如需求还不够清晰 或者做的过程中遇到了什么问题 + + + +> 需求确定了以后就可以开始专注的写代码了
+> 但在开始写代码之前 一定要先看一下如何正确的提交PR + +一点要仔细看: [如何提交PR](/common/pr.md) 👈点他点他 + +## 如何成为项目成员 + +> 1.对框架有重大贡献者(由作者与团队成员判定)
+> 2.完成社区发布的两项复杂任务
+> 3.持续完成社区发布的简单任务若干(作者会关注到)
+> 4.持续为社区优化文档或处理issue若干(作者会关注到)
+ +## 项目成员待遇 + +> 1.可免费进入vip收费群
+> 2.每年还会发放IDEA正版授权
+ diff --git a/ruoyi-admin/src/main/resources/static/common/demo_system.md b/ruoyi-admin/src/main/resources/static/common/demo_system.md new file mode 100644 index 00000000..18424e90 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/demo_system.md @@ -0,0 +1,13 @@ +# 系统演示(请大家不要乱改数据 影响他人体验 谢谢配合) +- - - +**感谢 `孤舟烟雨` 贡献的演示服务器** + +**1核2G 小服务器 经不起压测 请理性操作 违者直接封IP** + +> 访问地址: [http://43.138.9.96/](http://43.138.9.96/) + +> 登录账户 admin/admin123 + +> Admin监控中心 ruoyi/123456 + +> 任务调度中心 admin/123456 diff --git a/ruoyi-admin/src/main/resources/static/common/pr.md b/ruoyi-admin/src/main/resources/static/common/pr.md new file mode 100644 index 00000000..e9684dd4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/pr.md @@ -0,0 +1,37 @@ +# 如何提交PR贡献代码 +- - - +### 步骤一 Fork项目到自己仓库 + +![输入图片说明](https://foruda.gitee.com/images/1673427084798343408/142a55d0_1766278.png "屏幕截图") + +### 步骤二 基于dev分支 新建一个此PR功能点的专属分支 + +![输入图片说明](https://foruda.gitee.com/images/1673427220695789412/14c4f4ff_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1673427193964585607/16ea99d9_1766278.png "屏幕截图") + +### 步骤三 使用Git工具 将自己仓库的项目拉去到本地做代码编写 + +![输入图片说明](https://foruda.gitee.com/images/1673427313201488937/f2df59bf_1766278.png "屏幕截图") + +### 步骤四 使用Idea打开项目 切换到新建的功能分支 + +![输入图片说明](https://foruda.gitee.com/images/1673427394686229310/c479a5a5_1766278.png "屏幕截图") + +### 步骤五 将编写好的代码 提交到自己的远程仓库 + +![输入图片说明](https://foruda.gitee.com/images/1673427519150795591/d88c2bc9_1766278.png "屏幕截图") + +### 步骤六 创建PR申请(此操作在自己仓库或者要PR的仓库都可以) + +![输入图片说明](https://foruda.gitee.com/images/1673427616155043776/fe2ce097_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1673427865031025513/0f58a137_1766278.png "屏幕截图") + +### 步骤七 等待作者评审 按要求更改 直到没有问题后被作者合并 + +![输入图片说明](https://foruda.gitee.com/images/1673428029932524584/93234628_1766278.png "屏幕截图") + +### 评审期间 如需对PR内容做更改 直接在新功能分支提交代码即可 +### 无需重复提交PR申请 这边会自动比对两个分支的差异 + +![输入图片说明](https://foruda.gitee.com/images/1673428054139366497/4ecb6e98_1766278.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/common/user_register.md b/ruoyi-admin/src/main/resources/static/common/user_register.md new file mode 100644 index 00000000..9c221cd1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/user_register.md @@ -0,0 +1,80 @@ +# 使用者登记 +- - - +**使用此开源项目的公司或者组织** +> Vue版本登记地址: https://gitee.com/dromara/RuoYi-Vue-Plus/issues/I4QP39 + +> Cloud版本登记地址: https://gitee.com/dromara/RuoYi-Cloud-Plus/issues/I4VJ7G + +| 公司名 | 官网 | LOGO | +|-------------------|:-------------------------------|----------------------------------------------------------------------------------------------------------------| +| 中国联通(长春分公司) | http://www.10010.com | | +| 中国电信(湖南分公司) | http://www.189.cn/hn/ | | +| 南京感知信息技术有限公司 | https://njgzxx.cn/ | | +| 陕西骏景索道运营管理有限公司 | https://www.junjingsuodao.com/ | | +| 悠码科技有限公司 | https://orise.trytowish.cn/ | | +| 苏州龙的信息系统股份有限公司 | http://www.longdayinfo.com/ | | +| 北京数通智达科技有限公司 | http://www.bzdtech.com/ | | +| 广州六六七七科技有限公司 | https://artiversehub.ai/ | | +| 宁波三品软件科技有限公司 | http://nbsanpin.com/ | | +| 北京御一科技信息技术有限公司 | https://www.yudoctor.com | | +| 成都卡恩特医疗科技有限公司 | http://www.scknot.com | | +| 无锡科艾思科技有限公司 | https://www.kyoeis.com | | +| 深圳市海联天下科技有限公司 | www.sealinkin.com | | +| 上海非定义旅游服务有限公司 | http://www.anonymity.love/ | | +| 重庆威爱云科技有限公司 | https://www.51vive.com | | +| 中城智联(成都)创新科技有限公司 | http://www.zc-zl.com/ | | +| 浙江海亮股份有限公司 | https://www.hailiangstock.com | | +| 河北雄安山禾咨询工程有限公司 | https://shanheqei.club/ | | +| 数舵(河北雄安)信息科技有限公司 | http://www.shuduokeji.com | | +| 南昌鼎欣科技股份有限公司 | https://www.openzt.com | | +| 东莞市码载网络科技有限公司 | https://www.codeload.top | | +| 北京农信通科技有限责任公司 | http://www.nxt.com.cn | | +| 中康腾华网络科技(重庆)有限公司 | https://www.zkthwlkj.com/ | | +| 杭州码恒信息科技有限公司 | http://www.mh-barcode.com/ | | +| 南京晶益科技有限公司 | https://www.nanjingjingyi.com/ | | +| 合肥智享亿云科技有限公司 | http://www.izxyy.com | | +| 锡简科技 | https://www.xj-fast.com | | +| 福建亘前科技有限公司 | https://genqian.top | | +| 北京联宇信通科技有限公司 | http://www.lyxtkj.com/ | | +| 厦门市熵时光科技有限公司 | https://www.xetsoft.com | | +| 广州润沁教育科技有限公司 | https://www.ca163.net | | +| 广东乐善智能装备股份有限公司 | https://www.china-leshan.com/ | | +| 数字江西科技有限公司 | https://www.digitaljx.com/ | | +| 上海极锐星瀚传感技术有限公司 | http://www.jrsensing.com/ | | +| 北京数影互联科技有限公司 | http://www.dataflying.top/ | | +| 广州创服信息科技有限公司 | https://www.cfkjcloud.com | | +| 茂名云智科技有限公司 | http://www.winzkj.com | | +| 成都时光旅迹科技有限公司 | https://www.ttmup.com/ | | +| 成都炫影全息科技有限公司 | http://xyqxgs.com | | +| 中山厚德快速模具有限公司 | http://hordrt.com | | +| 深圳市深南夙星科技有限公司 | http://www.szsnsx.com/ | | +| 陕西华恒军创信息科技有限公司 | http://hhjc.cc | | +| 河南小牛信息科技有限公司 | http://www.hnxn888.com/ | | +| 武汉华智讯网络信息技术有限公司 | http://www.xun188.com | | +| 易税信息技术有限公司 | https://www.etax.top | | +| 广西华景城建筑设计有限公司 | http://www.hjcadc.com | | +| 铭创科技有限公司 | https://www.mcck.cn/ | | +| 西安鼎慧网络科技有限公司 | | | +| 营口鼎瑞网络科技有限公司 | | | +| 南昌漫库书店有限公司 | | | +| 广西文韬智能科技有限公司 | | | +| 贵州亿瑞祺科技有限公司 | | +| 贵州新绿视界环保科技有限公司 | | +| 湖南智才伯乐数据科技有限公司 | | +| 德州商储超市有限公司 | | +| 曲沃亿分科技中心 | | +| 南京杰度信息技术有限公司 | | +| 武汉忆秋科技有限公司 | | +| 济南千惠网络科技有限公司 | | +| 江苏泛联科技有限公司 | | +| 沈阳市果冻网络信息科技有限责任公司 | | +| 灵劲科技有限公司 | | +| 亿世达餐饮管理(北京)有限公司 | | +| 深圳市凯帝电子商务有限公司 | | +| 成都数智源蓉卡科技有限公司 | | +| 上海振福信息科技有限公司 | | +| 重庆六客会科技有限公司 | | +| 无限创优(西安)科技有限公司 | | +| 惠族网络科技发展有限公司 | | +| 纳森科技有限公司 | | + diff --git a/ruoyi-admin/src/main/resources/static/common/video.md b/ruoyi-admin/src/main/resources/static/common/video.md new file mode 100644 index 00000000..14fc2752 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/common/video.md @@ -0,0 +1,85 @@ +# 视频教程(联合出品) + +### 主讲与后期剪辑: `抓蛙师` + +抓蛙师简介: B站知名UP主 B站首页: https://space.bilibili.com/520725002 + +### 知识点统筹与内容审核: `疯狂的狮子Li` + +疯狂的狮子Li简介: RuoYi-Vue-Plus 与 RuoYi-Cloud-Plus 作者 + +## 已完结🎉🎉🎉 优惠价: 598(仅限前500名) ~~原价: 698~~ + +**注意: 视频采用 RuoYi-Vue-Plus 版本 4.X 分支讲解!!! (内容为通用技术与版本关联性不大)**
+**内容为框架内所用到的技术与设计原理(打破不知道、不会用、不知应用场景等问题)** + +课程简介: https://www.bilibili.com/video/BV16j411D7BX/ +
+试看课程: https://www.bilibili.com/video/BV1uS411P7JD/ +
+试看课程: https://www.bilibili.com/video/BV1vLbNeuESn/ +
+试看课程: https://www.bilibili.com/video/BV1xV4y127KM/ +
+试看课程: https://www.bilibili.com/video/BV1W5v8eBEgs/ +
+课程总结: https://www.bilibili.com/video/BV1734y1g7fk/ +
+ +## 购买方式 + +**小本生意 用心录制 拒绝砍价 已更新到 236 集 课程完结**
+> 课程咨询或购买请联系 价格598
+> QQ: 906670865 (疯狂的狮子Li)
+> QQ: 770492966 (抓蛙师) + +## 购买前常见问题答疑 +> 问题1: 购买后是否有群可以解答问题
+> 答: 购买后有专属课程付费群(千人大群)讲师在线答疑 +> +> 问题2: 是否持续更新 如新版本功能
+> 答: 课程目录即为全部课程内容 以课程目录为准 明年大概会出二期来讲新版本内容
+> 因为持续更新会导致前面的技术老旧 新购买的人无法及时学习新技术
+> 故而采用分期出课程制度 已经购买过的老客户 再次购买下一次会给力度非常大的折扣 +> +> 问题3: 目前视频未全部录制完成 后续更新是否二次收费
+> 答: 视频目录即为全部视频内容 一次收费后续更新仍然可看直到视频全部更新完成(明年出二期课程不算在内) +> +> 问题4: 视频如何下载如何观看
+> 答: 视频文件已加密 采用专门的播放器(播放器只限制截图录屏等不限制其他软件使用) 由管理员发放授权码观看
+> 支持通过 百度云 或者 阿里云 网盘下载视频资源 +> +> 问题5: 视频平均时长和总时长大概多久
+> 答: 视频每集短的大概10分钟以上 长的大概40个分钟左右 平均时长20多分钟每集
+> 目前已经录制了236集总时长为80多个小时 +> +> 问题6: 是否有讲解 Cloud 版本相关内容
+> 答: 视频主要讲解内容为框架内所用到的技术与设计原理 无论什么版本 功能和设计都是一样的
+> Cloud 版本只是多了 alibaba 的几个组件完全可以B站自学 + +## 课程目录 + +![输入图片说明](https://foruda.gitee.com/images/1695105467795304336/58fcd6db_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105494170842444/10f98fed_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105523526589287/f131c614_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105547992880680/9f4137f3_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105560849590514/d19fad6a_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105586641712428/349a971b_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105595501187093/fb819d35_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105609163585390/833dd89c_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105630469565265/8dbba1d2_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695105659037093525/09a4f6e1_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1695714493079698007/311980ee_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1697446957351573520/cab3617d_1766278.png "屏幕截图") + +## 学员观后感 + +| | | +|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| ![输入图片说明](https://foruda.gitee.com/images/1691386100129796781/44b69dae_1766278.jpeg "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1691386076834242484/a6073f7d_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1691386089186649583/98ac8b7c_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1691386108722171132/b937b23a_1766278.jpeg "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1695714607596127461/513b6893_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1692804549604261480/09ef12f6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1692804541482477905/578e5448_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1695714614517941469/cac681fb_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1698225407961714462/4d271901_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1698225416488201339/30572e7f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1698807198508085566/16c37a1b_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1698807208125772586/ceed632e_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1698807214013013096/ad3bc016_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1698807221010472627/72b10901_1766278.png "屏幕截图") | diff --git a/ruoyi-admin/src/main/resources/static/index.html b/ruoyi-admin/src/main/resources/static/index.html index 4e955668..76e44328 100644 --- a/ruoyi-admin/src/main/resources/static/index.html +++ b/ruoyi-admin/src/main/resources/static/index.html @@ -1,104 +1,74 @@ - - + - - - - - - - - 熊猫助手 - - - - + + plus-doc + + + + + + + + + - - - - - -
-
-
-
- -

- ageerle -

-

Code Create Life

-
-

如何得与凉风约, 不共尘沙一并来! -
- -「中牟道中」 -

- -
-
-
-
-
- -
-

- Copyright © 2023-2024 版权所有:xmzs 备案号:鄂ICP备20231008611号 -

-
-
- - + + + diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/_sidebar.md b/ruoyi-admin/src/main/resources/static/plus-ui/_sidebar.md new file mode 100644 index 00000000..00197250 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/_sidebar.md @@ -0,0 +1,22 @@ + +- **特别赞助** +- [![输入图片说明](https://foruda.gitee.com/images/1704162419429172656/d0521e59_1766278.png "2024-01-02=>2028-01-02")](http://ccflow.org/?frm=ryPlus) +- [![输入图片说明](https://foruda.gitee.com/images/1705569347386939952/3f187980_1766278.jpeg "2024-01-18=>2025-01-18")](http://www.shuduokeji.com) +- [![输入图片说明](https://foruda.gitee.com/images/1711681233267310022/2ffbcff2_1766278.png "2024-03-29=>2025-03-29")](https://www.jnpfsoft.com/index.html?from=plus-doc) + + +* **简介** + * [项目简介](/plus-ui/home.md) +* **开发文档** + * [通用方法](/plus-ui/devdoc/common_func.md) + * [开发规范](/plus-ui/devdoc/dev_norm.md) + * [请求流程](/plus-ui/devdoc/request_process.md) + * [路由使用](/plus-ui/devdoc/router_use.md) + * [组件使用](/plus-ui/devdoc/component_use.md) + * [权限使用](/plus-ui/devdoc/permissions_use.md) + * [页签缓存](/plus-ui/devdoc/page_cache.md) + * [使用图标](/plus-ui/devdoc/icon_use.md) + * [使用字典](/plus-ui/devdoc/dict_use.md) + * [使用参数](/plus-ui/devdoc/param_use.md) + * [异常处理](/plus-ui/devdoc/exception_handling.md) + * [内容复制](/plus-ui/devdoc/content_copy.md) diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/common_func.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/common_func.md new file mode 100644 index 00000000..e359c03c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/common_func.md @@ -0,0 +1,234 @@ +# 通用方法 +- - - + +### $tab对象 +> `$tab`对象用于做页签操作、刷新页签、关闭页签、打开页签、修改页签等,它定义在`plugins/tab.ts`文件中,它有如下方法 +* 打开页签 + +```typescript +// 打开页签 +proxy?.$tab.openPage('/system/user'); +// 打开页签并指定页签标题 +proxy?.$tab.openPage('/system/user', '用户管理'); +proxy?.$tab.openPage('/system/user', '用户管理').then(() => { + // 执行结束的逻辑 +}) +``` + +* 修改页签 + +```typescript +// 修改当前页签 +const obj = Object.assign({}, route, { title: '自定义标题' }); +proxy?.$tab.updatePage(obj); +``` +* 关闭页签 + +```typescript +// 关闭当前 +proxy?.$tab.closePage(); +// 关闭指定页签 +const obj = { path: "/system/user", name: "User" }; +proxy?.$tab.closePage(obj); + +proxy?.$tab.closePage(obj).then(() => { + // 执行结束的逻辑 +}) +``` + +* 刷新页签 + +```typescript +// 刷新当前页签 +proxy?.$tab.refreshPage(); + +// 刷新指定页签 +const obj = { path: "/system/user", name: "User" }; +proxy?.$tab.refreshPage(obj); + +proxy?.$tab.refreshPage(obj).then(() => { + // 执行结束的逻辑 +}) +``` + +* 关闭所有页签 + +```typescript +proxy?.$tab.closeAllPage(); + +proxy?.$tab.closeAllPage().then(() => { + // 执行结束的逻辑 +}) +``` + +* 关闭左侧页签 + +```typescript +// 关闭当前页签的左侧页签 +proxy?.$tab.closeLeftPage(); + +// 关闭指定页签的左侧页签 +const obj = { path: "/system/user", name: "User" }; +proxy?.$tab.closeLeftPage(obj); + +proxy?.$tab.closeLeftPage(obj).then(() => { + // 执行结束的逻辑 +}) +``` + +* 关闭右侧页签 + +```typescript +// 关闭当前页签的右侧页签 +proxy?.$tab.closeRightPage(); + +// 关闭指定页签的右侧页签 +const obj = { path: "/system/user", name: "User" }; +proxy?.$tab.closeRightPage(obj); + +proxy?.$tab.closeRightPage(obj).then(() => { + // 执行结束的逻辑 +}) +``` + +* 关闭其他页签 + +```typescript +proxy?.$tab.closeOtherPage(); + +const obj = { path: "/system/user", name: "User" }; +proxy?.$tab.closeOtherPage(obj); + +proxy?.$tab.closeOtherPage(obj).then(() => { + // 执行结束的逻辑 +}) +``` + +### $modal对象 +> `$modal`对象用于做消息提示、通知提示、对话框提醒、二次确认、遮罩等,它定义在`plugins/modal.ts`文件中,它有如下方法 + +* 提供成功、警告和错误等反馈信息 + +```typescript +proxy?.$modal.msg("默认反馈"); +proxy?.$modal.msgError("错误反馈"); +proxy?.$modal.msgSuccess("成功反馈"); +proxy?.$modal.msgWarning("警告反馈"); +``` + +* 提供成功、警告和错误等提示信息 + +```typescript +proxy?.$modal.alert("默认提示"); +proxy?.$modal.alertError("错误提示"); +proxy?.$modal.alertSuccess("成功提示"); +proxy?.$modal.alertWarning("警告提示"); +``` + +* 提供成功、警告和错误等通知信息 + +```typescript +proxy?.$modal.notify("默认通知"); +proxy?.$modal.notifyError("错误通知"); +proxy?.$modal.notifySuccess("成功通知"); +proxy?.$modal.notifyWarning("警告通知"); +``` + +* 提供确认窗体信息 + +```typescript +proxy?.$modal.confirm('确认信息').then(function() { + ... +}).then(() => { + ... +}).catch(() => {}); +``` + +* 提供遮罩层信息 + +```typescript +// 打开遮罩层 +proxy?.$modal.loading("正在导出数据,请稍后..."); + +// 关闭遮罩层 +proxy?.$modal.closeLoading(); +``` + +### $auth对象 +> `$auth`对象用于验证用户是否拥有某(些)权限或角色,它定义在`plugins/auth.ts`文件中,它有如下方法 + +* 验证用户权限 + +```typescript +// 验证用户是否具备某权限 +proxy?.$auth.hasPermi("system:user:add"); +// 验证用户是否含有指定权限,只需包含其中一个 +proxy?.$auth.hasPermiOr(["system:user:add", "system:user:update"]); +// 验证用户是否含有指定权限,必须全部拥有 +proxy?.$auth.hasPermiAnd(["system:user:add", "system:user:update"]); +``` + +* 验证用户角色 + +```typescript +// 验证用户是否具备某角色 +proxy?.$auth.hasRole("admin"); +// 验证用户是否含有指定角色,只需包含其中一个 +proxy?.$auth.hasRoleOr(["admin", "common"]); +// 验证用户是否含有指定角色,必须全部拥有 +proxy?.$auth.hasRoleAnd(["admin", "common"]); +``` + +### $cache对象 +> `$cache`对象用于处理缓存。我们并不建议您直接使用`sessionStorage`或`localStorage`(vue3版本推荐使用useStorage),因为项目的缓存策略可能发生变化,通过`$cache`对象做一层调用代理则是一个不错的选择。`$cache`提供`session`和`local`两种级别的缓存,如下: + +| 对象名称 | 缓存类型 | +| -------- | ---------------------------------- | +| session | 会话级缓存,通过sessionStorage实现 | +| local | 本地级缓存,通过localStorage实现 | + + +**示例** + +```typescript +// local 普通值 +proxy?.$cache.local.set('key', 'local value') +console.log(proxy?.$cache.local.get('key')) // 输出'local value' + +// session 普通值 +proxy?.$cache.session.set('key', 'session value') +console.log(proxy?.$cache.session.get('key')) // 输出'session value' + +// local JSON值 +proxy?.$cache.local.setJSON('jsonKey', { localProp: 1 }) +console.log(proxy?.$cache.local.getJSON('jsonKey')) // 输出'{localProp: 1}' + +// session JSON值 +proxy?.$cache.session.setJSON('jsonKey', { sessionProp: 1 }) +console.log(proxy?.$cache.session.getJSON('jsonKey')) // 输出'{sessionProp: 1}' + +// 删除值 +proxy?.$cache.local.remove('key') +proxy?.$cache.session.remove('key') +``` + +### $download对象 + +> `$download`对象用于文件下载,它定义在`plugins/download.ts`文件中,它有如下方法 + +* 通过ossId从存储中下载文件 + +``` typescript +// 默认下载方法 +proxy?.$download.oss(ossId); +``` + +* 根据请求地址下载zip包 + +```typescript +const url = '/tool/gen/batchGenCode?tables=' + tableNames; +const name = 'ruoyi'; + +// 默认方法 +proxy?.$download.zip(url, name); +``` diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/component_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/component_use.md new file mode 100644 index 00000000..18886db5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/component_use.md @@ -0,0 +1,55 @@ +# 组件使用 +- - - + +vue 注册组件的两种方式 +在 `@/components` 下创建的.vue文件自动为全局组件,可直接在任意位置使用。 + +### 局部注册 +在对应页使用`components`注册组件。 +```typescript + + + +``` + +### 全局注册 +我们可以使用[ Vue 应用实例](https://cn.vuejs.org/guide/essentials/application.html)的 `.component()` 方法,让组件在当前 Vue 应用中全局可用。 +```typescript +import { createApp } from 'vue' + +const app = createApp({}) + +app.component( + // 注册的名字 + 'MyComponent', + // 组件的实现 + { + /* ... */ + } +) +``` +如果使用单文件组件,你可以注册被导入的 `.vue` 文件: +```typescript +import MyComponent from './App.vue' + +app.component('MyComponent', MyComponent) +``` +`.component()` 方法可以被链式调用: +```typescript +app + .component('ComponentA', ComponentA) + .component('ComponentB', ComponentB) + .component('ComponentC', ComponentC) +``` +全局注册的组件可以在此应用的任意组件的模板中使用: +```Typescript +// 这在当前应用的任意组件中都可用 + + + +``` +所有的子组件也可以使用全局注册的组件,这意味着这三个组件也都可以在彼此内部使用。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/content_copy.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/content_copy.md new file mode 100644 index 00000000..a4150d72 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/content_copy.md @@ -0,0 +1,4 @@ +# 内容复制 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dev_norm.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dev_norm.md new file mode 100644 index 00000000..de714a13 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dev_norm.md @@ -0,0 +1,16 @@ +# 开发规范 +- - - + +### 新增view +> 在`@/views`文件下创建对应的文件夹,一般性一个路由对应一个文件, 该模块下的功能就建议在本文件夹下创建一个新文件夹,各个功能模块维护自己的`utils`或`components`组件。 + +### 新增api +> 在`@/api`文件夹下创建本模块对应的api服务。 +> 在api服务同级创建`types.ts`类型声明文件。 + +### 新增组件 +> 在全局的`@/components`写一些全局的组件,如富文本,各种搜索组件,封装的分页组件等等能被公用的组件。 每个页面或者模块特定的业务组件则会写在当前`@/views`下面。 +如:`@/views/system/user/components/xxx.vue`。这样拆分大大减轻了维护成本。 + +### 新增样式 +> 页面的样式和组件是一个道理,全局的`@/style`放置一下全局公用的样式,每一个页面的样式就写在当前 views下面,请记住加上scoped 就只会作用在当前组件内了,避免造成全局的样式污染。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dict_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dict_use.md new file mode 100644 index 00000000..7c6f9ba5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/dict_use.md @@ -0,0 +1,4 @@ +# 使用字典 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/exception_handling.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/exception_handling.md new file mode 100644 index 00000000..8de87fa2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/exception_handling.md @@ -0,0 +1,4 @@ +# 异常处理 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/icon_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/icon_use.md new file mode 100644 index 00000000..923e66ae --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/icon_use.md @@ -0,0 +1,4 @@ +# 使用图标 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/page_cache.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/page_cache.md new file mode 100644 index 00000000..0531b465 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/page_cache.md @@ -0,0 +1,4 @@ +# 页签缓存 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/param_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/param_use.md new file mode 100644 index 00000000..0cd93759 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/param_use.md @@ -0,0 +1,4 @@ +# 使用参数 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/permissions_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/permissions_use.md new file mode 100644 index 00000000..e18642f4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/permissions_use.md @@ -0,0 +1,4 @@ +# 权限使用 +- - - + +文档建设中 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/request_process.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/request_process.md new file mode 100644 index 00000000..146f7a8c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/request_process.md @@ -0,0 +1,65 @@ +# 请求流程 +- - - + +### 交互流程 +一个完整的前端UI交互到服务器端处理流程是这样的: + +1. UI 组件交互操作; +2. 调用统一管理的 api service 请求函数; +3. 使用封装的 request.js 发送请求; +4. 获取服务端返回; +5. 更新 data; + +为了方便管理维护,统一的请求处理都放在`@/src/api`文件夹中,并且一般按照`model`维度进行拆分文件,如: +``` +api/ + system/ + user/ + index.ts + types.ts + role/ + index.ts + types.ts + monitor/ + operlog/ + index.ts + types.ts + logininfor/ + index.ts + types.ts + ... +``` +> **提示** +> 其中`@/src/utils/request.ts`是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。 它封装了全局request拦截器、response拦截器、统一的错误处理、统一做了超时处理、baseURL设置等。 + +### 请求示例 +```typescript +// @/api/system/user/index.ts +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { UserQuery, UserVO } from './types'; + +export const listUser = (query: UserQuery): AxiosPromise => { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }); +}; + +// @/views/system/user/index.vue +import api from '@/api/system/user'; +const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value)); +``` +> **提示** +> 如果有不同的`baseURL`,直接通过覆盖的方式,让它具有不同的`baseURL`。 +> ```typescript +> export const listUser = (query: UserQuery): AxiosPromise => { +> return request({ +> url: '/system/user/list', +> method: 'get', +> params: query, +> baseURL: process.env.BASE_API +> }); +> }; +> ``` diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/router_use.md b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/router_use.md new file mode 100644 index 00000000..b13812bc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/devdoc/router_use.md @@ -0,0 +1,82 @@ +# 路由使用 +- - - + +框架的核心是通过路由自动生成对应导航,所以除了路由的基本配置,还需要了解框架提供了哪些配置项。 +### 路由配置 +```typescript +// 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 +hidden: true // (默认 false) + +//当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 +redirect: 'noRedirect' + +// 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 +// 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 +// 若你想不管路由下面的 children 声明的个数都显示你的根路由 +// 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 +alwaysShow: true + +name: 'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 +query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 +roles: ['admin', 'common'] // 访问路由的角色权限 +permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + +meta: { + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) + affix: true // 如果设置为true,它则会固定在tags-view中(默认 false) + + // 当路由设置了该属性,则会高亮相对应的侧边栏。 + // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list + // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置 + activeMenu: '/article/list' +} +``` +**普通示例** +```json +{ + path: '/system/test', + component: Layout, + redirect: 'noRedirect', + hidden: false, + alwaysShow: true, + meta: { title: '系统管理', icon : "system" }, + children: [{ + path: 'index', + component: (resolve) => require(['@/views/index'], resolve), + name: 'Test', + meta: { + title: '测试管理', + icon: 'user' + } + }] +} +``` +**外链示例** +```json +{ + path: 'http://ruoyi.vip', + meta: { title: '若依官网', icon : "guide" } +} +``` +### 静态路由 +代表那些不需要动态判断权限的路由,如登录页、404、等通用页面,在`@/router/index.ts`配置对应的公共路由。 +### 动态路由 +代表那些需要根据用户动态判断权限并通过addRoutes动态添加的页面,在`@/store/modules/permission.ts`加载后端接口路由配置。 +> **提示** +> * 动态路由可以在系统管理-菜单管理进行新增和修改操作,前端加载会自动请求接口获取菜单信息并转换成前端对应的路由。 +> * 动态路由在生产环境下会默认使用路由懒加载,实现方式参考loadView方法的判断。 +### 常用方法 +想要跳转到不同的页面,使用`router.push`方法 +```Typescript +const router = useRouter(); +router.push({ path: "/system/user" }); +``` +跳转页面并设置请求参数,使用`query`属性 +```Typescript +const router = useRouter(); +router.push({ path: "/system/user", query: {id: "1", name: "若依"} }); +``` +更多使用可以参考[vue-router](https://router.vuejs.org/zh/)官方文档。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/plus-ui/home.md b/ruoyi-admin/src/main/resources/static/plus-ui/home.md new file mode 100644 index 00000000..b389b5ca --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/plus-ui/home.md @@ -0,0 +1,53 @@ +# 项目简介 + +--- + +## 平台简介 + +- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 +- 配套后端代码仓库地址 +- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus) +- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus) + +## 前端运行 + +```bash +# 克隆项目 +git clone https://gitee.com/JavaLionLi/plus-ui.git + +# 安装依赖 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev + +# 推荐使用yarn或pnpm包管理工具 +# 构建测试环境 yarn build:stage +# 构建生产环境 yarn build:prod +# 前端访问地址 http://localhost:80 +``` + +## 后端改造 + +参考后端代码内 `ruoyi-gen/resources/vm/vue/v3/readme.txt` 说明 + +## 内置功能 + +1. 租户管理:配置系统租户,支持 SaaS 场景下的多租户功能。 +2. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +3. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +4. 岗位管理:配置系统用户所属担任职务。 +5. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +6. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +7. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +8. 参数管理:对系统动态配置常用参数。 +9. 通知公告:系统通知公告信息发布维护。 +10. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +11. 登录日志:系统登录日志记录查询包含登录异常。 +12. 在线用户:当前系统中活跃用户状态监控。 +13. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +14. 代码生成:前后端代码的生成(java、html、xml、sql)支持 CRUD 下载 。 +15. 系统接口:根据业务代码自动生成相关的 api 接口文档。 +16. 服务监控:监视当前系统 CPU、内存、磁盘、堆栈等相关信息。 +17. 缓存监控:对系统的缓存信息查询,命令统计等。 +18. 在线构建器:拖动表单元素生成相应的 HTML 代码。(TS 版本正在开发中。) diff --git a/ruoyi-admin/src/main/resources/static/questions/_sidebar.md b/ruoyi-admin/src/main/resources/static/questions/_sidebar.md new file mode 100644 index 00000000..2d74fb20 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/_sidebar.md @@ -0,0 +1,34 @@ + +- **特别赞助** +- [![输入图片说明](https://foruda.gitee.com/images/1704162419429172656/d0521e59_1766278.png "2024-01-02=>2028-01-02")](http://ccflow.org/?frm=ryPlus) +- [![输入图片说明](https://foruda.gitee.com/images/1705569347386939952/3f187980_1766278.jpeg "2024-01-18=>2025-01-18")](http://www.shuduokeji.com) +- [![输入图片说明](https://foruda.gitee.com/images/1711681233267310022/2ffbcff2_1766278.png "2024-03-29=>2025-03-29")](https://www.jnpfsoft.com/index.html?from=plus-doc) + + + +* **常见问题** + * [Lombok注解爆红](/questions/lombok.md) + * [如何使用Tomcat](/questions/use_tomcat.md) + * [如何使用druid连接池](/questions/use_druid.md) + * [vue与boot整合部署](/questions/deploy_vue.md) + * [导入excel实体类为空](/questions/import_excel.md) + * [如何同步项目更新](/questions/synchronous_update.md) + * [ParseException SQL解析异常](/questions/parse_exception.md) + * [swagger相关问题](/questions/swagger.md) + * [实体bean为空问题](/questions/bean_null.md) + * [Redis 报错 Permission denied](/questions/permission_denied.md) + * [关于HTTPS配置](/questions/https_config.md) + * [放行接口提示认证失败](/questions/identify_fail.md) + * [打包jar运行报错](/questions/jar_run_fail.md) + * [如何指定dubbo注册ip](/questions/dubbo_ip.md) + * [Sentinel页面404问题](/questions/sentinel_404.md) + * [无法读取nacos配置](/questions/nacos_read_fail.md) + * [接口文档对接knife4j](/questions/kinfe4j.md) + * [不支持ST请求](/questions/st_not_support.md) + * [Only one connection receive subscriber allowed](/questions/only_one_subscriber.md) + * [nacos 报错 The Raft Group [naming_instance_metadata]](/questions/nacos_naming_instance_metadata.md) + * [unable to read meta-data for class xxx](/questions/read_metadata.md) + * [JCE cannot authenticate the provider BC](/questions/jce_cannot.md) + * [关于请求响应参数解密](/questions/api_encrypt.md) + * [关于登录调试步骤](/questions/login_step.md) + * [如何对接国产数据库](/questions/domestic_databases.md) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/api_encrypt.md b/ruoyi-admin/src/main/resources/static/questions/api_encrypt.md new file mode 100644 index 00000000..95b516d4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/api_encrypt.md @@ -0,0 +1,148 @@ +# 关于请求响应参数解密 +--- +## 1:前端加密请求 + +![输入图片说明](https://foruda.gitee.com/images/1717033672316716771/8e30a2f1_4959041.png "屏幕截图") + +通过控制台获取加密结果: + +![输入图片说明](https://foruda.gitee.com/images/1717033792384655437/900a0e0d_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1717033896868612970/55581f0a_4959041.png "屏幕截图") + + +加密密钥: + +``` +PAg/fZzpV/cz0T1fMUJMJo/LEZvwVLb4bZgtCHkbB6FQAJWlLm/RLKtQ5fOo1blMjAkY+9ryWhsAfCqoMPTU4w== +``` + +请求参数加密结果: + +``` +F+Qxq6PzShcudDsUZHhp50lA67eBeTe63x5uGbdm/HJGgcDmjKncUk5VQm0evD8pz1sbmCbmmSl3X1D07K/qgHvP1YhjYSRBJf/M0GTfMkfOZqIkOtvfE5Z6fSFd8RYf6ji/qYxAmCiRmP/uADyJUAoBY1gMi5+zuvyHH3In/FyoFeD0rmJWvO4o4fn3n5GElHMWbP0O/HWPfgHFfg1F7bZQPuf4zAuDKQIqUG3jJTem3O97kAbTWw6lSSuYi1/8tV4cE9rq8SMSjx36/ZLSog== +``` + +### 解密步骤 + +1. 使用配置文件私钥对加密密钥解密 + +```java +// 参数说明: +// requestKey:即请求标头加密密钥 +// privateKey:application.yml 配置文件私钥 +String decryptByRsa = EncryptUtils.decryptByRsa(requestKey, privateKey); +``` + +2. 对步骤一结果进行 Base64 解密,得到 AES 加密密钥 + +```java +String aesPassword = EncryptUtils.decryptByBase64(decryptByRsa); +``` + +3. 使用步骤二得到的密钥,对请求参数进行解密 + +```java +String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword); +``` + +得到解密请求参数(已格式化): + +```json +{ + "tenantId": "000000", + "username": "admin", + "password": "admin123", + "rememberMe": false, + "uuid": "a39962b22c874f60872ef5db1cd811f5", + "code": "5", + "clientId": "e5cd7e4891bf95d1d19206ce24a7b32e", + "grantType": "password" +} +``` + +|参数名|说明| +|---|---| +|tenantId| 租户id | +|username| 用户名 | +|password| 密码 | +|rememberMe| 记住密码 | +|uuid| - | +|code| 验证码结果 | +|clientId| 客户端id(表 sys_client) | +|grantType| 授权类型(表 sys_client) | + +## 2:后端加密响应 + +对请求使用了注解 `@ApiEncrypt(response = true)` + +![输入图片说明](https://foruda.gitee.com/images/1717035066844744866/2286b394_4959041.png "屏幕截图") + +通过控制台获取加密结果: + +![输入图片说明](https://foruda.gitee.com/images/1717035156784270596/156f2aa7_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1717035193189175688/214631e5_4959041.png "屏幕截图") + +加密密钥: + +``` +MXnKYnXcXeFYWKZg8utuhDtbz54cPDcov11E1KT5l19/vMt37d4NhzzwBWnqug72SOgOK5URGaWPJSs9VdaP0Q== +``` + +响应参数加密结果: + +``` +70 O63EMmwvbAyWPqDDmVOGTy+BOQnIVgKInMFNRtp8Zwzs8DEL20VgL2IslYrL8bc1u7lPhYNU/6 Q3iTYebm4EokwiG+styaT+LO3M9bUimggoAGpBTW8gCRF/34 kJaOITSRqYqYcXIJKn73+Gqn7jevyKUHyRXog/3 q/PlBdmUjNiB4gtxlOO/Vm+4 o+0 W4jcEe0xwwzV91+Ze3S6Eu/1 XN21g0iOsYT34emv/vhd9Hy3p5LfJlAHvn96x/c3MQBQUU32uM3Vkk3o6IpVHjJljE64gnGximSwB9vrmMA21xX+fq9HYioumknmDDbaY/JAKh32CDgn5M5hdaIklf08sU38r1IyvipySzrHX+ci9GmOZhP2ttCtoZ7SGvFFbNEuyojssxwxXEmJHAsG/OhIAeRXMUr3+dzDJ++XvvMuMgNJR0BMldNydFAjNOQEszgcVM1QEGwxfW5rElW8VxQaaqPyDATX+y2JrK1vdKxxdI/hF5dGpQMdU4FAEhHIftoIbD/FH4XcWJamZjJpbVtZvTkFYpbhiU7sz9MICSuKwaoSFJ8JGANc0bDdVoWpA8sXi7a27IM0pDzk9gD/FADcFGHXxPYUhENkXiUcnmg5LSdigiY4J6HrqEJdH6zNSwoGubcsXhiPdlB3V0DqcLAHFt+GYj5lcxZeqUAmixGVGCV7gSBWNiyo9/NnXcynA/EIlV3OZIvgzjWxiKzcVJ1HOKoXGEcg3Q54QNh5pCqEa7AtqVkKO7/Ffgg8nSEeCdJPzTV7zmr3n94Hn671OL8A== +``` + +### 解密步骤 + +1. 使用前端配置文件私钥对加密密钥解密 + +```java +// 参数说明: +// responseKey:即响应标头加密密钥 +// privateKey:前端 .env.development | .env.production 配置文件私钥,注意和后端私钥区分 +String decryptByRsa = EncryptUtils.decryptByRsa(responseKey, privateKey); +``` + +2. 对步骤一结果进行 Base64 解密,得到 AES 加密密钥 + +```java +String aesPassword = EncryptUtils.decryptByBase64(decryptByRsa); +``` + +3. 使用步骤二得到的密钥,对响应参数进行解密 + +```java +String decryptBody = EncryptUtils.decryptByAes(responseBody, aesPassword); +``` + +得到解密请求参数(已格式化): + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "scope": null, + "openid": null, + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJjOVNWU1hRRVY4QVhFRkt4b2FrbndSSWxPczd4ajdRZCIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MSwidXNlck5hbWUiOiJhZG1pbiIsImRlcHRJZCI6MTAzLCJkZXB0TmFtZSI6IueglOWPkemDqOmXqCJ9.YuaXPu6eTzJVkLyQC3ekzmPS_jXp50ykaIB2nWy11qM", + "refresh_token": null, + "expire_in": 604799, + "refresh_expire_in": null, + "client_id": "e5cd7e4891bf95d1d19206ce24a7b32e" + } +} +``` + +|参数名|说明| +|---|---| +|scope| 令牌权限 | +|openid| 用户 openid | +|access_token| 授权令牌 | +|refresh_token| 刷新令牌 | +|expire_in| 授权令牌 access_token 的有效期 | +|refresh_expire_in| 刷新令牌 refresh_token 的有效期 | +|clientId| 客户端id(表 sys_client) | \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/bean_null.md b/ruoyi-admin/src/main/resources/static/questions/bean_null.md new file mode 100644 index 00000000..bf04048c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/bean_null.md @@ -0,0 +1,10 @@ +# 实体bean为空问题 +- - - +### 问题排查 + +检查是否存在 `链式调用` 注解 `@Accessors(chain = true)` 删除即可 + +### 原因 +java 规范 set 返回值为 `void` 链式调用 set 返回值为 `this`
+故多数框架底层使用 jdk 工具导致找不到 set 方法
+例如: `easyexcel` `cglib` `mybatis` 等 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/deploy_vue.md b/ruoyi-admin/src/main/resources/static/questions/deploy_vue.md new file mode 100644 index 00000000..348a1680 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/deploy_vue.md @@ -0,0 +1,13 @@ +# 关于vue与boot整合部署 +- - - +* [前端静态资源如何整合到后端访问](https://doc.ruoyi.vip/ruoyi-vue/other/faq.html#前端静态资源如何整合到后端访问) + +3.X 需在 `pom.xml` 增加资源过滤排除 + +```xml + + src/main/resources/页面目录 + + false + +``` diff --git a/ruoyi-admin/src/main/resources/static/questions/domestic_databases.md b/ruoyi-admin/src/main/resources/static/questions/domestic_databases.md new file mode 100644 index 00000000..e79762ba --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/domestic_databases.md @@ -0,0 +1,41 @@ +# 如何对接国产数据库 + +> 1. 框架采用 mybatis-plus 几乎支持大部分市面上的数据库且框架内几乎没有sql语句存在 +
+所以不用担心兼容性问题(顶多就是有一些关键字什么的 对接很简单) +
+> 2. 国产数据库大多都兼容主流三大数据库 mysql oracle postgresql +
+例如 达梦兼容oracle 人大金仓兼容mysql oceanbase兼容mysql 等等 + +# 对接方式 + +### 这里用 `达梦` 数据库为例 + +1.首先增加 jdbc依赖包 `vue版本在ruoyi-admin模块下` `cloud版本在ruoyi-common-mybatis模块下` + +![输入图片说明](https://foruda.gitee.com/images/1723288594335994875/216ae8e7_1766278.png "屏幕截图") + +2.在配置文件yml内配置数据库连接 + +![输入图片说明](https://foruda.gitee.com/images/1723288760519808620/3db91ba5_1766278.png "屏幕截图") + +3.sql脚本使用框架内自带的sql文件根据兼容的数据库模式 例如 达梦用oracle的sql脚本 + +![输入图片说明](https://foruda.gitee.com/images/1723289018873298537/4d95c892_1766278.png "屏幕截图") + +4.在代码生成器内 增加对应的数据库生成器依赖 代码生成器使用 anyline 支持几百种数据库只需要增加对应的依赖即可 + +![输入图片说明](https://foruda.gitee.com/images/1723288974693848785/3e8fc61f_1766278.png "屏幕截图") + +这样基本就完成了所有需要做的事可以尝试启动项目了 + +5.如果项目启或者运行动过程中有sql报错 不要慌基本上都是一些关键字引起的 +
+例如 达梦内的`domain`就是关键字 在我们的`SysOssConfig`表内使用`domain`进行自定义的域名存储 +
+我们只需要在`SysOssConfig`实体类的`domain`属性增加一个注解即可解决此问题 +
+**注意: 各种数据库处理关键字的标识符不一样注意替换** + +![输入图片说明](https://foruda.gitee.com/images/1723289232470339283/480d5172_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/questions/dubbo_ip.md b/ruoyi-admin/src/main/resources/static/questions/dubbo_ip.md new file mode 100644 index 00000000..43cb2346 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/dubbo_ip.md @@ -0,0 +1,18 @@ +# 如何指定dubbo注册ip +- - - +## 重点说明 +以下方法指定IP必须是本地有网卡的自己可以访问的IP 不可以随意乱写
+(云服务器公网IP是没有网卡的) + +## 在`nacos`指定协议IP地址(全局生效) +```yml +dubbo: + protocol: + # 指定dubbo协议注册ip + host: 192.168.0.100 +``` + +## docker指定dubbo环境变量(单服务生效) + +![输入图片说明](https://foruda.gitee.com/images/1678981332028792584/7eeef9c5_1766278.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/questions/https_config.md b/ruoyi-admin/src/main/resources/static/questions/https_config.md new file mode 100644 index 00000000..e590729e --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/https_config.md @@ -0,0 +1,27 @@ +# 关于HTTPS配置 +- - - +### 后端 HTTPS 改造 + +将申请的 `https` 证书放置到 `nginx` 对应目录内
+根据框架 `nginx https` 示例 更改后端代理为 `https`
+ +![输入图片说明](https://foruda.gitee.com/images/1678981283573122208/87cf19ad_1766278.png "屏幕截图") + +### 监控中心 与 任务调度中心 改造 + +`监控中心` 与 `任务调度中心` 属于系统管控服务
+应在内网使用 不应该暴漏到外网 也无需配置 `https` + +更改 `系统 -> 菜单管理 -> 监控中心 与 任务调度中心` 菜单配置
+将其改为 `外链访问` 访问路径为 **注意: 如果是外网使用 url需配置为 http://外网ip:端口** + +![输入图片说明](https://foruda.gitee.com/images/1678981287686638349/3734f085_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678981292545287978/f2471f97_1766278.png "屏幕截图") + +`nginx` 配置 `独立的端口` 进行反向代理即可访问(代理编写方式参考后端反向代理) + +### Minio https 改造 + +下方链接包含 minio+nginx 与 minio本身配置https 两种方案
+[终极版minio配置https教程](https://blog.csdn.net/Michelle_Zhong/article/details/126484358) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/identify_fail.md b/ruoyi-admin/src/main/resources/static/questions/identify_fail.md new file mode 100644 index 00000000..4e258067 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/identify_fail.md @@ -0,0 +1,10 @@ +# 放行接口提示认证失败 +- - - +## 可能的原因 +接口放行后不需要token即可访问
+但是没有token也就无法获取用户信息与鉴权 + +## 解决方案 +删除接口上的鉴权注解
+删除接口内获取用户信息功能
+删除数据库实体类 自动注入 `createBy` `updateBy` 因为会获取用户数据 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/import_excel.md b/ruoyi-admin/src/main/resources/static/questions/import_excel.md new file mode 100644 index 00000000..431863e8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/import_excel.md @@ -0,0 +1,4 @@ +# 关于导入excel实体类为空 +- - - +* 禁止在导入实体使用 `lombok` 链式调用注解 `@Accessors(chain = true)` +* 会导致找不到 `set` 方法无法注入内容 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/jar_run_fail.md b/ruoyi-admin/src/main/resources/static/questions/jar_run_fail.md new file mode 100644 index 00000000..cef9bcd2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/jar_run_fail.md @@ -0,0 +1,12 @@ +# 打包jar运行报错问题 +- - - + +**常见于 windows 平台以命令方式启动** + +windows 平台默认编码为 GBK 所以读取到所有的配置都是乱码 + +## 解决方案 + +需要在命令增加 `-Dfile.encoding=utf-8` 指定文件编码 + +例如: `java -Dfile.encoding=utf-8 -jar ruoyi-xxx.jar` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/jce_cannot.md b/ruoyi-admin/src/main/resources/static/questions/jce_cannot.md new file mode 100644 index 00000000..a1baf9dd --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/jce_cannot.md @@ -0,0 +1,3 @@ +# 问题说明 由于 OracleJDK 强校验加密证书导致 + +解决方案 禁止使用 oraclejdk 更换为其他例如 openjdk \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/kinfe4j.md b/ruoyi-admin/src/main/resources/static/questions/kinfe4j.md new file mode 100644 index 00000000..5051a782 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/kinfe4j.md @@ -0,0 +1,66 @@ +# 对接前声明 + +经常有小伙伴希望可以对接 knife4j + +那么这里将介绍如何使用 框架生成的 openapi 对接 knife4j + +# 如何对接 + +**重点声明: 本框架生成标准openapi结构 如对接后遇到不好用等问题 皆与本框架无关** + +knife4j 本身提供了独立的文档中间件 可以零成本的介入 openapi + +文档地址: https://doc.xiaominfo.com/docs/middleware-sources + +**注意: 此组件应单独搞一个boot项目 不要往框架里做任何代码上的更改** + +使用文档提供的 Cloud 模式 对接咱们框架的 openapi 地址即可完成对接 + +![输入图片说明](https://foruda.gitee.com/images/1685953873117929554/22dce56e_1766278.png "屏幕截图") + +vue版本对接配置如下: + +```yml +knife4j: + enable-aggregation: true + cloud: + enable: true + routes: + - name: 演示模块 + uri: localhost:8080 + location: /v3/api-docs/1.演示模块 + - name: 系统模块 + uri: localhost:8080 + location: /v3/api-docs/2.系统模块 + - name: 代码生成模块 + uri: localhost:8080 + location: /v3/api-docs/3.代码生成模块 +``` + +cloud版本对接配置如下: + +```yml +knife4j: + enable-aggregation: true + cloud: + enable: true + routes: + - name: 演示模块 + uri: localhost:8080 + location: /demo/v3/api-docs + - name: 认证服务 + uri: localhost:8080 + location: /auth/v3/api-docs + - name: 资源服务 + uri: localhost:8080 + location: /resource/v3/api-docs + - name: 系统服务 + uri: localhost:8080 + location: /system/v3/api-docs + - name: 监控服务 + uri: localhost:8080 + location: /monitor/v3/api-docs + - name: 代码生成服务 + uri: localhost:8080 + location: /gen/v3/api-docs +``` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/login_step.md b/ruoyi-admin/src/main/resources/static/questions/login_step.md new file mode 100644 index 00000000..c1d4fcc9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/login_step.md @@ -0,0 +1,69 @@ +# 关于登录调试步骤 + +## 1:关闭 api 接口加密 + +1. 修改后端配置文件 `application.yml` + +![输入图片说明](https://foruda.gitee.com/images/1717037518256330645/c5a9f0fc_4959041.png "屏幕截图") + +2. 修改前端配置文件 `.env.development` | `.env.production` + +![输入图片说明](https://foruda.gitee.com/images/1717037555118359683/0e73a369_4959041.png "屏幕截图") + +## 2:登录参数 + +![输入图片说明](https://foruda.gitee.com/images/1717038201634120005/e02882d3_4959041.png "屏幕截图") + +|参数名|说明| +|---|---| +|tenantId| 租户id | +|username| 用户名 | +|password| 密码 | +|rememberMe| 记住密码 | +|uuid| - | +|code| 验证码结果 | +|clientId| 客户端id(表 sys_client) | +|grantType| 授权类型(表 sys_client) | + +## 3:使用接口文档调试 + +### 3.1:使用接口文档请求 + +1. 配置接口文档([参考文档](/ruoyi-vue-plus/framework/association/doc)) +2. 请求接口 `http://localhost:8080/auth/login` + +![输入图片说明](https://foruda.gitee.com/images/1717039200581756307/97efbc9c_4959041.png "屏幕截图") + +### 3.2:使用 idea 请求 + +![输入图片说明](https://foruda.gitee.com/images/1717039459944753490/040d2b9d_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1717039534863944601/df91df67_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1717039598067298052/cc9fe61b_4959041.png "屏幕截图") + +### 3.3:获取验证码以及 uuid + +!> 验证码以及 uuid 获取方式: Redis | 控制台 + +方式一、Redis: + +![输入图片说明](https://foruda.gitee.com/images/1717040260329977942/42f7ed62_4959041.png "屏幕截图") + +> **如果没有验证码相关 key,说明已经过期被清理了,去前端页面刷新一下即可。** + +方式二、控制台: + +![输入图片说明](https://foruda.gitee.com/images/1717040428227070908/1ef7562a_4959041.png "屏幕截图") + +### 3.4:关闭验证码 + +如果嫌验证码太麻烦,可以关闭,修改后端配置文件 `application.yml` + +![输入图片说明](https://foruda.gitee.com/images/1717040533266608114/054fd984_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1717040745251872562/374267e8_4959041.png "屏幕截图") + +请求参数: + +![输入图片说明](https://foruda.gitee.com/images/1717040762860943102/81c9b44a_4959041.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/lombok.md b/ruoyi-admin/src/main/resources/static/questions/lombok.md new file mode 100644 index 00000000..47125153 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/lombok.md @@ -0,0 +1,4 @@ +# 关于lombok注解爆红 +- - - +* 已知 lombok 插件与 idea中文插件 存在兼容性问题 +* 移除中文插件或手动关闭idea检查 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/nacos_naming_instance_metadata.md b/ruoyi-admin/src/main/resources/static/questions/nacos_naming_instance_metadata.md new file mode 100644 index 00000000..ae35d937 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/nacos_naming_instance_metadata.md @@ -0,0 +1,35 @@ +# nacos 报错 The Raft Group [naming_instance_metadata] +- - - +## Nacos 服务下线报错问题 + +问题描述: + +Nacos 服务管理 > 服务列表 > 详情 > 下线 报错 + + + +报错详情: + +``` +caused: errCode: 500, errMsg: do metadata operation failed ;caused: com.alibaba.nacos.consistency.exception.ConsistencyException: The Raft Group [naming_instance_metadata] did not find the Leader node;caused: The Raft Group [naming_instance_metadata] did not find the Leader node; +``` + + + +解决方案: + +**删除 Nacos 根目录下 data 文件夹下的 protocol 文件夹** + +(推荐使用全局搜索软件查询,windows 环境根目录一般在 C:\Users\用户名\nacos) + + + +问题原因: + +> Nacos 采用 raft 算法来计算 Leader,并且会记录上次启动的集群地址,所以当我们自己的服务器 IP 改变时(网络环境不稳定,如WIFI, IP 地址也经常变化),导致 raft 记录的集群地址失效,导致选 Leader 出现问题。 + + + +参考目录: + +[解决疑难问题之服务下线报:The Raft Group naming_instance_metadata\] did not find the Leader node; - 嘉美祥瑞 - 博客园 (cnblogs.com)](https://www.cnblogs.com/whl-jx911/p/16736625.html) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/nacos_read_fail.md b/ruoyi-admin/src/main/resources/static/questions/nacos_read_fail.md new file mode 100644 index 00000000..f6cc36d9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/nacos_read_fail.md @@ -0,0 +1,15 @@ +# 无法读取nacos配置 +- - - +### 检查 `group` 与 `namespace` 是否一致 + +如果未使用框架自带 `ry-config.sql` 文件进行配置 会导致 `namespace` 不一致 无法查询配置 + +### 检查 `8848` `9848` `9849` 端口是否开启可用 + +### 检查配置文件名是否一致 例如: "xxx" 与 "xxx.yml" 的区别 + +### 检查是否手动改过 `nacos` 数据库数据 + +`nacos` 数据表层层关联 不要自作聪明手动改数据库 + +已经改过的 需要重新导入 `ry-config.sql` 之后在页面进行改数据操作 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/only_one_subscriber.md b/ruoyi-admin/src/main/resources/static/questions/only_one_subscriber.md new file mode 100644 index 00000000..f8d690e1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/only_one_subscriber.md @@ -0,0 +1,11 @@ +# Only one connection receive subscriber allowed +- - - +## 问题原因 +**经多人反馈 共同点为全都是做`小程序开发`使用的`uniapp`发送的网络请求而出现这种问题** + +`uniapp` 错误设置 `Content-Type` 将所有请求类型全都设置成了 `json` 导致不该读body的请求也读取了body 最终导致报错 + +## 解决方案 + +方案1: 升级 1.4.0 已经对这种不合规发的请求做了兼容处理(被迫)
+方案2: `uniapp` 内的请求设置正确的 `Content-Type` diff --git a/ruoyi-admin/src/main/resources/static/questions/parse_exception.md b/ruoyi-admin/src/main/resources/static/questions/parse_exception.md new file mode 100644 index 00000000..67a7a114 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/parse_exception.md @@ -0,0 +1,40 @@ +# ParseException SQL解析异常 +- - - +## 异常内容 + +`net.sf.jsqlparser.parser.ParseException: Encountered unexpected token:` + +![输入图片说明](https://foruda.gitee.com/images/1678981169309778625/a17ff852_1766278.png "屏幕截图") + +此异常为 SQL 解析异常, 应检查 SQL 语句内是否包含 SQL 关键字 + +异常通常都会提供坐标 + +![输入图片说明](https://foruda.gitee.com/images/1678981173813116217/a6f9ee32_1766278.png "屏幕截图") + +检查报错 SQL 相关坐标位置 + +![输入图片说明](https://foruda.gitee.com/images/1678981179153564043/bf4912b4_1766278.png "屏幕截图") + +## 异常由来 +由 Mybatis-Plus 拦截器进行 SQL 解析导致
+常见拦截器导致问题 `TenantLineInnerInterceptor` `DataPermissionInterceptor` + +## 解决方案 + +> 将关键字增加标识符区别开 + +1.实体类字段处理(以下仅限于mysql 其他数据库方法各不相同) + +![输入图片说明](https://foruda.gitee.com/images/1678981183515542682/fccd85ad_1766278.png "屏幕截图") + +2.自定义 SQL 或 XML 处理 + +![输入图片说明](https://foruda.gitee.com/images/1678981187926917963/38437edb_1766278.png "屏幕截图") + +3.Mapper排除 +> 查看具体使用了哪些拦截器导致问题 使用忽略注解依次进行排除即可 + +![输入图片说明](https://foruda.gitee.com/images/1678981192902044584/fb1c41eb_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/questions/permission_denied.md b/ruoyi-admin/src/main/resources/static/questions/permission_denied.md new file mode 100644 index 00000000..76d955db --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/permission_denied.md @@ -0,0 +1,15 @@ +# Redis 报错 Permission denied +- - - +### 此报错为无权限 + +需确保 redis 数据存储文件夹具有写权限 + +```shell +chmod 777 /docker/redis/data +``` + +没有写权限无法对数据进行存储 + +### 关于RDB报错 `/etc` 无权限问题 + +增加redis密码校验 无密码导致配置不安全 diff --git a/ruoyi-admin/src/main/resources/static/questions/read_metadata.md b/ruoyi-admin/src/main/resources/static/questions/read_metadata.md new file mode 100644 index 00000000..b91165fd --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/read_metadata.md @@ -0,0 +1,11 @@ +# unable to read meta-data for class xxx +- - - +## 问题原因 + +此问题由改包名导致框架内组件 spring 的 spi 配置文件包名被改乱套 + +## 解决方案 + +更正组件包下的 spring spi 配置文件内的类包名 + +![输入图片说明](https://foruda.gitee.com/images/1668608724503582409/50a77b4b_1766278.jpeg "test.jpg") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/sentinel_404.md b/ruoyi-admin/src/main/resources/static/questions/sentinel_404.md new file mode 100644 index 00000000..7e51fb58 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/sentinel_404.md @@ -0,0 +1,8 @@ +# Sentinel页面404问题 +- - - +## 原因 +检查 `webapp` 目录是否为资源目录 低版本 `idea` 不会自动解析 +## 解决方案 +手动设置 `webapp` 为资源目录即可
+ +![输入图片说明](https://foruda.gitee.com/images/1678981354612151228/52f2a886_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/questions/st_not_support.md b/ruoyi-admin/src/main/resources/static/questions/st_not_support.md new file mode 100644 index 00000000..a3012805 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/st_not_support.md @@ -0,0 +1,11 @@ +# 不支持ST请求 +- - - +## 问题原因 +**经多人反馈 共同点为全都是做`小程序开发`使用的`uniapp`发送的网络请求而出现这种问题** + +`uniapp` 错误设置 `Content-Type` 将所有请求类型全都设置成了 `json` 导致不该读body的请求也读取了body 最终导致报错 + +## 解决方案 + +方案1: 升级 1.4.0 已经对这种不合规发的请求做了兼容处理(被迫)
+方案2: `uniapp` 内的请求设置正确的 `Content-Type` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/swagger.md b/ruoyi-admin/src/main/resources/static/questions/swagger.md new file mode 100644 index 00000000..8b0a6e01 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/swagger.md @@ -0,0 +1,3 @@ +# 框架内没有任何swagger + +想使用接口文档功能 请查看框架接口文档说明 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/synchronous_update.md b/ruoyi-admin/src/main/resources/static/questions/synchronous_update.md new file mode 100644 index 00000000..70803f58 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/synchronous_update.md @@ -0,0 +1,3 @@ +# 如何同步项目更新 +- - - +参考文章: [关于如何同步更新开源项目](https://blog.csdn.net/qq_31360283/article/details/118345795) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/use_druid.md b/ruoyi-admin/src/main/resources/static/questions/use_druid.md new file mode 100644 index 00000000..77f3f605 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/use_druid.md @@ -0,0 +1,20 @@ +# 如何使用druid连接池 +- - - +## 为何移除druid + +性能低下 bug频发 内含fastjson问题众多 监控不支持集群(鸡肋) 不支持一些高版本数据库 社区活跃度冰点 + +### 性能对比图 +![输入图片说明](https://foruda.gitee.com/images/1667888745256002635/1bbd3481_1766278.png "屏幕截图") +### 包大小对比图 +![输入图片说明](https://foruda.gitee.com/images/1667888760611300040/87af8d82_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1667888766932068690/7b379298_1766278.png "屏幕截图") + +## 为何使用hikari(中文: 光) + +spring默认自带 代码量少结构简单 稳定可靠 性能突出(自行百度一堆测评) + +## 参考提交记录反向操作即可 + +https://gitee.com/dromara/RuoYi-Vue-Plus/commit/1f42bd3d22c104aaa2d780c20a555b5e467858bf
+https://gitee.com/dromara/RuoYi-Vue-Plus/commit/a63abbf268e4c0a60344f63b5cba828a1347e178 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/questions/use_tomcat.md b/ruoyi-admin/src/main/resources/static/questions/use_tomcat.md new file mode 100644 index 00000000..113d239f --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/questions/use_tomcat.md @@ -0,0 +1,9 @@ +# 关于如何使用Tomcat +- - - +### 查看ruoyi-framework模块的pom.xml文件,根据注释更改依赖 + +![输入图片说明](https://foruda.gitee.com/images/1678981109106652929/0803004d_1766278.png "屏幕截图") + +### 查看ruoyi-admin模块中的application.yml文件,根据注释更改配置 + +![输入图片说明](https://foruda.gitee.com/images/1678981112652965294/dda8df86_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/_sidebar.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/_sidebar.md new file mode 100644 index 00000000..4580b86c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/_sidebar.md @@ -0,0 +1,70 @@ + +- **特别赞助** +- [![输入图片说明](https://foruda.gitee.com/images/1704162419429172656/d0521e59_1766278.png "2024-01-02=>2028-01-02")](http://ccflow.org/?frm=ryPlus) +- [![输入图片说明](https://foruda.gitee.com/images/1705569347386939952/3f187980_1766278.jpeg "2024-01-18=>2025-01-18")](http://www.shuduokeji.com) +- [![输入图片说明](https://foruda.gitee.com/images/1711681233267310022/2ffbcff2_1766278.png "2024-03-29=>2025-03-29")](https://www.jnpfsoft.com/index.html?from=plus-doc) + + +* **简介** + * [项目简介](/ruoyi-cloud-plus/home.md) + * [更新日志](/ruoyi-cloud-plus/changlog.md) +* **快速开始** + * [项目初始化](/ruoyi-cloud-plus/quickstart/init.md) + * [1.X项目初始化](/ruoyi-cloud-plus/quickstart/1.Xinit.md) + * [工作流初始化](/ruoyi-cloud-plus/quickstart/worker_init.md) + * [idea环境配置](/ruoyi-cloud-plus/quickstart/idea_environment.md) + * [应用部署](/ruoyi-cloud-plus/quickstart/deploy.md) + * [扩展项目](/ruoyi-cloud-plus/quickstart/extend_project.md) + * [搭建SnailJob调度中心](/ruoyi-cloud-plus/quickstart/snail_job_init.md) + * [(废弃)搭建PowerJob调度中心](/ruoyi-cloud-plus/quickstart/power_job_init.md) +* **框架功能** + * [项目结构](/ruoyi-cloud-plus/framework/tree.md) + * [软件架构图](/ruoyi-cloud-plus/framework/architecture_diagram.md) + * 框架相关 + * [创建新服务](/ruoyi-cloud-plus/framework/association/new_module.md) + * [修改包名](/ruoyi-cloud-plus/framework/association/update_package_name.md) + * [接口文档](/ruoyi-cloud-plus/framework/association/doc.md) + * [修改应用路径](/ruoyi-cloud-plus/framework/association/update_url.md) + * [国际化](/ruoyi-cloud-plus/framework/association/i18n.md) + * [多团队开发](/ruoyi-cloud-plus/framework/association/collaboration.md) + * [内网鉴权](/ruoyi-cloud-plus/framework/association/inner_authentication.md) + * 基础功能 + * [系统用户相关](/ruoyi-cloud-plus/framework/basic/user.md) + * [权限控制](/ruoyi-cloud-plus/framework/basic/permissions_control.md) + * [导出功能](/ruoyi-cloud-plus/framework/basic/export.md) + * [导入功能](/ruoyi-cloud-plus/framework/basic/import.md) + * [参数校验](/ruoyi-cloud-plus/framework/basic/param_check.md) + * [代码生成](/ruoyi-cloud-plus/framework/basic/code_generate.md) + * [分页功能](/ruoyi-cloud-plus/framework/basic/page.md) + * [OSS功能](/ruoyi-cloud-plus/framework/basic/oss.md) + * [数据权限](/ruoyi-cloud-plus/framework/basic/permissions.md) + * [网关路由与放行](/ruoyi-cloud-plus/framework/basic/router_release.md) + * [多租户功能](/ruoyi-cloud-plus/framework/basic/tenant.md) + * [第三方授权功能](/ruoyi-cloud-plus/framework/basic/social.md) + * [客户端管理功能](/ruoyi-cloud-plus/framework/basic/client.md) + * 扩展功能 + * [多数据源](/ruoyi-cloud-plus/framework/extend/dynamic_datasource.md) + * [短信模块](/ruoyi-cloud-plus/framework/extend/sms.md) + * [邮件功能](/ruoyi-cloud-plus/framework/extend/mail.md) + * [防重幂等](/ruoyi-cloud-plus/framework/extend/idempotent.md) + * [数据脱敏](/ruoyi-cloud-plus/framework/extend/sensitive.md) + * [API加解密](/ruoyi-cloud-plus/framework/extend/api_encrypt.md) + * [数据加解密](/ruoyi-cloud-plus/framework/extend/encrypt.md) + * [翻译功能](/ruoyi-cloud-plus/framework/extend/translation.md) + * [WebSocket功能](/ruoyi-cloud-plus/framework/extend/websocket.md) + * 功能说明 + * [事务相关](/ruoyi-cloud-plus/framework/explain/transaction.md) + * [单元测试](/ruoyi-cloud-plus/framework/explain/test.md) + * [主键使用说明](/ruoyi-cloud-plus/framework/explain/key.md) + * [关于多表查询](/ruoyi-cloud-plus/framework/explain/about_join.md) +* **扩展功能** + * [ELK搭建](/ruoyi-cloud-plus/extend-function/elk.md) + * [ES搜索引擎](/ruoyi-cloud-plus/extend-function/es.md) + * [RabbitMQ搭建](/ruoyi-cloud-plus/extend-function/rabbitmq.md) + * [RocketMQ搭建](/ruoyi-cloud-plus/extend-function/rocketmq.md) + * [Kafka搭建](/ruoyi-cloud-plus/extend-function/kafka.md) + * [Nacos集群搭建](/ruoyi-cloud-plus/extend-function/nacos.md) + * [SkyWalking搭建与集成](/ruoyi-cloud-plus/extend-function/skywalking.md) + * [Prometheus+Grafana搭建](/ruoyi-cloud-plus/extend-function/prometheus_grafana.md) + * [Sharding-Proxy搭建分库分表](/ruoyi-cloud-plus/extend-function/shardingproxy.md) + * [对接MaxKey单点登录](/ruoyi-cloud-plus/extend-function/maxkey.md) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/changlog.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/changlog.md new file mode 100644 index 00000000..368844cb --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/changlog.md @@ -0,0 +1,1385 @@ +# 更新日志 +- - - + +## v2.2.1 - 2024-08-26 + +### 重大改动 + +* 增加 ruoyi-common-sse 模块 支持SSE推送 比ws更轻量更稳定的推送 +* 增加 springboot snailjob 等 actuator 账号密码认证 杜绝内外网信息泄漏问题 +* 增加 重构代码生成器 集成anyline开源框架 支持400+种数据库适配 + +### 依赖升级 + +* update springboot 3.2.6 => 3.2.9 +* update snailjob 1.0.1 => 1.1.2 +* update mapstruct-plus 1.4.3 => 1.4.4 +* update hutool 5.8.27 => 5.8.31 解决hutool不兼容jakarta问题 +* update anyline 8.7.2-20240808 +* update sms4j 3.2.1 => 3.3.2 +* update redisson 3.31.0 => 3.34.1 +* update mapstruct-plus 1.3.6 => 1.4.3 +* update lombok 1.18.32 => 1.18.34 +* update easyexcel 3.3.4 => 4.0.2 +* update springdoc 2.5.0 => 2.6.0 +* update flowable 7.0.0 => 7.0.1 + +### cloud内容更新 + +* update springcloud 2023.0.2 => 2023.0.3 +* update springcloud-alibaba 2023.0.1.0 => 2023.0.1.2 +* update redis 6.2.7 => 6.2.12 解决订阅key报错问题 +* update 优化 seata dockerfile 增加环境变量 +* update 优化 增加日志处理器顺序说明 +* update 优化 使用 seata-server 官方依赖简化seata集成方式 +* update 优化 屏蔽 sentinel 心跳日志 +* update 优化 dubbo元数据注册redis支持timeout(注意时间必须使用数字) +* update 优化 调整sentinel日志级别 屏蔽心跳日志 +* update 优化 sky-agent 默认开启即使连不上服务端也跟踪配置 (有些人就爱这么用) +* update 优化 kafka 自动创建 topic 部分人副本数不够报错问题 +* add 增加 nacos sentinel snailjob 健康检查 actuator 账号密码认证 +* fix 修复 dubbo redis元数据中心 获取监听器null问题 +* fix 修复 nacos sentinel seata 不适配新版undertow问题 先换回tomcat +* fix 修复 依赖漏洞 限制部分依赖版本 +* fix 修复 由于alibaba sentinel 初始化机制变更导致的无法连接问题 +* fix 修复 dubbo 日志输出异常判断错误 +* remove 删除 kafka-streams 所有人都不会用也不学怎么用 删除了事 + +### 功能更新 + +* update 优化 去除日志部署环境判断 通过日志级别控制 +* update 优化 忽略租户与忽略数据权限支持嵌套使用(感谢 amadeus5201) +* update 优化 租户相关controller 增加租户开关配置控制是否注册 +* update 优化 移除 alibaba ttl 与线程池搭配有问题(可传递但无法清除与更新) +* update 优化 个人中心编辑 忽略数据权限 +* update 优化 兼容部分用户不想给用户分配角色与部门的场景 +* update 优化 租户套餐重名校验 +* update 优化 部门下存在岗位不允许删除 +* update 优化 角色编辑状态未校验问题 +* update 优化 用户脱敏增加编辑权限标识符 +* update 优化 代码生成器 自动适配oss翻译 +* update 优化 临时升级 undertow 版本 解决虚拟线程溢出问题 +* update 优化 支持通过配置文件关闭工作流 +* update 优化 增加mybatis-plus填充器兜底策略 +* update 优化 TenantSpringCacheManager 处理逻辑 +* update 优化 角色权限判断 +* update 优化 增加删除标志位常量优化查询代码 +* update 优化 监控使用独立web依赖 +* update 优化 更多脱敏策略(感谢 hemengji) +* update 优化 设置nginx sse相关代理参数 +* update 优化 调整默认推送使用SSE +* update 优化 Monitor监控服务通知分类打印(感谢 AprilWind) +* update 优化 限流注解 又写key又不是表达式的情况 +* update 优化 WorkflowUtils查询用户信息发送消息未查询邮件和手机号(感谢 yanzy) +* update 优化 注释掉其他数据库 jdbc 依赖 由用户手动添加 +* update 优化 oracle snailjob 兼容低版本oracle索引名称长度限制 +* update 优化 数据权限支持通过菜单标识符获取数据所有权 +* update 优化 数据权限支持自定义连接符 +* update 优化 TestDemo 删除前校验数据权限 +* update 优化 更换docker镜像底层系统 避免无字体情况 + +### 问题修复 + +* fix 修复 三方登录构建去除无用代码 +* fix 修复 多线程对同一个session发送ws消息报错问题 +* fix 修复 依赖漏洞 限制部分依赖版本 +* fix 修复 excel 基于其他字段 合并错误问题 +* fix 修复 一级缓存key未区分租户问题 +* fix 修复 id字符串格式转换错误问题 +* fix 修复 登出无法正确删除对应的租户数据问题 +* fix 修复 登录错误锁定不区分租户问题 +* fix 修复 转换模型缺少分类字段 +* fix 修复 权限标识符处理未设置成功状态问题 +* fix 修复 无法导入 bpmn 类型文件问题 + +### 前端改动 + +* update element-plus 2.7.5 => 2.7.8 +* update vue 3.4.25 => 3.4.34 +* update vite 5.2.10 => 5.2.12 +* add 增加 使用 vueuse 编写 sse 推送功能 +* update 优化 使用匹配模式简化预编译配置 +* update 优化 时间搜索组件统一 +* update 优化 oss 配置按钮 使用ossConfig权限标识符与oss权限分离 +* update 优化 类型报错问题 +* update 优化 切换租户后刷新首页 +* update 优化 实现表格行选中切换 +* update 优化 使用 vueuse 重构 websocket 实现 +* update 优化 代码生成器编辑页禁用缓存 防止同步后页面不更新问题 +* update 优化 调整默认推送使用SSE +* fix 修复 租户套餐导出路径错误问题 +* fix 修复 登出后重新登录 sse推送报错问题 + + +## v2.2.0 - 2024-07-09 + +### 重大更新 + +* [重大更新] 使用 caffeine 重构 PlusSaTokenDao 层实现 减少将近90%的redis查询提高性能 +* [重大更新] 新增 PlusCacheWrapper 装饰器 为 SpringCache 增加本地缓存减少redis查询提高性能 +* [重大更新] 升级 awsS3 到2.X版本 支持异步与自动分片上传下载(感谢 AprilWind) +* [重大更新] 新增 flowable 工作流功能(感谢 May) +* [重大更新] 新增 snailjob 调度中心 移除 powerjob (投诉的人太多) (感谢 dhb52) +* [重大更新] 重构 将spring-cloud-stream改为普通的mq依赖用法(感谢 Xbhog) +* [重大更新] 新增 ruoyi-common-bus 消息总线组件 基于MQ跨服务投递事件消息 + +### 依赖升级 + +* update springboot 3.1.7 => 3.2.6 支持虚拟线程 +* update springboot-admin 3.1.8 => 3.2.3 +* update springdoc 2.2.0 => 2.5.0 +* update redisson 3.24.3 => 3.29.0 支持虚拟线程 +* update hutool 5.8.22 => 5.8.26 +* update dynamic-ds 4.2.0 => 4.3.0 +* update mybatis-plus 3.5.4 => 3.5.7 修复与boot代码冲突问题 +* update lock4j 2.2.5 => 2.2.7 消除启动警告 +* update sms4j 2.2.0 => 3.2.1 支持自定义配置key 可用于多厂商多租户等 +* update mapstruct-plus 1.3.5 => 1.3.6 +* update easyexcel 3.3.3 => 3.3.4 +* update lombok 1.18.30 => 1.18.32 +* update satoken 1.37.0 -> 1.38.0 +* update aws-oss 1.12.600 => 2.25.15 + +### 功能更新 + +* update 优化 StreamUtils 抽取 findFirst findAny 方法 +* update 优化 更新使用 Spring 官方推荐 JDK +* update 优化 webscoket 配置与异常拦截 +* update 优化 isTenantAdmin 空校验 +* update 优化 修改路由name命名规则(感谢 玲娜贝er) +* update 优化 大数据量下join卡顿问题 使用子查询提高性能 +* update 优化 用户ID查询角色列表(感谢 AprilWind) +* update 优化 获取用户账户(感谢 AprilWind) +* update 优化 租户列表接口 避免登录之后列表被域名过滤 +* update 优化 三方登录不同域名获取不到租户id问题 +* update 优化 获取aop代理的方式 减少与其他使用aop的功能冲突的概率 +* update 优化 临时解决 spring 启动报 warn 问题 +* update 优化 移除表单构建菜单(没有可用组件 用处不大以后再考虑) +* update 优化 修改用户信息接口(感谢 AprilWind) +* update 优化 切换动态租户 默认线程内切换(如需全局 手动传参) +* update 优化 适配最新前端代码生成模板 +* update 优化 代码生成 el-radio 标签过期属性 +* update 优化 文件下载(使用对流传递 降低内存使用量)(感谢 秋辞未寒) +* update 优化 去除gc日志参数(有需要自己加) +* update 优化 拆分异常处理器 +* update 优化 常规web异常状态码 +* update 优化 设置静态资源路径防止所有请求都可以访问静态资源 +* update 优化 代码生成表导入 排除工作流相关表 +* update 优化 redis 对Long值的存储类型不同问题 +* update 优化 去除加密请求类型限制 +* update 优化 mp多租户插件注入逻辑 +* update 优化 移除删表语句 用户自行处理 +* update 优化 RedisUtils 支持忽略租户 +* update 更新 ip地址 xdb文件 +* update 优化 新增修改菜单权限字符校验 +* update 优化 验证码背景色改为浅灰色 +* update 优化 更新 mybatis 多包扫描配置 +* update 优化 RateLimiter 注解使用体验(感谢 ly-chn) +* update 优化 GET 方法响应体支持加密 +* update 优化 excel 单元格合并可以基于注解选择需要依赖哪些字段(感谢 司猫子) +* update 优化 OssFactory 获取实例锁性能(感谢 fanc) +* update 优化 登录消息 支持集群发送 +* update 优化 数据权限 使用预扫描mapper注解提升代码性能 +* update 优化 数据加密 使用预扫描实体类提升代码性能(感谢 老马) +* update 优化 Async 针对虚拟线程配置 与其他注意事项注释 +* update 优化 框架整体sql提高查询性能 +* update 优化 将p6spy配置文件统一放置到 common-mybatis 插件包内 +* update 优化 使用翻译注解简化用户查询 调整用户查询逻辑 + + +### 新增功能 + +* add 新增 SMS异常处理器(感谢 AprilWind) +* add 新增 在线设备管理(个人中心)(感谢 AprilWind) +* add 新增 岗位编码与部门编码 并将岗位放到部门下(感谢 秋辞未寒) +* add 新增 分布式锁Lock4j异常拦截(感谢 AprilWind) +* add 新增 BaseMapperPlus提供一组可选是否抛出异常的selectVoOne方法(感谢 秋辞未寒) +* add 新增 用户、部门、角色、岗位 下拉选接口与代码实现优化 +* add 新增 JustAuth 整合 TopIam 单点登录(感谢 马铃薯头) +* add 新增 StringUtils.isVirtual 方法 +* add 新增 正则工具类 字符串提取 字符串校验 + +### 问题修复 + +* fix 修复 isLogin 方法抛异常无法正常返回值问题 +* fix 修复 spring路径规则 导致 actuator 被特殊方式访问问题 +* fix 修复token无效时关闭ws(感谢 AprilWind) +* fix 修复 oss未使用租户 拼接租户id null问题 +* fix 修复 用户昵称修改后未清除对应缓存问题 +* fix 修复 文件上传图片预览问题 +* fix 修复 三方账号可以被同一个用户多次绑定问题 +* fix 修复 兼容redis5.0出现的问题 +* fix 修复 字典键值可重复配置问题 +* fix 修复 部分浏览器无法获取加密响应头问题 +* fix 修复 用户未设置部门 登录报错问题 +* fix 修复 全局异常处理器 空指针null问题 +* fix 修复 excel 表达式字典 下拉框导出格式错误 +* fix 修复 InjectionMetaObjectHandler 已存在数据依旧会获取用户信息报异常问题 +* fix 修复 关闭租户功能 三方登录报错问题 +* fix 修复 部门树排序问题 +* fix 修复 CryptoFilter 代码逻辑问题 + +### 前端改动 + +* update 升级 element vite 版本 最低nodejs版本提升到18.18.0 +* update 优化 更改客户端状态接口 使用clientId传参 +* update 优化 ws开关改为常开(vite5修复了崩溃bug) +* update 优化 移除cjs +* update 优化 对Volar支持 +* update 优化 富文本组件,修复两个组件上传图片位置错乱问题 +* update 优化 request请求类判断请求头方式 +* update 优化 密码校验策略增加非法字符限制 +* update 优化 支持全局开启或关闭接口加密功能 +* update 优化 暗黑模式,增加vxe的暗黑模式 +* update 优化 首页打开topNav不展开菜单问题 +* update 优化 el-select 与 el-input 全局样式 +* update 优化 跟密码相关的默认前端关闭防重功能 +* add 新增 社交登录整合 TopIam +* add 新增 图片上传组件增加压缩功能支持,可自行开关 +* add 新增 vxe-table依赖支持 +* add 新增 全局用户选择组件 +* add 新增 工作流相关页面与组件 +* add 新增 使用bpmnjs流程预览 +* add 新增 在线登录设备管理(感谢 AprilWind) +* add 新增 用户选择角色时 可搜索功能(感谢 追梦稻草人Li) +* fix 修复 登录失效,重新登录丢失参数问题(感谢 爱宇阳) +* fix 修复 websocket 非index页面刷新无法重连问题 +* fix 修复 全局属性找不到的问题(感谢 ahaos) +* fix 修复 vue 类型识别问题 +* fix 修复 富文本编辑器 单页面多实例图片混乱问题 +* fix 修复 i18n无感刷新问题 +* fix 修复 文件预览大写后缀不展示的问题(感谢 北桥) +* fix 修复 面板因为min width原因收缩不全 +* fix 修复 移动端下 无法展开菜单问题 +* fix 修复 菜单搜索下方出现白色区域 +* fix 修复 el-tag标签类型不一致问题 +* fix 修复 角色必填*号 + +### 微服务修改 + +* update springcloud 2022.0.4 => 2023.0.2 +* update springcloud-alibaba 2022.0.0.0 => 2023.0.1.0 +* update dubbo 3.2.7 => 3.2.14 +* update easy-es 2.0.0-beta4 => 2.0.0 正式版 +* update nacos 2.2.1 => 2.3.2 默认开启nacos服务端授权认证 (感谢 OldDriver9527) +* update rocketmq 4.9.4 => 5.2.0 docker镜像升级 +* update kafka 3.2.0 => 3.6.2 docker镜像升级 +* update rabbitmq 3.10.6 => 3.13.3 docker镜像升级 +* update sentinel 1.8.6 => 1.8.8 +* update skywalking 9.3.0 => 9.7.0 +* update skywalking-agent 8.16.0 => 9.2.0 +* update 优化 dubbo 使用 redis 作为元数据中心管理 支持过期时间 避免过期数据堆积 解放nacos存储空间 +* update 优化 调整配置文件语法 +* update 优化 使用spring工具自定义dubbo ip获取方法(针对多网卡ip获取不正确问题) +* update 优化 common-dubbo 删除无用依赖 +* update 优化 去除重复的扫描器 @EnableDubbo 会自行扫描包 +* update 优化 加密组件 mp依赖改为可选 +* update 优化 mybatis依赖设置为可选依赖 避免出现不应该注入的情况 +* fix 修复 sentinel-dashboard的pom引入logaback冲突问题 +* fix 修复 nacos 不兼容 logback 1.4 新版本问题 +* fix 修复 开启数据库加密 auth服务报错问题 +* fix 修复 gateway sentinel 限流报错问题(临时方案) https://github.com/alibaba/Sentinel/issues/3298 + + +## v2.1.2 - 2023-12-22 + +### 依赖升级 + +* update springboot 3.1.5 => 3.1.7 +* update springboot 2.7.17 => 2.7.18(扩展服务升级到boot2最终版本) +* update mybatis-boot 3.0.2 => 3.0.3 优化依赖传递 +* update powerjob 4.3.3 => 4.3.6 +* update easyexcel 3.3.2 => 3.3.3 +* update transmittable-thread-local 2.14.2 => 2.14.4 +* update justauth 1.16.5 => 1.16.6 +* update redisson 3.24.1 => 3.24.3 修复订阅重启连接超时问题 +* update easy-es 1.1.1 => 2.0.0-beta4 + +### 功能更新 + +* update 优化 oss 远程调用 支持降级处理 +* update 优化 丰富RedisUtils对List Set类型的操作 +* update 优化 为 admin 模块 单独增加ratelimiter模块 +* update 优化 验证码接口 增加限流配置 +* update 优化 excel合并注解会根据第一合并列的结果来决定后续的列合并 (感谢 Simple) +* update 优化 SocialUtils 代码 +* update 优化 删除无用异常类 +* update 优化 补全三方登录校验国际化 +* update 优化 sms组件 预留自动配置类 +* update 更新 关于数据库的说明 +* update 优化 sms组件 预留自动配置类 +* update 优化 将 OSS配置 改为全局模式 降低使用难度 保留sql便于用户自行扩展(常规项目用不上配置分多租户) +* update 优化 细化oss配置管理权限控制 +* update 优化 开启 redisson 脚本缓存 减少网络传输 +* update 优化 删除 hikaricp 官方不推荐使用的配置 jdbc4 协议自带校验方法 +* update 优化 减少 PlusSaTokenDao 不必要的查询优化性能 +* update 优化 更新用户异常提示 使用登录账号 +* update 优化 使用登录用户判断是否登录 提高效率 +* update 优化 重构 LoginHelper 将本地存储代码操作封装 +* update 优化 getTenantId 判断是否开启多租户 +* update 优化 Dockerfile 使用shell模式 支持环境变量传入jvm参数 +* update 优化 WebSocketUtils 连接关闭改为警告 +* update 优化 excel多sheet页导出 (感谢 May) +* update 优化 删除无用接口实现 +* update 优化 jvm参数调整 全面启用zgc +* update 优化 使用动态租户重构业务对租户的逻辑 +* update 优化 TenantHelper 动态租户支持函数式方法 +* update 优化 支持多租户绑定相同的三方登录 +* update 优化 更新用户登录信息方法忽略数据权限 +* update 优化 补全三方绑定时间字段 删除无用excel注解 +* update 优化 将登录记录抽取到监听器统一处理 +* update 优化 登录消息推送异常拦截(未启动resource也不耽误用) +* update 优化 租户插件 ignoreTable 方法支持动态租户 + +### 新增功能 + +* add 新增 RedisUtils.setObjectIfExists 如果存在则设置方法 +* add 新增 丰富RedisUtils对List Set类型的操作 +* add 新增 翻译组件 用户昵称翻译实现 +* add 新增 响应加密功能 支持注解强制加密接口数据 (感谢 MichelleChung) +* add 新增 common-ratelimiter 限流模块 用于自定义业务限流 与 sentinel不冲突 + +### 问题修复 + +* fix 修复 stream-mq 测试服务未导入租户模块 导致鉴权不一致问题 +* fix 修复 使用zgc导致seata报错(未知原因 将alibaba组件全还原) +* fix 修复 sentinel 镜像添加了多余接口参数 +* fix 修复 注册接口获取开关未在租户范围内问题 +* fix 修复 seata-server logback版本冲突问题 +* fix 修复 selectDictTypeByType 查询方法错误问题 +* fix 修复 一些不正常类无法加载报错问题 +* fix 修复 powerjob sql脚本针对其他数据库转义符问题 (感谢 branches) +* fix 修复 MybatisSystemException 空指针问题 +* fix 修复 excel合并注解会根据第一合并列的结果来决定后续的列合并 +* fix 修复 session 多账号共用覆盖问题 改为 tokenSession 独立存储 +* fix 修复 token 失效后 登录获取用户null问题 +* fix 修复 powerjob部署方案 高版本nginx不生效问题 +* fix 修复 OssFactory 并发多创建实例问题 +* fix 修复 延迟队列在投递消息未到达时间的时候 服务死机导致重启收不到消息 + +### 前端改动 + +* update 优化 用户头像 img 变量无确定类型问题 +* update 优化 细化oss配置管理权限控制 +* update 优化 明确打包命令 +* update 优化 代码中存在的警告 +* update 优化 前端白名单页面放行逻辑 +* update 优化 页面关于权限标识符说明 +* fix 修复 append-to-body 编写错误 (感谢 Ai3_刘小龙) +* fix 关闭动态路由tab页签时不清理组件缓存 (感谢 NickLuo) +* fix 删除重复环境变量ElUploadInstance (感谢 棉花) +* fix 修复 在线用户 强推按钮点击取消控制台警告问题 +* fix 修复 字典使用 default 样式报警告问题 + +## v1.8.2 - 2023-11-27 + +### 依赖升级 + +* update springboot 2.7.16 => 2.7.18 升级到2.X最终版本(官方停更) +* update mybatis-plus 3.5.3.2 => 3.5.4 +* update satoken 1.36.0 => 1.37.0 +* update hutool 5.8.20 => 5.8.22 +* update aws-java-sdk-s3 1.12.400 => 1.12.540 +* update vue-quill 1.1.0 => 1.2.0 + +### 功能更新 + +* update 优化 页面关于权限标识符说明 +* update 优化 数据权限拦截器优先判断方法是否有效 提高性能减少无用sql解析 +* update 优化 部门数据权限使用默认兜底方案 +* update 优化 补全代码生成 columnList 接口参数注解缺失 +* update 优化 AddressUtils 兼容linux系统本地ip +* update 优化 操作日志 部门信息完善 +* update 优化 数据权限 减少二次校验查询 +* update 修改 获取用户token和后端不一致的问题 (感谢 bestrevens) +* update 优化 vue3 版本用户初始密码从字典查询 +* update 优化 富文本Editor组件检验图片格式 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 全局数据存储用户编号 +* update 优化 菜单管理类型为按钮状态可选 + +### 问题修复 + +* fix 修复 OssFactory 并发多创建实例问题 +* fix 修复 demo页面字段编写错误 +* fix 修复 数据权限优化后 update delete 报null问题 +* fix 修复 五级路由缓存无效问题 +* fix 修复 oss服务无法连接 +* fix 修复 内链iframe没有传递参数问题 +* fix 修复 外链带端口出现的异常 +* fix 修复 普通角色编辑使用内置管理员code越权问题 +* fix 修复 seata XA模式缺失druid工具问题 +* fix 修复 代码生成 是否必填与数据库不匹配问题 +* fix 修复 富文本上传接口地址错误 +* fix 修复 HeaderSearch组件跳转query参数丢失问题 +* fix 修复树结构代码生成新增方法赋值错误 + +## v2.1.1 - 2023-11-14 + +### 依赖升级 + +* update springboot 3.1.3 => 3.1.5 +* update springboot 2.7.14 => 2.7.17(扩展服务) +* update springboot-admin 3.1.5 => 3.1.7 +* update satoken 1.35.0.RC => 1.37.0 +* update mybatis-plus 3.5.3.2 => 3.5.4 适配mp新版本改动 +* update dynamic-ds 4.1.3 => 4.2.0 +* update bouncycastle 1.72 => 1.76 +* update poi 5.2.3 => 5.2.4 +* update redisson 3.23.2 => 3.24.1 +* update hutool 5.8.20 => 5.8.22 +* update lombok 1.18.26 => 1.18.30(适配支持jdk21) +* update vue-quill 1.1.0 => 1.2.0 +* update seata 1.7.0 => 1.7.1 +* update dubbo 3.2.5 => 3.2.7 + +### 功能更新 + +* update 优化 移除不合理的方法 携带附件的邮件建议直接集成插件发送 +* update 优化 携带 clientid 跨域问题 +* update 优化 数据权限拦截器优先判断方法是否有效 提高性能减少无用sql解析 +* update 优化 适配 maxkey 新版本 +* update 优化 @Sensitive脱敏增加角色和权限校验 (感谢 盘古给你一斧) +* update 优化 部门数据权限使用默认兜底方案 +* update 优化 更改默认日志等级为info 避免日志过多(按需开启debug) +* update 优化 登录策略代码优化(感谢 David Wei) +* update 优化 补全代码生成 columnList 接口参数注解缺失 +* update 优化 nginx 配置支持 websocket +* update 优化 notice 新增通知公告发送ws推送 +* update 优化 websocket 模块减少日志输出 增加登录推送 +* update 优化 重构登录策略增加扩展性降低复杂度 +* update 优化 addressUtils 兼容linux系统本地ip +* update 优化 补全操作日志部门数据 +* update 优化 支持数据库操作在非web环境下切换租户 +* update 优化 排除powerjob无用的依赖 减少打包30M体积 +* update 优化 删除 satoken yml 时间配置 此功能已迁移至客户端管理 +* update 优化 redis 集群模式注释说明 +* update 优化 客户端禁用限制 +* update 优化 登录日志, 在线用户展示信息(增加 客户端, 设备类型)(感谢 MichelleChung) +* update 优化 限制框架中的fastjson版本 +* update 优化 数据权限 减少二次校验查询 +* update 优化 将部门id存入token避免过度查询redis +* update 优化 增加租户ID为Null错误日志 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 通过参数键名获取键值接口的返回体(感谢 David Wei) +* update 优化 为 sys_grant_type 字典增加样式 +* update 优化 代码生成 页面输入框样式 +* update 优化 全业务分页查询增加排序规则避免因where条件导致乱序问题 +* update 优化 登录接口租户id被强制校验问题 +* update 优化 加密模块 支持父类统一使用加密注解(感谢 Tyler Ge) +* update 优化 将graalvm镜像更新为openjdk镜像 需要的人自行切换即可 +* update 优化 部分使用者乱设权限导致无法获取用户信息 增加权限提示 +* update 优化 表格列的显示与隐藏小组件(感谢 bestrevens) +* update 优化 增加表单构建不能使用说明 +* update 优化 富文本Editor组件检验图片格式 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 菜单管理类型为按钮状态可选 +* update 优化 用户初始密码从参数配置查询 +* update 优化 通过参数键名获取键值接口的返回体(感谢 David Wei) +* update 优化 字典标签支持数组和多标签(感谢 抓蛙师) + +### 新增功能 + +* add 新增 websocket 群发功能 +* add 新增 前端接入websocket接收消息(感谢 三个三) +* add 增加 rpc消息推送接口与实现 +* add 新增 CacheController Redis 缓存监控接口(感谢 Michelle.Chung) + +### 问题修复 + +* fix 修复 因扩展服务不支持boot3导致无法引入common-web包 日志写出不生效问题 +* fix 修复 seata XA模式缺失druid工具问题 +* fix 修复 oss服务无法连接 导致业务异常问题 查询不应该影响业务 +* fix 修复 租户id为null 无法匹配字符串导致的嵌套key问题 +* fix 修复 部门管理orderNum排序失效问题 +* fix 修复 外链带端口出现的异常 +* fix 修复 普通角色编辑使用内置管理员code越权问题 +* fix 修复 代码生成 是否必填与数据库不匹配问题 +* fix 修复 用户注册接口校验用户名不区分租户问题 +* fix 修复 错误增加组导致的校验不生效问题 +* fix 修复 新增校验主键id问题 +* fix 修复 powerjob 使用 nginx 部署无法访问的问题 +* fix 修复 SysUserMapper 内标签使用错误(不影响使用) +* fix 修复 新增或编辑 SysOssConfig 数据后 推送到 redis 数据不完整 +* fix 修复 树表生成查询变量使用错误 +* fix 修复 个人信息修改密码接口隐藏新旧密码参数明文(感谢 bleachtred) +* fix 修复 删除字段后 * update sql 未更新问题 +* fix 修复 三方登录支付宝source与实际支付宝业务code不匹配问题 +* fix 修复 五级路由缓存无效问题 +* fix 修复 内链iframe没有传递参数问题 +* fix 修复 绑定第三方帐号参数“wechar”更正为“wechat” (感谢 scmiot) +* fix 修复 用户注册缺失 clientid 问题 +* fix 修复 HeaderSearch组件跳转query参数丢失问题 +* fix 修复 自定义字典样式不生效的问题 +* fix 修复 login 页面 loading 未关闭问题 + +## v1.8.1 - 2023-09-26 + +### 依赖升级 + +* update springboot 2.7.14 => 2.7.16 +* update springboot-admin 2.7.10 => 2.7.11 +* update satoken 1.35.0.RC => 1.36.0 +* update lombok 1.18.26 =. 1.18.30 +* update springboot 2.7.13 => 2.7.14 +* update mybatis-plus 3.5.3.1 => 3.5.3.2 +* update easyexcel 3.3.1 => 3.3.2 +* update hutool 5.8.18 => 5.8.20 +* update dubbo 3.1.8 => 3.1.11 + +### 功能更新 + +* update 优化 代码生成 vo实体类序列化 +* update 优化 excel 导出不必要的请求头 +* update 优化 字典标签支持传分隔符分隔的字符串和数组 +* update 优化 控制台debuger位置错误问题 +* update 优化 TopNav 菜单样式 +* update 优化 注册用户异常报错不正确问题 +* update 优化 全局异常处理器 业务异常不输出具体堆栈信息 减少无用日志存储 +* update 优化 用户管理 只查询未禁用的部门角色岗位数据 +* update 优化 岗位如果绑定了用户则不允许禁用 +* update 优化 部门与角色如果绑定了用户则不允许禁用 +* update 优化 加密实现 使用 EncryptUtils 统一处理 +* update 优化 适配 mysql 8.0.34 升级连接机制 +* update 优化 excel导出字典转下拉框 无需标记index自动处理 +* update 优化 excel 导出字典默认转为下拉框 +* update 删除一些跟swagger有关的字眼 避免误解 +* update 优化 角色权限支持仅本人权限查看 解决无法查看自己创建的角色问题 +* update 优化 xxljob 端口随着主应用端口飘逸 避免集群冲突 + +### 问题修复 + +* fix 修复 自定义字典样式不生效的问题 +* fix 修复 新建用户可能会存在的越权行为 +* fix 修复 字典缓存删除方法参数错误问题 +* fix 修复 修复树模板父级编码变量错误 +* fix 修复 demo 模块缺少 security 依赖问题 +* fix 修复 升级 mp 版本导致的问题 +* fix 修复 加密模块数据转换异常问题 +* fix 修复 动态设置 token 有效期不生效问题 +* fix 修复 token 过期登出无法清理在线用户问题 + + +## v2.1.0 - 2023-09-06 + +# 开发历程 + +* 2023年5月 开始 2.1.0 计划 历经1个月的设计与讨论 +* 2023年6月 开始着手开发 历经2个多月的开发 特别感谢团队的小伙伴与一些热心的粉丝 参与功能开发与测试 +* 2023年8月 开始公测 历经将近1个月的公测与修复工作(期间成功支持多位使用者生产使用) +* 2023年9月初 正式发布(经过多个小伙伴的生产实践 已基本可尝试生产使用) +> 关于1.X的说明 由于SpringBoot2.X与vue2.X均在11月底停止维护
+> 故而咱们vue版本1.X也无法再继续更新
+> 介于1.X的用户量特别庞大 功能也非常的稳定
+> 计划于11月底同Boot2.X一同停止更新但还会持续维护修复bug(修复的形式为直接提交到1.X分支停止发版)
+ +# 视频介绍 + +为了更好的让大家了解 2.1.0 作者录制了相关的视频 供大家快速了解上手 + +* 2.1.0 新功能与变更介绍: https://www.bilibili.com/video/BV1fj411y71X/ + +# 更新日志 + +### 重大更新 + +* [重大更新] 优化 相关代码 完成代码生成多数据源统一存储(感谢 WangBQ) +* [不兼容更新] 移除 原短信功能 集成更强大的 sms4j 短信工具包(感谢 友杰) +* [不兼容更新] 对接 powerjob 实现分布式任务调度 删除原有 xxljob 原因为社区不更新功能太少只支持mysql(感谢 yhan219) +* [重大更新] 新增 三方授权绑定登录功能 基于 justauth 支持市面上大部分三方登录(感谢 三个三) +* [不兼容更新] 新增 客户端授权功能 不需要更改任何代码即可完成多端动态对接(感谢 Michelle.Chung) +* [重大更新] 新增 前后端接口请求加密传输 基于AES+RSA动态高强度加密(感谢 wdhcr) +* [重大更新] 新增 三方授权登录 对接 maxkey 单点登录 +* [不兼容更新] 优化 redis序列化配置 更改为通用格式(升级需清除redis所有数据) +* [重大更新] 新增 通过 sharding-proxy 实现分库分表(感谢 rice666 !pr94) + +### 依赖升级 + +* update springboot 3.0.7 => 3.1.3 +* update springboot-admin 3.1.3 => 3.1.5 +* update springcloud 2022.0.2 => 2022.0.4 +* update springcloud-alibaba 2022.0.0.0-RC2 => 2022.0.0.0 +* update springdoc 2.1.0 => 2.2.0 +* update spring-mybatis 3.0.1 => 3.0.2 +* update mybatis-plus 3.5.3.1 => 3.5.3.2 +* update easyexcel 3.2.1 => 3.3.2 +* update mapstruct-plus 1.2.3 => 1.3.5 解决修改实体类 idea不编译问题 +* update satoken 1.34.0 => 1.35.0.RC 优化过期配置 支持多端token自定义有效期 +* update dynamic-ds 3.6.1 => 4.1.3 支持 SpringBoot3 +* update sms4j 2.2.0 +* update hutool 5.8.18 => 5.8.20 +* update redisson 3.20.1 => 3.23.4 +* update lock4j 2.2.4 => 2.2.5 +* update aws-java-sdk-s3 1.12.400 => 1.12.540 +* update maven-surefire-plugin 3.0.0 => 3.1.2 +* update seata 1.6.1 => 1.7.0 +* update sharding-proxy 5.4.0 +* update dubbo 3.2.2 => 3.2.5 +* update skywalking-toolkit 8.14.0 => 8.16.0 +* update logstash 7.2 => 7.4 + +### 功能更新 + +* update 优化 与 vue 版本同步代码结构 +* update 优化 放行springboot默认error接口 +* update 优化 RepeatSubmitAspect 逻辑避免并发请求问题 +* update 调整 gateway 访问日志输出等级 +* update 优化 修改角色如果未绑定用户则无需清理 +* update 优化 用户昵称非空校验 +* update 优化 在全局异常拦截器中增加两类异常处理 +* update 优化 StreamUtils 方法过滤null值 +* update 优化 powerjob 端口随着主应用端口飘逸 避免集群冲突 +* update 优化 角色权限支持仅本人权限查看 解决无法查看自己创建的角色问题 +* update 修改代码生成模版,日期范围统一采用addDateRange方法 +* update 优化 树表生成前端缺少 children 字段 +* update 优化 ruoyi-resource 服务添加 websocket 模块 +* update 优化 放行验证码接口、第三方登录请求与回调 +* update 更新 GlobalLogFilter#filter 根据请求头判断加密参数(感谢 Michelle.Chung !pr100) +* update 优化 SaReactorFilter 过滤器判断 token 客户端 id 是否有效(感谢 Michelle.Chung !pr101) +* update 删除一些跟swagger有关的字眼 避免误解 +* update 优化 兼容 clientid 通过 param 传输 +* update 优化 excel导出字典转下拉框 无需标记index自动处理(感谢 一夏coco) +* update 优化 增加线程池销毁配置 +* update 优化 屏蔽 powerjob 无用的心跳日志 +* update 优化 适配 mysql 8.0.34 升级连接机制 +* update 优化 加密实现 使用 EncryptUtils 统一处理 +* update 优化 删除字典无用状态字段(基本用不上 禁用后还会导致回显问题) +* update 优化 部门与角色如果绑定了用户则不允许禁用 +* update 优化 岗位如果绑定了用户则不允许禁用 +* update 优化 用户管理 只查询未禁用的部门角色岗位数据 +* update 优化 登录用户增加昵称返回 +* update 优化 全局异常处理器 业务异常不输出具体堆栈信息 减少无用日志存储 +* update 优化 将部门管理 负责人选项改为下拉框选择 +* update 优化 登录用户缓存 去除冗余统一存储 +* update 优化 注册用户异常报错不正确问题 +* update 优化 放宽菜单权限 角色关联菜单无需管理员 + +### 新增功能 + +* add 增加 RedisUtils 批量删除 hash key 方法 +* add 新增 Oss 上传 File 文件方法(感谢 jenn) +* add 增加 excel 导出下拉框功能 +* add 新增 RedisUtils.setObjectIfAbsent 如果不存在则设置方法 + +### 修复问题 + +* fix 修复 用户重名登录报错问题 +* fix 修复 服务未添加 common-security 模块导致异常拦截器不生效问题 +* fix 修复 用户篡改管理员角色标识符越权问题 +* fix 修复 文件管理 创建人未翻译问题 +* fix 修复 monitor 监控无法展示数据问题 +* fix 修复 更换 satoken dubbo 插件导致包名不一致问题 +* fix 修复 字典缓存注解使用错误问题 +* fix 修复 接口文档未拼接服务路径问题 +* fix 修复 excel 枚举反向解析失败问题 +* fix 修复 查询部门下拉树未过滤数据权限问题 +* fix 修复 CacheName 缓存key存储错误问题 +* fix 修复 oss 列表 用户名回显错误 +* fix 修复 不同vo相同字段mybatis会自动赋值问题 +* fix 修复 删除 skywalking dubbo 2.X 插件避免与 3.X 出现兼容性问题 +* fix 修复 新增角色使用内置管理员标识符问题 +* fix 修复 token 过期登出无法清理在线用户问题 +* fix 修复 动态设置 token 有效期不生效问题 +* fix 修复 加密模块数据转换异常问题 +* fix 修复 dubbo 更改内部序列化方式 导致异常类无法反序列化问题 +* fix 修复 客户端编辑时授权类型变更未保存的问题 +* fix 修正 缺失 SysClientVoConvert 导致转换异常(感谢 Michelle.Chung) +* fix 修正 auth 模块缺失引用导致解密异常(感谢 Michelle.Chung) +* fix 修复 demo 与 stream-mq 模块缺少 security 依赖问题 +* fix 修复 导入用户数据 变量使用错误问题 +* fix 修复 验证码开关未动态刷新问题 +* fix 修复 自动填充数据 loginUser 为 null(感谢 charles !pr108) +* fix 修复 修复树模板父级编码变量错误 +* fix 修复 部署部分系统出现乱码问题 +* fix 修复 一级菜单无法显示问题 +* fix 修复 新建用户可能会存在的越权行为 +* fix 修复 代码生成页面参数缺少逗号问题 + +### 移除功能 + +* remove 移除原有短信功能(建议使用sms4j) +* remove 移除xxljob功能(建议使用powerjob) + + +## v1.8.0 - 2023-07-11 + +### 重大更新 + +* [重大更新] 新增 sms4j 短信融合框架整合(支持数十种短信厂商接入、发送限制、负载均衡等功能) +* [不兼容更新] 移除 原短信功能(建议使用新 sms4j 功能) +* [重要迁移] 迁移 vue3 前端到主仓库统一维护 + +### 依赖升级 + +* update springboot 2.7.11 => 2.7.13 +* update spring-cloud 2021.0.7 => 2021.0.8 +* update satoken 1.34.0 => 1.35.0.RC +* update easyexcel 3.2.1 => 3.3.1 +* update sms4j 2.2.0 +* update element 2.15.12 => 2.15.13 + +### 功能更新 + +* update 优化 StreamUtils 方法过滤null值 +* update 优化 页签在Firefox浏览器被遮挡 +* update 优化 在全局异常拦截器中增加两类异常处理 +* update 优化 下载zip方法增加遮罩层(感谢@梁剑锋) +* update 优化 用户昵称非空校验 +* update 优化 修改角色如果未绑定用户则无需清理 +* update 优化 RepeatSubmitAspect 逻辑避免并发请求问题 +* update 优化 satoken 过期配置 支持多端token自定义有效期 +* update 优化 加密注解注释错误 +* update 优化 切换 maven 仓库到华为云(aliyun 不可用) +* update 优化 excel 导出存在合并项时在初始化类时进行数据的处理避免多次调用(感谢@yueye) +* update 优化 重构 CellMergeStrategy 支持多级表头修复一些小问题 整理代码结构 +* update 补全 SysLogininforMapper.xml 缺失字段 +* update 优化 demo 模块 路径适配统一前端 +* update 调整 gateway 访问日志输出等级 + +### 新增功能 + +* add 新增 RedisUtils.setObjectIfAbsent 不存在则设置方法 +* add 新增 Excel 导出附带有下拉框(字典自动导出为下拉框) 可自定义多级下拉框(感谢@Emil.Zhang) +* add 新增 OssClient File 文件上传方法 +* add 增加 RedisUtils 批量删除 hash key 方法 + +### 问题修复 + +* fix 修复 sa-token.check-same-token 开关对网关鉴权无效问题 +* fix 修复 服务未添加 common-security 模块导致异常拦截器不生效问题 +* fix 修复 删除 skywalking dubbo 2.X 插件避免与 3.X 出现兼容性问题 +* fix 修复 excel 枚举反向解析失败问题 +* fix 修复 字典缓存注解使用错误问题 +* fix 修复 新增角色使用内置管理员标识符问题 +* fix 修复 缓存监控图表 支持跟随屏幕大小自适应调整(感谢@抓蛙师) +* fix 修复 防重组件 错删注解问题 +* fix 修复 CacheName 缓存key存储错误问题 +* fix 修复 字典缓存注解使用错误问题 +* fix 修复 用户篡改管理员角色标识符越权问题 +* fix 修复 登录校验错误次数未达到上限时 错误次数缓存未设置有效时间问题 +* fix 修复 OssClient 切换服务 实例不正确问题 +* fix 修复 element ui 因版本而未被工具识别问题(感谢@梁剑锋) +* fix 修复 admin监控 切换tab页需要重复登录问题 +* fix 修复 个人中心tab栏关闭页面内容压缩问题 + +## v2.0.0 - 2023-06-15 + +**重点说明: 由于 SpringCloudAlibaba 一直未发布正式版 导致系统底层组件可能存在些许问题 故而不建议生产使用 框架也将直接开启后续 2.1.0 的开发工作** + +### 重大更新 + +* [不兼容升级] java 版本从 jdk 8 升级到 jdk 17 且需要使用 graalvm 运行(暂时未解决原生jdk存在的问题) +* [不兼容升级] springboot 升级 3.0 版本 +* [不兼容升级] 重构 项目模块结构 采用插件化结构 易扩展易解耦 +* [不兼容升级] com.sun.mail 更改为 jakarta.mail 修改最新写法 +* [不兼容升级] javax.servlet 替换为 jakarta.servlet 更新所有代码 +* [简化性升级] 默认开启复杂结构 resultMap 自动映射 简化xml编码(多结构实体需带上主键id) +* [数据库改动] 更新 create_by update_by 字段类型 (保存用户id) +* [数据库改动] 新增 create_dept 字段 (保存创建部门id) +* [不兼容更新] system 模块 所有实体类均使用 bo|vo 规范化 +* [重大更新] 新增 多租户功能设计 整体框架代码结构与数据库更改 +* [重大更新] 新增 mapstruct-plus 替换 BeanUtil 与 BeanCopyUtils 工具 +* [不兼容更新] 重构 登录注解接口与cloud版本统一接口路径 +* [不兼容更新] 重构 BaseMapperPlus接口 去除 `@param Mapper` 泛型 +* [不兼容更新] 移除 vue2 前端工程 全面启用 vue3 +* [重大更新] 新增 vue3 + TS 版本前端(独立仓库后续与Cloud版本共用) +* [重大更新] 增加 websocket 模块 支持token鉴权 支持分布式集群消息同步 +* [重大更新] 框架文档全面翻新 https://plus-doc.dromara.org +* [不兼容更新] 代码生成 支持代码生成多数据源统一存储(主库存储子库的表 无需子库加gen表了) +* [不兼容更新] 重构 将系统内置配置放置到common包内独立加载 不允许用户随意修改 + +### 依赖升级 + +* update java 1.8 => 17 +* update springboot 2.7.7 => 3.0.7 +* update springcloud 2021.0.6 => 2022.0.2 +* update springcloud-alibaba 2022.0.0.0-RC2 +* update springboot-admin 2.7.10 => 3.0.4 +* update springdoc 1.6.14 => 2.1.0 +* udpate dubbo 3.1.8 => 3.2.2 +* update lock4j 2.2.3 => 2.2.4 +* update dynamic-ds 3.5.2 => 3.6.1 +* update easyexcel 3.1.5 => 3.2.1 +* update hutool 5.8.11 => 5.8.18 +* update redisson 3.19.2 => 3.20.1 +* update lombok 1.18.24 => 1.18.26 +* update spring-boot.mybatis 2.2.2 => 3.0.1 +* update mapstruct-plus 1.2.3 +* update maven-compiler-plugin 3.10.1 => 3.11.0 +* update maven-surefire-plugin 3.0.0-M7 => 3.0.0 +* update docker mysql 8.0.31 => 8.0.33 +* update docker nginx 1.22.1 => 1.32.4 +* update docker redis 6.2.7 => 6.2.12 +* update docker minio RELEASE.2023-04-13T03-08-07Z + +### 功能更新 + +* update 适配 AsyncConfig 替换过期继承类改为实现 AsyncConfigurer 接口 +* update 适配 redis 新版本配置文件写法 +* update 适配 获取redis 监控参数接口 替换过期语法 +* update 适配 sa-token 替换新依赖 sa-token-spring-boot3-starter +* update 适配 springboot-admin 改为最新 spring-security 写法 +* update 适配 springdoc 新版本配置方式 +* update 适配 ServletUtils 更换继承 JakartaServletUtil +* update 适配 新序列化注解 +* update 优化 利用 resultMap 自动映射配置 简化 xml (非嵌套) +* update 优化 调整 system entity 实体与 controller 包结构 +* update 优化 实体类中校验注解的提示信息 +* update 优化 使用 jdk17 语法优化代码 +* update 优化 所有 properties 文件改为注解启用 +* update 更新 docker 基础镜像 graalvm java17 +* update 优化 用户头像 改为存储 ossId 使用转换模块转为 url 展示 +* update 优化 重构 CellMergeStrategy 支持多级表头修复一些小问题 整理代码结构 +* update 优化 登录流程代码注释 +* update 优化 将框架内的swagger命名更改为springdoc命名避免误解 + +### 新增功能 + +* add 新增 flatten-maven-plugin 插件统一版本号管理 +* add 新增 ip2region 实现离线IP地址定位库 + +### 移除功能 + +* remove 移除 BeanCopyUtils 工具类 与 JDK17 不兼容 +* remove 移除 devtools 依赖 并不好用(建议直接用idea自带的热更) +* remove 移除 vue2 前端工程 统一使用 vue3 工程 + +### 修复功能 + +* fix 修复 根据 seata 官方提交记录 临时修复 seata 关于jdk17代理的bug +* fix 修复 登录校验错误次数未达到上限时 错误次数缓存未设置有效时间问题 +* fix 修复 common-core 包使用aop注解 但未添加aop实现类导致单独使用报错问题 + +## v1.7.0 - 2023-05-10 + +### 依赖升级 + +* update springboot 2.7.9 => 2.7.11 修复 DoS 漏洞 修复CVE漏洞 +* update springcloud 2021.0.6 => 2021.0.7 +* update springcloud-alibaba 2021.0.4.0 => 2021.0.5.0 +* update dubbo 3.1.7 => 3.1.10 +* update nacos 2.2.0 => 2.2.1 +* update xxljob 2.3.1 => 2.4.0 +* update minio 升级至最新版 避免低版本信息泄漏问题 +* update hutool 5.8.15 => 5.8.18 +* update redisson 3.20.0 => 3.20.1 +* update lombok 1.18.24 => 1.18.26 + +### 功能更新 + +* update 优化 更改 sys_oss_config 表注释 避免误解 +* update 优化 sys_logininfor 丰富多种信息 +* update 项目正式入驻 dromara 开源社区 更改项目地址 +* update 全新 logo 全新背景图(设计师打造) +* update 优化 代码生成模块的数据同步功能 +* update 修改多团队开发插件,支持多网卡 +* update 修改controller中校验直接返回R.fail +* update 优化 角色sort值一样的排序问题 +* update 更换默认用户头像 +* update 优化 WebFluxUtils.getOriginalRequestUrl 方法获取空路径报错问题 +* update 去除same-token有限期配置,使用默认配置(一天) +* update 优化固定头部页签滚动条被隐藏的问题 +* update delete vue-multiselect style +* update 按代码规范补全重写注解 +* update 优化 极端情况获取LoginUser可能为null问题 +* update 优化 更改系统所有服务日志配置文件命名为 logback-plus.xml 避免与其他框架默认配置冲突 +* update 优化 skywalking-agent 探针日志等级调整为 WARN 减少无用日志输出 +* update 优化 加解密模块 将null判断下推防止任何可能的null出现 +* update 优化 在线用户token获取方式 +* update 优化 用户更改角色 踢掉角色相关所有在线用户 + +### 新功能 + +* add 集成 ip2region 实现离线IP地址定位库 +* add 增加 邮箱验证码发送接口 +* add 增加 邮箱登陆接口 +* add 增加 EncryptUtils 加解密安全工具类 可以处理base64,aes,sm4,sm2,rsa,md5,sha256加解密 +* add 增加 EncryptUtils 类中增加国密sm3的不可逆加密算法 +* add 新增 忽略数据权限写法 防止异常不执行关闭问题 + +### 问题修复 + +* fix 修复 MybatisExceptionHandler 未自动装载问题 +* fix 修复 代码生成 点选按钮不生效问题 +* fix 修复 Nacos 服务 SpringBoot-admin 客户端功能失效问题 +* fix 修复 findInSet 在mysql下方法搜索非数字字段时 无引号报错问题 +* fix 修复 ruoyi-demo postgres 数据库用户名密码变量错误 +* fix 修复 oracle postgres 数据库日志表索引创建错误 +* fix 修复 无法注入 mailProperties 导致 resource 模块无法启动问题 +* fix 修复tab栏”关闭其他“异常的问题 +* fix 修复 加解密拦截器 对象属性为null问题 +* fix 修复 取消oss预览状态修改 图标变化不正常问题 +* fix 修复 nacos 新版本升级后 与 docker 基础镜像系统存在兼容性问题 + + +## v1.6.0 - 2023-03-14 + +### 重大更新 + +[重大更新] add 新增 通用翻译模块 `ruoyi-common-translation` 实现(部门名、字典、oss、用户名) +[重大更新] add 新增 数据加解密模块 `ruoyi-common-encrypt` + + +### 依赖升级 + +* update springboot 2.7.7 => 2.7.9 +* update springcloud 2021.0.5 => 2021.0.6 +* update easyexcel 3.1.5 => 3.2.1 +* update redisson 3.19.1 => 3.20.0 +* update springdoc 1.6.14 => 1.6.15 +* update hutool 5.8.12 => 5.8.15 (13与14有问题勿使用) +* update logstash-sdk 7.1.1 => 7.2 +* update aws-java-sdk-s3 1.12.373 => 1.12.400 +* update tencent-sms 3.1.660 => 3.1.687 +* update skywalking 8.9.1 => 9.3.0 +* update skywalking-agent 8.13.0 => 8.14.0 +* update dubbo 3.1.4 => 3.1.7 解决dubbo报一些无用警告问题 +* update element-ui 2.15.10 => 2.15.12 + +### 功能更新 + +* update 优化 修改 oss 配置页面开关说明 避免造成误解 +* update 优化 `gateway` 对接 `sentinel` 使用网关特定模式 +* update 优化 转移 `logback-common` 配置到 `common-web` 模块 `gateway` 单独处理 +* update 优化 调整连接池默认参数 +* update 优化 `zookeeper` 自带控制台占用 `8080` 端口 +* update 优化 `DictDataMapper` 注解标注过期 推荐使用 `@Translation` 注解 +* update 优化 获取菜单数据权限接口 删除无用角色属性与逻辑 +* update 优化 调整连接池最长生命周期 防止出现警告 +* update 优化 连接池增加 `keepaliveTime` 探活参数 +* update 优化 `DataPermissionHelper` 增加 `开启/关闭` 忽略数据权限功能 +* update 重构 `OssFactory` 加载方式 改为每次比对配置做实例更新 +* update 优化 更新角色后踢掉所有相关的登录用户 用户量过大会导致redis阻塞卡顿(应粉丝要求) +* update 优化 翻译组件 支持返回值泛型 支持多种类型数据翻译(例如: 根据主键翻译成对象) +* update 优化 `tagsView` 右选框,首页不应该存在关闭左侧选项 +* update 优化 `copyright 2023` +* update 优化 日志注解支持排除指定的请求参数 +* update 优化 业务校验优化代码 +* update 优化 日志管理使用索引提升查询性能 +* update 优化 框架时间检索使用时间默认值 `00:00:00 - 23:59:59` +* update 优化 oss 预览使用 `ImagePreview` 组件 +* update 优化 统一登录接口令牌key + + +### 新功能 + +* add 新增 数据加解密模块 测试案例 +* add 新增 `StringUtils` `splitTo` 与 `splitList` 方法 优化业务代码 + +### 问题修复 + +* fix 修复 vue3模板 删除功能书写错误 +* fix 修复 部分服务未开启日志存储 +* fix 修复 接口问题开关不生效问题 +* fix 修复 优化文件下载出现的异常 +* fix 修复 修改密码日志存储明文问题 +* fix 修复 代码生成 `postgreSQL` 查出多余的已删除字段 + +## v1.5.0 - 2023-01-13 + +### 重大更新 + +* [重大更新] 框架主体业务与代码生成器 完成 oracle postgres 多数据库类型支持(中间件不支持) +* [重大更新] 使用 spring 事件发布机制 重构登录日志与操作日志 支持多事件监听无入侵扩展 +* 例如: 可以增加一个监听者将日志上传至ES等存储 对原有逻辑无影响 + +### 依赖升级 + +* update springboot 2.7.6 => 2.7.7 +* update springboot-admin 2.7.7 => 2.7.10 +* update dubbo 3.1.3 => 3.1.4 +* update seata 1.5.2 => 1.6.1 适配升级 +* update nacos 2.1.2 => 2.2.0 适配升级 +* update mybatis-plus 3.5.2 => 3.5.3.1 +* update sa-token 1.33.0 => 1.34.0 +* update springdoc 1.6.13 => 1.6.14 +* update snakeyaml 1.32 => 1.33 +* update easyexcel 3.1.3 => 3.1.5 +* update redisson 3.18.0 => 3.19.1 +* update easy-es 1.1.0 => 1.1.1 +* update hutool 5.8.10 => 5.8.11 +* update aws-s3 1.12.349 => 1.12.373 +* update aliyun-sms 2.0.22 => 2.0.23 +* update tencent-sms 3.1.635 => 3.1.660 +* update echarts 4.9.0 => 5.4.0 + +### 功能更新 + +* update 优化 BaseMapperPlus 使用 MP V3.5.3 新工具类 Db 简化批处理操作实现 +* update 优化 demo服务 过滤健康检查 sql 打印 +* update 优化 代码生成与框架主体使用相同的主键生成器 全局统一避免问题 +* update 优化 系统登录 使用单表查询校验用户 避免多次join查询 +* update 优化 适配框架多数据库支持 完成 oracle postgres 数据库适配(放弃 sqlserver 适配 原因: 基础中间件均不支持) +* update 优化 删除主 sql 内无用数据 +* update 优化 删除 vue3 模板无用参数 +* update 优化 重构 ExcelUtil 全导出方法支持 OutputStream 流导出 不局限于 response +* update 优化 maven 地址切换回 aliyun 仓库 +* update 优化 springdoc 配置鉴权头写死问题 增加持久化鉴权头配置 +* update 优化 actuator 依赖整合到 common-web 模块 +* update 优化 验证码结果使用 spel 引擎自动计算 +* update 优化 数据权限处理器 变量命名错误 +* update 优化 去除 RedisUtils 无用继承 +* update 优化 弹窗内容过多展示不全问题 +* update 优化 删除 fuse 无效选项 maxPatternLength +* update 优化 minio 安装警告 使用新版本参数 +* update 优化 使用 spring 事件发布机制 重构登录日志与操作日志 +* update 优化 使用 spring 事件机制 重构 OssConfig 缓存更新 +* update 优化 单元格合并判断cellValue是否相等方法 +* update 优化 调整 gateway 拦截器执行顺序 优先处理 xss 过滤 然后进行缓存处理 + +### 新功能 + +* add 增加 GET 请求提交日期参数 默认格式化配置 +* add 增加 RedisUtils 检查缓存对象是否存在方法 +* add 增加 oracle postgres docker编排 +* add 新增 代码生成器适配 多数据库可切换生成代码 +* add 新增 oracle postgres 数据库框架sql脚本 +* add 增加 DataBaseHelper 数据库助手 用于适配多类型数据库 +* add 新增 BeanCopyUtils#mapToMap 方法 + +### 问题修复 + +* fix 修复 注册页面 验证码开关不生效问题 +* fix 修复 新版本 dubbo-filter-seata 插件内核与seata不一致问题(临时) +* fix 修复 根据 key 更新参数配置报 null 问题 +* fix 修复 用户注册 用户类型字段书写错误 +* fix 修复 代码生成图片/文件/单选时选择必填无法校验问题 +* fix 修复 修改参数键名时 未移除过期缓存配置 +* fix 修复 内网鉴权 Filter 优先级问题 导致 websocket 连接失败 +* fix 修复 gateway 流控规则生效但不显示问题 +* fix 修复 新版本 Redisson 存在与 boot 2.X 的兼容性问题 + +## v1.4.0 - 2022-12-01 + +### 重大更新 +* [重大更新] 新增 对接 skywalking 全功能(详细看下方新功能列表) +* [重大更新] 重构 ruoyi-nacos 使用官方依赖整合 解决一些问题 并升级 2.1.2 版本 +* [重大更新] 新增 oss 私有库功能(数据库结构改动 需执行升级sql) +* [重大更新] 优化 数据源连接池从 druid 切换到 hikari(原因看文档) +* [重大更新] 新增 对接 prometheus + grafana 全功能(详细看下方新功能列表) + +### 依赖升级 +* update springcloud 2021.0.4 => 2021.0.5 +* update springboot 2.7.4 => 2.7.6 +* update springboot-admin 2.7.5 => 2.7.7 +* update springdoc 1.6.11 => 1.6.13 +* update poi 5.2.2 => 5.2.3 +* update hutool 5.8.6 => 5.8.10 +* update aliyun-sms 2.0.18 => 2.0.22 +* update tencent-sms 3.1.591 => 3.1.611 +* update sa-token 1.30.0 => 1.33.0 +* update redisson 3.17.6 => 3.18.0 +* update easy-es 1.0.2 => 1.1.0 +* update easyexcel 3.1.1 => 3.1.3 +* update lock4j 2.2.2 => 2.2.3 +* update s3-adk 1.12.300 => 1.12.349 +* update sentinel 1.8.5 => 1.8.6 +* update nacos 2.1.1 => 2.1.2 +* update ELK 7.17.2 => 7.17.6 升级镜像版本 +* update nginx 1.21.6 => 1.22.1 修复漏洞 +* update mysql-docker 8.0.29 => 8.0.31 + +### 功能更新 +* update 优化 分页对象 PageQuery 支持多排序 适配 文件管理 页面支持多排序 +* update 优化 获取用户信息getInfo接口 使用缓存数据获取 +* update 优化 rpc文件上传 增加 ossId 数据返回 +* update 优化 nacos 集群模式搭建 关于 nacos.home 注释说明 +* update 优化 修改头像在小屏幕上页面布局错位的问题 +* update 优化 oss 云厂商增加 华为obs关键字 +* update 优化 重置时取消部门选中 +* update 优化 新增返回警告消息提示 +* update 优化 抽取 logback 通用配置 logback-common.xml 简化其他服务日志文件书写 +* update 更改 nacos 配置文件目录 从dev文件夹迁移到nacos文件夹与其他配置区分 +* update 优化 gateway 只缓存body +* update 优化 Dockerfile 创建目录命令简化操作 +* update 优化 gateway filter顺序 与 代码工具封装 +* update 优化 将空 catch 块形参重命名为 ignored +* update 优化 satoken 依赖传递 +* update 优化 重写字典查询 使用本地缓存优化网络开销 提升到上级实现减少rpc调用频率 使用流处理减少字符串操作 +* update 优化 减小腾讯短信引入jar包的体积 +* update 优化 简化一些方法的写法 +* update 优化 消除Vue3控制台出现的警告信息 +* update 优化 忽略不必要的属性数据返回 +* update 优化 重构 mysql-jdbc 依赖到 mybatis 包内 替换为最新坐标 + +### 新功能 +* add 新增 所有服务 docker 部署对接 skywalking +* add 新增 三大 mq 整合 skywalking +* add 新增 seata 整合 skywalking 手动编译 seata 插件包 +* add 新增 ruoyi-common-skylog 整合 skywalking 日志推送 +* add 增加 skywalking docker编排 +* add 增加 ruoyi-seata-server redis模式配置 +* add 新增 ruoyi-common-prometheus 模块 用于对接 prometheus 监控 +* add 新增 docker prometheus + grafana 容器编排 +* add 新增 ruoyi-monitor 监控服务 提供 prometheus http_sd 服务发现功能 +* add 新增 所有服务整合 ruoyi-common-prometheus 模块 +* add 新增 grafana 监控大屏配置文件(框架定制) +* add 新增 使用 mica-metrics 为 undertow 提供健康检查 +* add 新增 字典数据映射翻译注解 +* add 增加 RedisUtils 获取缓存Map的key列表 + +### 问题修复 +* fix 修复 开启账号同端互斥登录 被顶掉后登出报null异常问题 +* fix 修复 设置NameMapper导致队列功能异常问题 +* fix 修复 EnvironmentPostProcessor 不生效问题 +* fix 修复 文件上传组件格式验证问题 +* fix 修复 ruoyi-xxl-job-admin 服务健康检查配置缺失问题 +* fix 修复 Excel导出字典值转换方法由于内部调用缓存不生效bug +* fix 修复 SysOss 方法内部调用导致缓存不生效 bug +* fix 修复 主题颜色在Drawer组件不会加载问题 +* fix 修复 修改用户信息 校验用户名未排除当前用户问题 +* fix 修复 升级 nginx 修复漏洞 https://www.oschina.net/news/214309 +* fix 修复 用户编辑时角色和部门存在无法修改情况 +* fix 修复 RemoteDictServiceImpl 代理对象获取异常bug +* fix 修复 菜单激活无法填充颜色 去除某些svg图标的fill属性 +* fix 修复 使用透明底png图片时, 自动填充黑色背景 +* fix 修复 table中更多按钮切换主题色未生效修复问题 +* fix 修复 dubbo 使用 tri 协议 header 请求头变为小写导致无法获取参数问题 +* fix 修复 DubboRequestFilter 优先级过高导致的 skywalking tid 取不到问题 +* fix 修复 前端脚本乱码问题 +* fix 修复 WebFluxUtils 读取空 body 报 null 问题 +* fix 修复 Log注解GET请求记录不到参数问题 +* fix 修复 某些特性的环境生成代码变乱码TXT文件问题 +* fix 修复 开启TopNav没有子菜单隐藏侧边栏 +* fix 修复 回显数据字典数组异常问题 +* fix 修复 升级 satoken 导致白名单热更不生效问题 +* fix 修复 swagger 版本与 springdoc 版本不一致导致找不到class问题 +* fix 修复 grafana 监控模板绑定数据源ID 导致无法正常读取数据问题 + +## v1.3.0 - 2022-09-29 + +### 重大更新 + +* [重大更新] 新增 ruoyi-nacos 源码集成 nacos 服务端控制台 支持单机/集群模式 +* [重大更新] 重写 spring-cache 实现 更人性化的操作 支持注解指定ttl等一些参数 +* [重大更新] 新增 RuoYi-Cloud-Plus-UI 项目 Vue3 前端分支 +* [重大更新] 移除maven docker插件 过于老旧功能缺陷大 使用idea自带的docker插件替代 +* [重大更新] 优化 ruoyi-common-job 支持通过调度中心服务名注册 xxl-job-admin +* [重大更新] 新增 ruoyi-common-sentinel 模块 支持使用服务名注册 sentinel 控制台 + +### 依赖升级 + +* update spring-cloud 2021.0.3 => 2021.0.4 +* update springboot 2.7.2 => 2.7.4 +* update springboot-admin 2.7.3 => 2.7.5 +* update sentinel 1.8.4 => 1.8.5 集成新 dubbo3 插件 +* update springdoc 1.6.9 => 1.6.11 +* update easy-es 0.9.80 => 1.0.2 +* update dubbo 3.0.10 => 3.1.1 +* update redisson 3.17.5 => 3.17.6 +* update druid 1.2.11 => 1.2.12 +* update hutool 5.8.5 => 5.8.6 +* update dynamic-ds 3.5.1 => 3.5.2 +* update aws-java-sdk-s3 1.12.264 => 1.12.300 +* update aliyun-sms 2.0.16 => 2.0.18 +* update tencent-sms 3.1.555 => 3.1.591 +* update snakeyaml 1.30 => 1.32 + +### 功能更新 + +* update 优化 getLoginId 增加必要参数空校验 +* update 优化 将 elasticsearch 解压后放入 避免造成用户误解 +* update 优化 修改资料头像与部门被覆盖的问题 +* update 优化 字典管理操作类型新增其他 +* update 优化 使用 spring-cache 注解优化缓存 +* update 优化 easy-es.enable=false 关闭 actuator 健康检查 +* update 优化 优化多角色数据权限匹配规则 +* update dubbo 升级 3.1.0 删除自行处理的源码修复 采用官方修复后的代码 +* update 优化 页面内嵌iframe切换tab不刷新数据 +* update 优化 调整 oss表key 与 ossconfig的service 字段长度不匹配 +* update 优化 操作日志密码脱敏 +* update 优化 补全缺失的接口 更改更新日志链接 +* update 优化 插入 SysOperLog 时, 限制 operUrl 属性的长度 +* update 优化 satoken 鉴权拦截器 优化多次校验 + +### 新功能 + +* add 增加 项目中使用到的请求头放行跨域 +* add 新增 获取oss对象元数据方法 +* add 新增 字典管理操作类型 其他 + +### 问题修复 + +* fix 修复 个人中心卡死或鼠标点击和键盘输入无效 +* fix 修复 BaseMapperPlus 方法命令不一致问题 +* fix 修复 图片预览组件src属性为null值控制台报错问 +* fix 修复 短信功能是否启用判断不生效 +* fix 修复 web模块 不引入nacos依赖报错问题 +* fix 修复 sentinel 构建无法读取webapp目录问题 +* fix 修复 菜单管理遗漏的prop属性 +* fix 修复 minio配置https遇到的问题 +* fix 修复 点击删除后点击取消控制台报错问题 +* fix 修复 文件/图片上传组件 第一次上传报错导致后续上传无限loading问题 +* fix 修复 ruoyi-auth 服务与 elasticsearch 端口号冲突问题 +* fix 修复 ruoyi-resource 服务与 elasticsearch 端口号冲突问题 +* fix 修复 角色部门状态字典错误 与 菜单注释错误 +* fix 修复 hutool 存在多版本问题 +* fix 修复 openapi结构体 因springdoc缓存导致多次拼接接口路径问题 +* fix 修复 oss配置删除内部数据id匹配类型问题 +* fix 修复 没有权限的用户编辑部门缺少数据 +* fix 修复 用户导入存在则更新不生效 +* fix 修复 日志转换非json数据导致报错 +* fix 修复 p6spy输出sql语句时间格式化不正确问题 +* fix 修复 不同网段因reset请求头导致下载导出跨域问题 +* fix 修复 在线用户设置永不过期 超时时间-1推送redis无效问题 +* fix 修复 snakeyaml 1.31 依旧存在漏洞 升级 1.32 + +## v1.2.0 - 2022-08-09 + +### 重大更新 + +* [重大更新] 新增 ruoyi-common-elasticsearch 模块 集成 easy-es 傻瓜式操作搜索引擎 +* [重大更新] 新增 ruoyi-common-doc 整合 springdoc 基于 javadoc 实现无注解零入侵生成接口文档 +* [不兼容更新] 移除 swagger 所属 ruoyi-doc ruoyi-common-swagger 两个模块 建议使用 ruoyi-common-doc 模块 + +### 依赖升级 + +* update springboot 2.6.9 => 2.7.2 重构使用最新自动配置方式 +* update springboot-admin 2.6.7 => 2.7.3 +* update dubbo 3.0.9 => 3.0.10 +* update redisson 3.17.4 => 3.17.5 +* update hutool 5.8.3 => 5.8.5 +* update okhttp 4.9.1 => 4.10.0 +* update aws-java-sdk-s3 1.12.248 => 1.12.264 修复依赖安全漏洞 +* update aliyun.sms 2.0.9 => 2.0.16 +* update tencent.sms 3.1.537 => 3.1.555 +* update guava 30.0-jre => 31.1-jre + +### 功能更新 + +* update 修改 资源服务 不提供默认短信 sdk 依赖 +* update 优化表格上右侧工具条(搜索按钮显隐&右侧样式凸出) +* update 优化 前后端多环境部署保持一致 删除无用环境文件 +* update 优化 错误登录锁定与新增解锁功能 +* update 优化字典数据使用store存取 +* update 优化布局设置使用el-drawer抽屉显示 +* update 更新框架文档 专栏与视频 链接地址 +* update 优化 对象上传 主动设置文件公共读 解决天翼云OSS文件私有问题 +* update 优化 网关验证码过滤器 路径匹配改为严格匹配 +* update 优化 数据导致权限生成 SQL 重复问题 + +### 新功能 + +* add 增加 全局跨域过滤器 处理跨域请求 适配移动端访问 +* add 增加 搜索引擎 crud 演示案例 + +### 问题修复 + +* fix 防止date-picker组件报错,降级element-ui版本 +* fix 修复 RedisUtils 并发 set ttl 错误问题 +* fix 防止vue3主键字段名与row或ids一致导致报错的问题 +* fix 修复 幂等组件 逻辑问题导致线程变量未清除 +* fix 修复 图片回显查询 路径错误问题 +* fix 修复 脱敏没有实现类导致返回数据异常问题 +* fix 修复 xxljob 错误导入配置文件引发的问题 +* fix 修复 gateway模块 dockerfile 端口编写错误 +* fix 修复用户导出字典使用错误 +* fix 修复 demo 模块 远程调用失败问题 +* fix 修复 sentinel 控制台未适配 springboot 2.6 新路由策略导致无法登录问题 + +## v1.1.0 - 2022-07-18 + +### 重大更新 + +* [重大更新] 新增 ELK 分布式日志中心整合 +* [重大更新] 新增 ruoyi-stream-mq 演示模块 完成 RabbitMQ RocketMQ Kafka 整合 +* [重大更新] 优化 docker 部署方式 使用 host 模式简化部署流程 降低使用成本 +* [重大更新] 调整 dubbo 服务注册命名空间与 cloud 服务保持一致 通过注册组区分访问服务 +* [安全性] 优化 nginx 限制外网访问内网 actuator 相关路径 建议升级 + +### 依赖升级 + +* update springboot 2.6.8 => 2.6.9 +* update easyexcel 3.1.0 => 3.1.1 +* update hutool 5.8.2 => 5.8.3 +* update redisson 3.17.2 => 3.17.4 +* update aws-java-sdk-s3 1.12.215 => 1.12.248 +* update tencentcloud-sdk-java 3.1.500 => 3.1.537 +* update dubbo 3.0.8 => 3.0.9 +* update seata 1.5.1 => 1.5.2 + +### 功能更新 + +* update 增加 redisson key 前缀配置 +* update 优化 DateColumn 支持单模板多key场景 +* update 优化部署脚本 增加 elk kafka rabbitmq rocketmq 等配置 +* update 修改 oss 客户端自定义域名 统一使用https开关控制协议头 +* update 优化 使用 StreamUtils 简化业务流操纵 +* update 优化 ruoyi-demo 模块 去除用不上的 seata 依赖 +* update 优化 接口文档 接口地址与服务地址不匹配问题 +* update 优化字典数据回显样式下拉框显示值 +* update 默认不启用压缩文件缓存防止node_modules过大 +* update 优化登出方法 + +### 新功能 + +* add 增加 rocketmq docker编排 +* add 新增 rabbitmq docker编排 包含延迟插件 +* add 新增 kafka docker编排 +* add 增加 es ik 分词器插件集成 +* add 增加 StreamUtils 流工具 简化 stream 流操纵 + +### 问题修复 + +* fix 修复 获取 SensitiveService 空问题 增加空兼容 +* fix 修复 演示页面导出路径错误 +* fix 修复 minio 上传自定义域名回显路径错误问题 +* fix 修复 hutool 工具返回不可操纵类型 导致报错问题 +* fix 修复 远程调用短信功能返回实体 SysSms 序列化报错问题 +* fix 修复 复制过程错误 导致演示excel文件损坏问题 +* fix 修复 dubbo 注册组不生效问题 通过覆盖源码方式 +* fix 修复代码生成首字母大写问题 + + +## v1.0.0 - 2022-06-20 + +### 新增/优化 工程模块 + +* add 新增 ruoyi-common-alibaba-bom 工程管理 alibaba 相关依赖 +* add 新增 ruoyi-common-bom 工程管理 ruoyi-common 相关依赖 +* add 新增 ruoyi-api-bom 工程管理 ruoyi-api 依赖项 +* add 新增 ruoyi-api-resource 模块 规范用法 移除 ruoyi-file 模块 +* add 新增 ruoyi-common-web 模块 使用 undertow 替换 tomcat +* add 新增 ruoyi-common-dubbo 整合 dubbo 3.X 实现高性能 rpc 远程调用 替换 feign +* add 新增 ruoyi-common-dict 实现字典多服务调用 +* add 新增 ruoyi-common-loadbalancer 自定义负载均衡模块 用于多团队开发 +* add 新增 ruoyi-common-excel 模块 集成 Alibaba EasyExcel 替换 自带excel实现 +* add 新增 ruoyi-common-oss 模块 支持 AWS S3 协议 分布式文件存储 +* add 新增 ruoyi-common-mail 邮件模块 +* add 新增 ruoyi-common-sms 短信模块 整合 阿里云、腾讯云 短信功能 +* add 新增 ruoyi-common-idempotent 分布式幂等模块 +* add 新增 ruoyi-common-satoken 整合 sa-token 重写所有权限 +* add 新增 ruoyi-xxl-job-admin 整合 xxljob 替换 quartz 支持分布式任务调度 +* add 新增 ruoyi-job 模块 统一远程处理任务 规范用法 +* add 新增 ruoyi-doc 模块 集成 Knife4j 替换 swagger +* add 新增 ruoyi-seata-server 源码集成 Seata 1.5.X 服务端 +* add 新增 ruoyi-sentinel-dashboard 模块 源码集成 sentinel 控制台 +* update 抽取所有公用配置到 maven profile 管理 + +### 代码依赖改动 + +* update SpringCloud 2021.0.3 +* update 适配 SpringCloudAlibaba 2021.0.1.0 全新配置方式 +* update poi 4.1.2 => 5.2.2 性能大幅提升 +* update 重构 整合 jackson 替换 fastjson +* update 重构 整合 redisson 客户端 +* update 重构 整合 mybatis-plus +* update 重写 数据权限实现 基于 mybatis-plus +* add 增加 lombok 优化原生代码 +* add 整合 hutool 优化相关代码 +* add 新增 国际化 功能 +* add 新增 lock4j 分布式锁 +* add 增加监控中心 在线日志监控 优化日志文件格式 +* add 适配 docker 部署方式 + +### 后续/进行中计划 + +* 增加 Vue3 前端工程 +* 应用模块 适配 Oracle、PostgreSQL、SQLServer +* 增加 SpringCloud Stream 支持 +* 适配 Apache Kafka、Apache RocketMQ、RabbitMQ +* 适配 ElasticSearch 分布式搜索引擎 +* 适配 Alibaba Canal 分布式数据同步中心 +* 适配 Apache SkyWalking 分布式链路追踪监控中心 +* 适配 ELK 分布式日志中心 +* 适配 Prometheus、Grafana 分布式全方位数据大屏监控 diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/elk.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/elk.md new file mode 100644 index 00000000..58c2eda8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/elk.md @@ -0,0 +1,37 @@ +# ELK搭建 +- - - +# 环境搭建 + +项目内置 `ELK` 的 `docker-compose` 编排 可查看 `/docker/docker-compose.yml` 文件下方扩展编排 + +**注意: `/docker/elk/elasticsearch/` 目录下所有文件夹 均需要写权限** + +`chmod 777 /docker/elk/elasticsearch/data`
+`chmod 777 /docker/elk/elasticsearch/logs`
+`chmod 777 /docker/elk/elasticsearch/plugins`
+**注意: es插件需要解压后放入 `plugins` 目录** + +# 运行命令 + +```shell +docker-compose up -d elasticsearch kibana logstash +``` + +# 参考文章 +[docker-compose 搭建 ELK 7.X 并整合 SpringBoot](https://lionli.blog.csdn.net/article/details/125743132) + +# 项目内配置 + +服务引入依赖项 + +```xml + + + com.ruoyi + ruoyi-common-logstash + +``` + +更改主 `pom` 文件 `logstash.address` 地址
+ +![输入图片说明](https://foruda.gitee.com/images/1678981534923588112/ba6cb5b7_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/es.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/es.md new file mode 100644 index 00000000..65d9c4d4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/es.md @@ -0,0 +1,26 @@ +# ES搜索引擎 +- - - +## 环境搭建(如果已经搭建了ELK则跳过) + +项目内置 `ELK` 的 `docker-compose` 编排 可查看 `/docker/docker-compose.yml` 文件下方扩展编排 + +**注意: `/docker/elk/elasticsearch/` 目录下所有文件夹 均需要写权限** + +`chmod 777 /docker/elk/elasticsearch/data`
+`chmod 777 /docker/elk/elasticsearch/logs`
+`chmod 777 /docker/elk/elasticsearch/plugins`
+**注意: es插件需要解压后放入 `plugins` 目录** + +## 运行命令 + +```shell +docker-compose up -d elasticsearch +``` + +## Easy-ES 文档 +[Easy-ES 文档](https://www.easy-es.cn/) + +## 用法 + +基本配置和用法可参考 `ruoyi-demo` 模块 更多高级用法请参考 Easy-ES 文档
+![输入图片说明](https://foruda.gitee.com/images/1660030085169129908/屏幕截图.png "屏幕截图.png") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/kafka.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/kafka.md new file mode 100644 index 00000000..78521431 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/kafka.md @@ -0,0 +1,9 @@ +# Kafka搭建 +- - - +## 环境搭建 +参考文章: [docker-compose 安装 Kafka 3.X 附带可视化界面](https://lionli.blog.csdn.net/article/details/125855550) + +## 用法参考 +参考 `ruoyi-stream-mq` 模块内的测试案例 + +![输入图片说明](https://foruda.gitee.com/images/1660031528265343174/屏幕截图.png "屏幕截图.png") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/maxkey.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/maxkey.md new file mode 100644 index 00000000..4b4b4305 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/maxkey.md @@ -0,0 +1,20 @@ +# 对接 MaxKey 单点登录 +- - - + +# 安装 MaxKey 应用服务 + +参考 MaxKey 官方文档安装 [MaxKey安装部署](http://www.maxkey.top/doc/docs/intro/) + +# 配置应用 OAuth2.0 认证注册 + +![输入图片说明](https://foruda.gitee.com/images/1693377802128677240/0927270a_1766278.png "屏幕截图") + +# 配置后端服务 + +找到 `Nacos` 内的 `ruoyi-auth.yml` 配置文件 + +修改 `maxkey` 对应的 `client-id` 与 `client-secret` + +![输入图片说明](https://foruda.gitee.com/images/1693378118762354596/2f02c8a3_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1693378168538263792/24476d2a_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/nacos.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/nacos.md new file mode 100644 index 00000000..18e6aefb --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/nacos.md @@ -0,0 +1,13 @@ +# Nacos集群搭建 +- - - +## 集群搭建两种方式 +### 文件寻址集群 +[【RuoYi-Cloud-Plus】学习笔记 02 - Nacos(二)寻址机制之文件寻址分析](https://blog.csdn.net/Michelle_Zhong/article/details/127423521) + +### 地址服务器寻址集群(推荐) +[【RuoYi-Cloud-Plus】学习笔记 03 - Nacos(三)使用 Nginx 实现地址服务器寻址及其原理分析](https://blog.csdn.net/Michelle_Zhong/article/details/127474238) + +## 集群路由代理设置 +[【RuoYi-Cloud-Plus】学习笔记 04 - Nacos(四)使用 Nginx 简单实现 Nacos 集群负载均衡](https://blog.csdn.net/Michelle_Zhong/article/details/127486350) + +设置好代理之后 跟单机用法一致 后端nacos地址写代理 `ip:端口` 即可 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/prometheus_grafana.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/prometheus_grafana.md new file mode 100644 index 00000000..2df4870b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/prometheus_grafana.md @@ -0,0 +1,45 @@ +# Prometheus+Grafana搭建 +- - - +## 基础搭建 + +参考文章: https://lionli.blog.csdn.net/article/details/127959009 + +## 框架内扩展 + +框架已经包含了 docker-compose 编排 执行如下命令启动容器即可 + +```shell +docker-compose up -d prometheus grafana +``` + +## 应用配置 + +各个服务引入 `ruoyi-common-prometheus` 模块 + +![输入图片说明](https://foruda.gitee.com/images/1668998415863943539/413dc560_1766278.png "屏幕截图") + +修改 `prometheus.yml` 配置采集数据源 + +![输入图片说明](https://foruda.gitee.com/images/1668998433756761442/bf31c212_1766278.png "屏幕截图") + +修改 `Nacos` 地址 与 `SpringBoot-Admin` 监控地址 用于数据采集
+如都为本地应用则无需更改 + +![输入图片说明](https://foruda.gitee.com/images/1668998317973042740/2d3590ec_1766278.png "屏幕截图") + +## 导入框架特制模板 +**注意: 此处数据源名称必须与图片保持一致 不然会和模板对应不上导致无法读取数据**
+![输入图片说明](https://foruda.gitee.com/images/1669866309495145064/1de987ce_1766278.png "屏幕截图") + +> 找到框架内的特制模板json文件 在grafana点击上传json文件 导入模板
+ +![输入图片说明](https://foruda.gitee.com/images/1668998149634542527/f0881c8e_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1668998179391197847/b1d3a630_1766278.png "屏幕截图") + +## 选择查看监控 + +点击右侧菜单浏览 选择想要查看的监控即可 + +![输入图片说明](https://foruda.gitee.com/images/1668998515814170229/817ac8b0_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1668998567335384306/acdf2833_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1668998616894681785/ac27538b_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rabbitmq.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rabbitmq.md new file mode 100644 index 00000000..75e0187c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rabbitmq.md @@ -0,0 +1,10 @@ +# RabbitMQ搭建 +- - - +## 环境搭建 + +参考文章: [docker-compose 安装 RabbitMQ 3.X 附带延迟队列插件](https://lionli.blog.csdn.net/article/details/125855177) + +## 用法参考 +参考 `ruoyi-stream-mq` 模块内的测试案例 + +![输入图片说明](https://foruda.gitee.com/images/1660031371503504748/屏幕截图.png "屏幕截图.png") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rocketmq.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rocketmq.md new file mode 100644 index 00000000..98d50bb7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/rocketmq.md @@ -0,0 +1,9 @@ +# RocketMQ搭建 +- - - +## 环境搭建 +参考文章: [docker-compose 安装 RocketMQ 4.9.X (apache官方镜像) namesrv broker 与可视化控制台 console](https://lionli.blog.csdn.net/article/details/125798865) + +## 用法参考 +参考 `ruoyi-stream-mq` 模块内的测试案例 + +![输入图片说明](https://foruda.gitee.com/images/1660031496623275622/屏幕截图.png "屏幕截图.png") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/shardingproxy.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/shardingproxy.md new file mode 100644 index 00000000..ebe461bc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/shardingproxy.md @@ -0,0 +1,75 @@ +# Sharding-Proxy搭建分库分表 +- - - + +# 如何使用 + +查看 `ruoyi-demo` 服务 `TestShardingController` + +![输入图片说明](https://foruda.gitee.com/images/1688014028842337522/cd26026a_1766278.png "屏幕截图") + +## 首先在 MySQL 创建两个库 + +创建两个库 `data-center_0` `data-center_1` 分别执行如下SQL + +```sql +CREATE TABLE `t_order_0` ( + `order_id` bigint(20) UNSIGNED NOT NULL COMMENT '主键ID', + `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户ID', + `total_money` int(10) UNSIGNED NOT NULL COMMENT '订单总金额', + PRIMARY KEY (`order_id`), + KEY `idx_user_id` (`user_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单总表'; + +CREATE TABLE `t_order_1` ( + `order_id` bigint(20) UNSIGNED NOT NULL COMMENT '主键ID', + `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户ID', + `total_money` int(10) UNSIGNED NOT NULL COMMENT '订单总金额', + PRIMARY KEY (`order_id`), + KEY `idx_user_id` (`user_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单总表'; + +CREATE TABLE `t_order_item_0` ( + `order_item_id` bigint(20) UNSIGNED NOT NULL COMMENT '子订单ID', + `order_id` bigint(20) UNSIGNED NOT NULL COMMENT '主键ID', + `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户ID', + `money` int(10) UNSIGNED NOT NULL COMMENT '子订单金额', + PRIMARY KEY (`order_item_id`), + KEY `idx_order_id` (`order_id`) USING BTREE, + KEY `idx_user_id` (`user_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单子表'; + +CREATE TABLE `t_order_item_1` ( + `order_item_id` bigint(20) UNSIGNED NOT NULL COMMENT '子订单ID', + `order_id` bigint(20) UNSIGNED NOT NULL COMMENT '主键ID', + `user_id` bigint(20) UNSIGNED NOT NULL COMMENT '用户ID', + `money` int(10) UNSIGNED NOT NULL COMMENT '子订单金额', + PRIMARY KEY (`order_item_id`), + KEY `idx_order_id` (`order_id`) USING BTREE, + KEY `idx_user_id` (`user_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单子表'; + +``` + +## 然后更改配置文件 + +更改 `config-sharding.yaml` 配置文件内的数据库连接地址与用户名密码 + +## 服务搭建 + +参考部署文档上传 docker 文件夹 内部包含 `shardingproxy` 配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1688013921062151295/89652dda_1766278.png "屏幕截图") + +框架已经包含了 docker-compose 编排 执行如下命令启动容器即可 + +```shell +docker-compose up -d shardingproxy +``` + +## 最后运行 demo + +运行 demo 提供的 controller 代码查看数据库内数据即可 + +## 用法参考视频(略有不同 理性观看) + +用法参考视频: https://www.bilibili.com/video/BV1XN411A7Tv/ diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/skywalking.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/skywalking.md new file mode 100644 index 00000000..6ad0aecc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/extend-function/skywalking.md @@ -0,0 +1,41 @@ +# SkyWalking搭建与集成 +- - - +## 服务搭建 +参考文章: [SpringBoot 整合 SkyWalking 8.X (包含 Logback 日志采集)](https://lionli.blog.csdn.net/article/details/127656534) + +框架已经包含了 docker-compose 编排 执行如下命令启动容器即可 + +```shell +docker-compose up -d elasticsearch sky-oap sky-ui +``` + +### 本地开发使用 +参考上方文章 + +### docker部署使用 +上传探针到服务器 `/docker/skywalking/agent` 目录
+**不要使用网上下载的 请使用框架自带的 内含一些官网没有的插件**
+![输入图片说明](https://foruda.gitee.com/images/1667453098143152651/f1b4f492_1766278.png "屏幕截图") + +在对应服务的`dockerfile`内 打开 `skywalking` 相关参数注释
+![输入图片说明](https://foruda.gitee.com/images/1667452514896786032/f4322fb9_1766278.png "屏幕截图") + +服务编排增加探针路径映射
+![输入图片说明](https://foruda.gitee.com/images/1667453276389844864/7e139aa9_1766278.png "屏幕截图") + + +### 对接日志推送(不推荐 建议使用ELK收集日志) + +框架已经封装好了对应的依赖和配置 在服务内添加如下依赖 + +```xml + + + com.ruoyi + ruoyi-common-skylog + +``` + +在 `logback.xml` 日志配置文件内引入 `skylog` 配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1667452697748002725/a18212cd_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/architecture_diagram.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/architecture_diagram.md new file mode 100644 index 00000000..05db07a3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/architecture_diagram.md @@ -0,0 +1,3 @@ +# 软件架构图 +- - - +![输入图片说明](https://foruda.gitee.com/images/1722569321458793955/8672b1fc_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/collaboration.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/collaboration.md new file mode 100644 index 00000000..569aed75 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/collaboration.md @@ -0,0 +1,27 @@ +# 多团队开发 +- - - +## 功能介绍 + +> 多人员/团队开发往往会出现 调试程序 被负载均衡到别人那里 自己抓不到请求等问题
+> 正确团队开发模式 `测试机一台` 公共服务都放到测试机上
+> 本地开发人员 需启动 `ruoyi-gateway` 与 其他 调试的业务模块
+> 将所有服务都统一指向同一个 Nacos 服务
+> 前端连接本机 `ruoyi-gateway` 网关调试程序
+ +框架提供了 `ruoyi-common-loadbalancer` 多团队 负载均衡模块 可以将网关的请求锁定到与网关相同的IP服务 + +需要在 `ruoyi-gateway` `ruoyi-auth` `ruoyi-modules` 引入 `ruoyi-common-loadbalancer` 模块 + +![输入图片说明](https://foruda.gitee.com/images/1678980590168990366/afa2fdf6_1766278.png "屏幕截图") + +启动前端访问本机 `ruoyi-gateway` 网关在请求转发 和 `dubbo` 进行 RPC 调用时
+会获取与本机IP地址相同的服务优先调用(如未找到 会随机返回) + +# 重点说明 + +请检查本机是否有虚机网卡IP 如有多网卡获取IP地址会不准确 + +可使用如下代码检查本机IP是否正常 +```java +InetAddress.getLocalHost().getHostAddress() +``` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/doc.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/doc.md new file mode 100644 index 00000000..422aeb9e --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/doc.md @@ -0,0 +1,88 @@ +# 接口文档 +- - - +## 版本 >= `1.2.0` +## 说明 +由于 `springfox` 与 `knife4j` 均停止维护 bug众多
+故从 `1.2.0` 开始 迁移到 `springdoc` 框架
+基于 `javadoc` 无注解零入侵生成规范的 `openapi` 结构体
+由于框架自带文档UI功能单一扩展性差 故移除自带UI 建议使用外置文档工具 + +## 文档工具使用 +由于框架采用 `openapi` 行业规范 故市面上大部分的框架均支持 可自行选择
+例如: `apifox` `apipost` `postman` `torna` `knife4j` 等 根据对应工具的文档接入即可 + +## Swagger升级SpringDoc指南 + +常见功能如下 其他功能自行挖掘
+**注意: `javadoc` 只能替换基础功能 特殊功能还需要使用注解实现** + +| swagger | springdoc | javadoc | +|----------------------------------|---------------------------------|--------------------| +| @Api(name = "xxx") | @Tag(name = "xxx") | java类注释第一行 | +| @Api(description= "xxx") | @Tag(description= "xxx") | java类注释 | +| @ApiOperation | @Operation | java方法注释 | +| @ApiIgnore | @Hidden | 无 | +| @ApiParam | @Parameter | java方法@param参数注释 | +| @ApiImplicitParam | @Parameter | java方法@param参数注释 | +| @ApiImplicitParams | @Parameters | 多个@param参数注释 | +| @ApiModel | @Schema | java实体类注释 | +| @ApiModelProperty | @Schema | java属性注释 | +| @ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 无 | +| @ApiResponse | @ApiResponse | java方法@return返回值注释 | + +# 建议使用 `Apifox`(常见问题有其他对接方式) + +官网连接: [https://www.apifox.cn/](https://www.apifox.cn/)
+视频教程: [springdoc与apifox配合使用](https://www.bilibili.com/video/BV1mr4y1j75M?p=8&vd_source=8f52c77be3233dbdd1c5e332d4d45bfb) + +![输入图片说明](https://foruda.gitee.com/images/1678976476639902970/f1617b40_1766278.png "屏幕截图") + +支持 文档编写 接口调试 Mock 接口压测 自动化测试 等一系列功能 + +### 接入框架 + +> 1.下载或使用web在线版 创建一个自己的项目
+ +![输入图片说明](https://foruda.gitee.com/images/1678976502850663851/7bbd8728_1766278.png "屏幕截图") + +> 2.进入项目 选择项目设置 找到自动同步
+ +![输入图片说明](https://foruda.gitee.com/images/1678976508918240326/6a4a61a8_1766278.png "屏幕截图") + +> 3.根据项目内所有文档组完成所有数据源创建(拉取后端`openapi`结构体)
+数据源URL格式 `http://网关ip:端口/服务路径/v3/api-docs`
+项目内所需:
+`http://localhost:8080/demo/v3/api-docs` 演示服务
+`http://localhost:8080/auth/v3/api-docs` 认证服务
+`http://localhost:8080/resource/v3/api-docs` 资源服务
+`http://localhost:8080/system/v3/api-docs` 系统服务
+`http://localhost:8080/code/v3/api-docs` 代码生成服务
+ +![输入图片说明](https://foruda.gitee.com/images/1678980352012289965/24e0e4da_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678980368645148754/62308680_1766278.png "屏幕截图") + +> 4.选择 接口管理 项目概览 点击立即导入 并等待导入完成
+后续会根据策略每3个小时自动导入一次
+每次重新进入apifox也会自动同步一次
+后端有改动也可以手动点击导入
+ +![输入图片说明](https://foruda.gitee.com/images/1678980393851604773/a0c657d3_1766278.png "屏幕截图") + +> 5.(注意版本号)设置鉴权 选择接口管理 项目概览 找到Auth 按照如下配置
+ +**版本号: >= 2.X** + +![输入图片说明](https://foruda.gitee.com/images/1690966897370710566/6a688aea_1766278.png "屏幕截图") + +**版本号: 1.X** + +![输入图片说明](https://foruda.gitee.com/images/1678980398409729963/db4502a0_1766278.png "屏幕截图") + +> key对应项目配置 默认为 `Authorization`
+ +![输入图片说明](https://foruda.gitee.com/images/1678976544342001474/c2ff85d3_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678976549237304743/bcdfadda_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/i18n.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/i18n.md new file mode 100644 index 00000000..304d3e04 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/i18n.md @@ -0,0 +1,31 @@ +# 国际化方案 +- - - +* 前端国际化参考 [ruoyi前端国际化文档](http://doc.ruoyi.vip/ruoyi-vue/document/htsc.html#前端国际化流程)
+* 参考 `demo` 模块 `TestI18nController` 国际化演示案例 + 在 `Header` 请求头 增加上下文语言参数 `content-language` 参数需与国际化配置文件后缀对应 + 如 `zh_CN` `en_US` 等
+ +![输入图片说明](https://foruda.gitee.com/images/1678976722892396585/60917594_1766278.png "屏幕截图") + +## 获取 `code` 对应国际化内容 + +![输入图片说明](https://foruda.gitee.com/images/1678976728533100954/0ab8e36a_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976733019209506/a16574d6_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976738409745057/a073b425_1766278.png "屏幕截图") + +## 使用 `Validator` 框架校验 `controller` 参数返回国际化 + +`controller` 校验接口参数 需要在类增加 `@Validated` 注解
+![输入图片说明](https://foruda.gitee.com/images/1678976741834729507/6c19b9cc_1766278.png "屏幕截图")
+参数对应校验注解 使用 `{code}` 形式标注使用国际化处理
+![输入图片说明](https://foruda.gitee.com/images/1678976746093285542/ad0989db_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976750822808564/56bd60d7_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976754755107198/b89bf173_1766278.png "屏幕截图") + +## 使用 `Validator` 框架校验 `Bean` 返回国际化 + +`Bean` 校验需要在接口校验 `Bean` 参数使用 `@Validated` 注解
+![输入图片说明](https://foruda.gitee.com/images/1678976761015767874/729da3bc_1766278.png "屏幕截图")
+`Bean` 内属性校验注解 使用 `{code}` 形式标注使用国际化处理
+![输入图片说明](https://foruda.gitee.com/images/1678976765122587920/0b1027af_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976769965314387/0c416ede_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/inner_authentication.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/inner_authentication.md new file mode 100644 index 00000000..c90d2c61 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/inner_authentication.md @@ -0,0 +1,19 @@ +# 内网鉴权 +- - - +## 功能介绍 + +此功能用于防止外部请求访问内部服务应用
+在请求经过 `gateway网关` 会生成一个 `id-token` 携带到后续服务进行校验
+若未经过 `gateway网关` 调用内网服务 会出现 `id-token无效` 异常
+有效防止非法请求直接访问内网服务
+ +## 开启/关闭内网鉴权 + +更改 `application-common.yml` 配置文件的 `sa-token.check-id-token` 配置即可 + +![输入图片说明](https://foruda.gitee.com/images/1678980608778275681/9a2c1054_1766278.png "屏幕截图") + +## 放行内网鉴权 +进入 `ruoyi-common-security` 模块找到 `SecurityConfiguration` 类 增加排除路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1678980612657326393/cea32a8c_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/new_module.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/new_module.md new file mode 100644 index 00000000..0d75525b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/new_module.md @@ -0,0 +1,39 @@ +# 创建新服务 +- - - +### 最简单的方式 +> 找个配置好的 例如 `ruoyi-system` 直接copy一份 + +> 将 `pom` 名称改掉
+ +![输入图片说明](https://foruda.gitee.com/images/1678980168782983123/c717e9ba_1766278.png "屏幕截图") + +> 服务启动类 名称改掉
+ +![输入图片说明](https://foruda.gitee.com/images/1678980179829877203/f89d5c18_1766278.png "屏幕截图") + +> `application.yml` 配置服务应用名 改掉
+ +![输入图片说明](https://foruda.gitee.com/images/1678980184047648028/e4c6c6cc_1766278.png "屏幕截图") + +> `nacos` 新建一份新的 对应新模块名称的 配置文件
+![输入图片说明](https://foruda.gitee.com/images/1678980188806372269/cfd9731a_1766278.png "屏幕截图") + +更改 `nacos` 上的 `ruoyi-gateway.yml` 增加新服务路由
+新服务访问路径 `网关ip:端口/服务路径/controller路径/接口路径`
+例子: `http://localhost:8080/system/user/list`
+ +![输入图片说明](https://foruda.gitee.com/images/1666861595048863422/9e9755b3_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1666861629037264535/bdfd5484_1766278.png "屏幕截图") + +### 注意事项 +如果是两个不同包名的模块 需要修改如下配置 + +![输入图片说明](https://foruda.gitee.com/images/1719813861680271619/82435586_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1692006501957936219/059f8526_1766278.png "屏幕截图") + +如果新服务需要使用 `seata` 分布式事务
+需要在 `nacos` 上的 `seata-server.properties` 文件内增加服务组 + +![输入图片说明](https://foruda.gitee.com/images/1692006825427360840/5b9e410c_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_package_name.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_package_name.md new file mode 100644 index 00000000..2dcab848 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_package_name.md @@ -0,0 +1,33 @@ +# 关于修改包名 +- - - + +**注意: 老包名为 com.ruoyi** + +## 1.随便找个地方新建 org.dromara 包 +![输入图片说明](https://foruda.gitee.com/images/1708491220807198688/b95c0c34_1766278.png "屏幕截图") + +## 2.在包上右键选择 refactor -> rename 选择 All Directories +![输入图片说明](https://foruda.gitee.com/images/1683276891079076405/79808b22_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1708491697128844860/1e87ad39_1766278.png "屏幕截图") + +**因为dromara组织下有很多依赖导致idea无法识别完整包名** +
+![输入图片说明](https://foruda.gitee.com/images/1708490576909691001/692e5b37_1766278.png "屏幕截图") + +**需要先将dromara修改为 例如: ruoyi 然后重复上述步骤 这样就可以整包修改了** +
+![输入图片说明](https://foruda.gitee.com/images/1708490906933084793/ff104cd7_1766278.png "屏幕截图") + +## 3.使用IDEA全局替换 org.dromara 替换为 com.xxx + +![输入图片说明](https://foruda.gitee.com/images/1708491055347995519/dedda0d1_1766278.png "屏幕截图") + +**注意: 由于dromara组织下项目很多 非本框架的依赖模块 请勿修改 例如上图中的 org.dromara.sms4j** + +## 4.如有需要 将所有模块名逐一修改即可 + +## 5.修改完成后需查看所有common包下模块spi文件是否修改正确 + +**老版本idea或者未按照教程修改包名可能导致文件丢包问题** + +![输入图片说明](https://foruda.gitee.com/images/1708491365841192006/8bc337c2_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_url.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_url.md new file mode 100644 index 00000000..d36012f5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/association/update_url.md @@ -0,0 +1,25 @@ +# 修改应用路径 +- - - +# 修改访问后端接口路径 + +更改 前端环境配置文件 `VITE_APP_BASE_API` 代理路径 + +![输入图片说明](https://foruda.gitee.com/images/1661824572484410642/14265f05_1766278.png "屏幕截图")
+ +![输入图片说明](https://foruda.gitee.com/images/1724317552931269967/f7515655_1766278.png "屏幕截图") + +`prod` 生产环境需修改 `nginx.conf` 后端代理路径(上述配置文件也要改) + +![输入图片说明](https://foruda.gitee.com/images/1678980501204821424/d3340308_1766278.png "屏幕截图") + +# 修改前端页面访问路径 +修改对应环境的 `.env.环境` 文件内的 `VITE_APP_CONTEXT_PATH` 应用访问路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1661824572484410642/14265f05_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1724317049535973756/0a2cc43b_1766278.png "屏幕截图") + +生产环境 `nginx.conf` 与之对应修改即可
+**注意: 文件真实目录为 `/usr/share/nginx/html/admin/index.html` 此功能一般为多项目部署需要 故会增加一层目录 如不需要可以自行修改**
+ +![输入图片说明](https://foruda.gitee.com/images/1678976662194341301/2720b7e9_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/client.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/client.md new file mode 100644 index 00000000..66ae32a9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/client.md @@ -0,0 +1,85 @@ +# 客户端管理功能 +- - - +## 版本 >= 2.X + +## 客户端管理页面 + +![输入图片说明](https://foruda.gitee.com/images/1690961819029076660/c44374ac_4959041.png "屏幕截图") + +### 客户端管理字段说明 +| 字段名称 | 取值说明 | 注意事项 | +|----------------|----------------------------|--------------------------------| +| 客户端id | 由后端生成,用于前端登录校验以及接口数据加密 | 无法修改,不要删除默认数据,否则会报错 | +| 客户端key | 前端自定义 | 无法修改,不要删除默认数据,否则会报错 | +| 客户端秘钥 | 前端自定义 | 无法修改,不要删除默认数据,否则会报错 | +| 授权类型 | 密码认证、短信认证、邮件认证、小程序认证、第三方认证 | 根据授权类型判断当前客户端是否支持该登录方式 | +| 设备类型 | PC端、APP端 | | +| Token活跃超时时间 | 自定义 | 指定时间无操作则过期(单位:秒),默认30分钟(1800秒) | +| Token固定超时时间 | 自定义 | 指定时间必定过期(单位:秒),默认七天(604800秒) | + +### 前后端使用新的客户端id + +步骤如下: +1. 前端管理页面生成新的客户端id。 +2. 将新的客户端id复制到前端配置文件。 + +![输入图片说明](https://foruda.gitee.com/images/1690962894318847386/133d2f90_4959041.png "屏幕截图") + +## 新增自定义客户端 + +### 步骤一:新增客户端数据(例如增加小程序端) + +![输入图片说明](https://foruda.gitee.com/images/1690965463070099188/baeb4441_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1690965508836621042/df06248f_4959041.png "屏幕截图") + +### 步骤二:配置前端请求头信息 + +需要在全局请求头 header 中增加 cientid
+确保客户端所有请求都携带此id 可参考项目 `request.ts` + +![输入图片说明](https://foruda.gitee.com/images/1690965768235114596/980b88d2_4959041.png "屏幕截图") + +`VITE_APP_CLIENT_ID` 即配置文件中的客户端id。 + +**重点:不同客户端登录获取到的token不同与其他端不互通(例如: app登录获取到的token无法用于pc端接口查询)** + +## 新增自定义登录方式授权类型 + +**重点说明: 不要单独增加登录接口 系统全局统一只有一个登录接口 只需增加不同的鉴权方式即可** + +如何调试使用登录看这里 -> [关于登录调试步骤](/questions/login_step.md) + +### 步骤一:新增字典数据 + +![输入图片说明](https://foruda.gitee.com/images/1690968849418013624/3b28417e_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1690968865819397010/64529fad_4959041.png "屏幕截图") + +### 步骤二:新增/修改客户端数据 + +### 步骤三:后端新增认证策略 + +新增策略实现类实现 `IAuthStrategy` 接口。
+ +![输入图片说明](https://foruda.gitee.com/images/1690972828588111954/7614a4c5_4959041.png "屏幕截图") + +参照已有策略实现类实现自定义参数校验登录方法逻辑。
+ +![输入图片说明](https://foruda.gitee.com/images/1718951146945578143/789c80e4_1766278.png "屏幕截图") + +**注意修改 `@Service` 名称保证规范性** + +![输入图片说明](https://foruda.gitee.com/images/1718951179571300385/8db730b9_1766278.png "屏幕截图") + +`LoginBody` 校验参数(可自定义)
+ +![输入图片说明](https://foruda.gitee.com/images/1718951237123374392/f7840db2_1766278.png "屏幕截图") + +例如 扩展小程序登录参数 只需要继承 `LoginBody
+ +![输入图片说明](https://foruda.gitee.com/images/1718951283931895761/e6348be5_1766278.png "屏幕截图")` + +校验分组(可自定义)
+ +![输入图片说明](https://foruda.gitee.com/images/1718951343601334215/8ef404b4_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/code_generate.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/code_generate.md new file mode 100644 index 00000000..742ef939 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/code_generate.md @@ -0,0 +1,86 @@ +# 代码生成 +- - - +## 功能介绍 + +### 数据源配置 + +![输入图片说明](https://foruda.gitee.com/images/1678976867341325193/a2be0608_1766278.png "屏幕截图") + +**项目适配多种类型数据库 可以在代码生成页面切换**
+ +> 填写对应的数据源名称 点击搜索按钮 即可切换到对应的数据源
+ +![输入图片说明](https://foruda.gitee.com/images/1678976876081856486/4ef4841c_1766278.png "屏幕截图") + +**>= 2.2.1版本 项目支持100+种数据库适配 在代码生成模块增加对应的数据库依赖即可**
+ +![输入图片说明](https://foruda.gitee.com/images/1722396530340741054/3914eb72_1766278.png "屏幕截图") + + +### 导入数据表 + +> 点击导入按钮 会加载系统数据库所有的表
+ +![输入图片说明](https://foruda.gitee.com/images/1678976880393939803/3ecf1dcc_1766278.png "屏幕截图") + +> 选择需要的表 点击确定即可
+ +![输入图片说明](https://foruda.gitee.com/images/1678976885370716109/4834faa5_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976891856866728/853420d9_1766278.png "屏幕截图") + +### 编辑表生成结构 + +> 点击表对应的编辑按钮
+ +![输入图片说明](https://foruda.gitee.com/images/1678976899111822310/aeaa33f9_1766278.png "屏幕截图") + +> 更改要生成表的数据
+ +![输入图片说明](https://foruda.gitee.com/images/1678976903345795925/4326f6ee_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976908897387614/4cdf939b_1766278.png "屏幕截图") + +### 生成条件影响 + +![输入图片说明](https://foruda.gitee.com/images/1678976913809284051/24da09b0_1766278.png "屏幕截图") + + +* `插入` `编辑` 影响生成 BO 类 与 前端添加编辑页面 是否有该字段 +* `列表` 影响生成 VO 类 与 前端列表页面展示 是否有该字段 +* `查询` 影响 前端页面是否有该字段的搜索框 与 后端代码是否生成对应的查询条件 +* `查询方式` 影响生成查询条件的类型 +* `必填` 影响 BO 类 与 页面是否强制校验 +* `显示类型` 影响生成页面使用何种展示组件 +* `字典类型` 影响页面是否生成与字典的关联 + +### 树表配置 + +> 编辑表生成信息 生成模板为 `树表` 填写对应数据即可
+ +![输入图片说明](https://foruda.gitee.com/images/1678976917918548901/f5886c5c_1766278.png "屏幕截图") + +### 主子表说明 + +框架不支持也不推荐使用主子表
+原因一般业务场景 基本都是一对N表 多表关联场景
+还有一些 主 => 子 <= 主 场景 需求很复杂 很少有单纯主子表场景出现
+另外主子表关联 很容易出现 笛卡尔积 或者数据错乱等问题 需要自行sql调优场景
+所以建议大家都按照 单表生成 自行编写业务逻辑 + +### 预览功能 + +> 配置好生成信息后 可以点击预览按钮
+ +![输入图片说明](https://foruda.gitee.com/images/1678976924411765532/2e9747df_1766278.png "屏幕截图") + +> 系统会根据已经配置好的数据 生成对应的代码预览
+> 可以再此处观察代码的生成结构和数据是否正确等
+ +![输入图片说明](https://foruda.gitee.com/images/1678976945982406065/ca7383bb_1766278.png "屏幕截图") + + +### 代码结构同步 + +> 实际开发中 难免会有表结构更改的需求
+> 这时可以使用 同步功能 点击同步按钮 即可与实时数据库表进行字段同步
+ +![输入图片说明](https://foruda.gitee.com/images/1678976952919156537/3c47c078_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/export.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/export.md new file mode 100644 index 00000000..fa901792 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/export.md @@ -0,0 +1,250 @@ +# 导出功能 + +- - - + +在本框架中引入了 `Easy Excel` 依赖(对 `Apache POI`进行了封装以及扩展),可以对数据进行导出操作(即写 Excel)。 + +[EasyExcel 文档地址](https://easyexcel.opensource.alibaba.com/) + +## 导出功能使用流程说明 + +### 步骤一:定义导出实体对象 + +以框架中 `SysUserExportVo` 为例: + +```Java + /** + * 用户ID + */ + @ExcelProperty(value = "用户序号") + private Long userId; + + // ....................... + + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; +``` + +> 说明:
+> 1. 使用 `@ExcelProperty` 注解标注需要导出的属性。 +> 2. 注解 `@ExcelProperty` 中 `value` 属性代表表格头部标题字段,`converter` 代表使用的转换器,后面会详细说明。 +> 3. 注解 `@ExcelDictFormat` 为自定义注解,与自定义转换器结合使用,同样在后面进行详细说明。 + +### 步骤二:使用导出方法 + +以框架中 `SysUserController#export` 方法为例: + +```Java + /** + * 导出用户列表 + */ + @PostMapping("/export") + public void export(SysUserBo user, HttpServletResponse response) { + // 根据参数查询导出的用户列表数据 + List list = userService.selectUserList(user); + // 将列表转换为导出对象列表 + List listVo = MapstructUtils.convert(list, SysUserExportVo.class); + // 导出方法 + ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response); + } +``` + +> 说明:
+> 使用 `ExcelUtil.exportExcel` 方法完成导出功能,上述 Demo 传入参数分别是:导出对象集合,Excel sheet 表名称,导出对象类型,response。 + +## 框架工具使用说明 + +### 1:字典转换器 + +字典转换器 `ExcelDictConvert` 与自定义注解 `@ExcelDictFormat` 结合使用,标注在需要转换的属性上。 + +使用方式一: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; +``` + +使用方式二: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp="0=男,1=女,2=未知", separator=",") + private String sex; +``` + +`@ExcelDictFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|------------------|--------|-----|-----------------------------------| +| dictType | String | "" | 字典的type值 (如: sys_user_sex) | +| readConverterExp | String | "" | 读取内容转表达式 (如: 0=男,1=女,2=未知) | +| separator | String | "," | 与 readConverterExp 属性结合使用,表达式的分隔符 | + +### 2:枚举转换器 + +字典转换器 `ExcelEnumConvert` 与自定义注解 `@ExcelEnumFormat` 结合使用,标注在需要转换的属性上。 + +使用方式: + +```Java + /** + * 用户类型 + *

+ * 使用ExcelEnumFormat注解需要进行下拉选的部分 + */ + @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) + @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") + private String userStatus; +``` + +`@ExcelEnumFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|-----------|------------|------|------------------------------| +| enumClass | Enum Class | - | 字典枚举类型 | +| codeField | String | code | 字典枚举类中对应的 code 属性名称,默认为 code | +| textField | String | text | 字典枚举类中对应的 text 属性名称,默认为 text | + +### 3:合并单元格 + +`@CellMerge` 注解用于合并相同的列数据,需要结合 `CellMergeStrategy` 策略使用,标注在需要转换的属性上。 + +使用方式: + +步骤一:在属性标注 `@CellMerge` 注解: +```Java + /** + * 部门id + */ + @CellMerge + @ExcelProperty(value = "部门id") + private Long deptId; +``` + +`@CellMerge` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|---------|----------|-----|------------------------------| +| index | int | -1 | 合并列的下标,建议使用默认值 | +| mergeBy | String[] | {} | 合并需要依赖的其他字段名称(基于这个字段内容做合并条件) | + + +步骤二:导出方法开启合并: +```Java + /** + * 导出测试单表列表 + */ + @PostMapping("/export") + public void export(@Validated TestDemoBo bo, HttpServletResponse response) { + List list = testDemoService.queryList(bo); + // 参数 true 表示开启合并单元格策略 + ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, true, response); + } +``` +![输入图片说明](https://foruda.gitee.com/images/1700128921644543994/e8d4704f_1766278.png "屏幕截图") + +### 4:复杂 Excel 导出示例 +`TestExcelController` 提供了几种导出示例,如果需要可以参照相应方法进行导出。 + +#### 4.1:单列表多数据导出(模板导出) + +模板内容: + +![输入图片说明](https://foruda.gitee.com/images/1700124852002972562/d9f57a8c_4959041.png "屏幕截图") + +模板位置:`ruoyi-example/ruoyi-demo/src/main/resources/excel/` + +导出示例代码:参考 demo 模块 `TestExcelController` 模板写法请查看 `EasyExcel` 文档 + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700124885532359879/0d011d05_4959041.png "屏幕截图") + +#### 4.2:多列表多数据导出(模板导出) + +模板内容: + +![输入图片说明](https://foruda.gitee.com/images/1700125025931981176/105dbaaa_4959041.png "屏幕截图") + +模板位置:`ruoyi-example/ruoyi-demo/src/main/resources/excel/` + +导出示例代码:参考 demo 模块 `TestExcelController` 模板写法请查看 `EasyExcel` 文档 + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700125054011300002/71869c1d_4959041.png "屏幕截图") + +#### 4.3:导出下拉框 + +`ExcelDictFormat` 注解指定的字典项默认都会转换成下拉框 + +自定义导出省市区下拉框示例代码:参考 demo 模块 `TestExcelController` + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700125265411678973/7f767719_4959041.png "屏幕截图") + +## Easy Excel 常用注解 + +`Easy Excel` 提供了丰富的注解可以对导出对象进行定制化操作,这里的注解说明针对的是原生注解,自定义注解会结合转换器一起进行说明。 + +| 类型 | 注解名称 | 使用举例 | 说明 | +|-------|-------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| 格式化注解 | @DateTimeFormat | @DateTimeFormat(value=格式化值) | 对字符串进行日期格式化 (参照 `java.text.SimpleDateFormat` 书写即可) | +| 格式化注解 | @NumberFormat | @NumberFormat(value=格式化值, roundingMode=舍入模式) | 对字符串进行数值格式化 (参照 `java.text.DecimalFormat` 书写即可, `roundingMode` 默认 `RoundingMode.HALF_UP`) | +| 样式注解 | @ColumnWidth | @ColumnWidth(value=值) | 设置列宽 | +| 样式注解 | @ContentFontStyle | @ContentFontStyle(color=颜色) | 可以设置字体类型,颜色,粗细,是否斜体,下划线等,具体可查看注解 `@ContentFontStyle` | +| 样式注解 | @ContentLoopMerge | @ContentLoopMerge(eachRow=行值, columnExtend=列值) | 设置循环合并的区域 | +| 样式注解 | @ContentRowHeight | @ContentRowHeight(value=值) | 设置内容行高 | +| 样式注解 | @ContentStyle | - | 设置单元格样式,具体可查看注解 `@ContentStyle` | +| 样式注解 | @HeadFontStyle | @HeadFontStyle(color=颜色) | 设置表头字体格式,类似 `@ContentFontStyle`,具体可查看注解 `@HeadFontStyle` | +| 样式注解 | @HeadRowHeight | @HeadRowHeight(value=值) | 设置表头行高 | +| 样式注解 | @HeadStyle | - | 设置表头样式,具体可查看注解 `@HeadStyle` | +| 样式注解 | @OnceAbsoluteMerge | @OnceAbsoluteMerge(firstRowIndex=开始行下标, lastRowIndex=结束行下标, firstColumnIndex=开始列下标, lastColumnIndex=结束列下标) | 根据设置值合并单元格 | +| 属性注解 | @ExcelIgnore | @ExcelIgnore | 导出忽略该字段 | +| 属性注解 | @ExcelIgnoreUnannotated | @ExcelIgnoreUnannotated | 默认不管加不加 `@ExcelProperty` 的注解的所有字段都会参与读写,加了 `@ExcelIgnoreUnannotated` 注解以后,不加 `@ExcelProperty` 注解的字段就不会参与 | +| 属性注解 | @ExcelProperty | @ExcelProperty(value=值, order=排序值, index=下标, converter=转换器) | 默认按照对象属性顺序导出,如果设置了 `order` 以及 `index`,优先级 `index` > `order` > 默认;converter 可以自定义 | + +## 扩展说明 + +### 自定义转换器实现 + +由于业务需要,原生注解不一定能够符合需要,因而衍生出了自定义转换器。能够实现定制化的内容转换需要。 +以下以框架中的字典转换器 `ExcelDictConvert` 为例进行说明。 + +字典转换器 `ExcelDictConvert`,字典转换器使用了自定义注解 `@ExcelDictFormat` 配合使用。 + +_**注:自定义转换器并非一定需要自定义注解,也可以针对已有的注解进行自定义转换实现。**_ + +#### 实现方式 + +自定义转换器需要实现 `com.alibaba.excel.converters.Converter` 接口,实现接口中的方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700104014304819918/33eb0c42_4959041.png "屏幕截图") + +转换方法 `ExcelDictConvert#convertToExcelData` : + +![输入图片说明](https://foruda.gitee.com/images/1700104426131801297/72931ef0_4959041.png "屏幕截图") + +## 更多功能 + +更多导出功能使用可以参照 `Easy Excel` [官方文档](https://easyexcel.opensource.alibaba.com/docs/current/api/write)。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/import.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/import.md new file mode 100644 index 00000000..f1bbca7d --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/import.md @@ -0,0 +1,202 @@ +# 导入功能 +- - - + +在本框架中引入了 `Easy Excel` 依赖(对 `Apache POI`进行了封装以及扩展),可以对数据进行导入操作(即读 Excel)。 + +## 导入功能使用流程说明 + +### 步骤一:定义导入实体对象 + +以框架中 `SysUserImportVo` 为例: + +```java + /** + * 用户ID + */ + @ExcelProperty(value = "用户序号") + private Long userId; + + // ....................... + + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; +``` + +> 说明:
+> 1. 使用 `@ExcelProperty` 注解标注需要导入的属性。 +> 2. 注解 `@ExcelProperty` 中 `value` 属性代表表格头部标题字段,`converter` 代表使用的转换器,后面会详细说明。 +> 3. 注解 `@ExcelDictFormat` 为自定义注解,与自定义转换器结合使用,同样在后面进行详细说明。 +> 4. 对象禁止使用链式注解 `@Accessors(chain = true)`,会找不到set方法。 + +### 步骤二:使用导入方法 + +以框架中 `SysUserController#importData` 方法为例: + +```Java + /** + * 导入数据 + * + * @param file 导入文件 + * @param updateSupport 是否更新已存在数据 + */ + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @SaCheckPermission("system:user:import") + @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception { + // 导入方法 + ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport)); + return R.ok(result.getAnalysis()); + } +``` +> 说明:
+> 使用 `ExcelUtil.importExcel` 方法完成导出功能,上述 Demo 传入参数分别是:导入文件流,导入对象类型,导入监听器 `SysUserImportListener`。 + +## 框架工具使用说明 + +### 1:字典转换器 + +字典转换器 `ExcelDictConvert` 与自定义注解 `@ExcelDictFormat` 结合使用,标注在需要转换的属性上。 + +使用方式一: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; +``` + +使用方式二: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp="0=男,1=女,2=未知", separator=",") + private String sex; +``` + +`@ExcelDictFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|------------------|--------|-----|-----------------------------------| +| dictType | String | "" | 字典的type值 (如: sys_user_sex) | +| readConverterExp | String | "" | 读取内容转表达式 (如: 0=男,1=女,2=未知) | +| separator | String | "," | 与 readConverterExp 属性结合使用,表达式的分隔符 | + +### 2:枚举转换器 + +字典转换器 `ExcelEnumConvert` 与自定义注解 `@ExcelEnumFormat` 结合使用,标注在需要转换的属性上。 + +使用方式: + +```Java + /** + * 用户类型 + *

+ * 使用ExcelEnumFormat注解需要进行下拉选的部分 + */ + @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) + @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") + private String userStatus; +``` + +`@ExcelEnumFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|-----------|------------|------|------------------------------| +| enumClass | Enum Class | - | 字典枚举类型 | +| codeField | String | code | 字典枚举类中对应的 code 属性名称,默认为 code | +| textField | String | text | 字典枚举类中对应的 text 属性名称,默认为 text | + + +### 3:导入监听器 + +#### 3.1:ExcelListener 监听器接口 + +`ExcelListener` 扩展了 `ReadListener` 接口,增加了获取结果方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700181723794469524/99bf83c9_4959041.png "屏幕截图") + +#### 3.2:DefaultExcelListener 默认监听器 + +`DefaultExcelListener` 默认监听器在读 Excel 时调用,主要对数据进行校验、解析、异常处理、返回结果等。导入操作时如果没有特别指定则使用该监听器。 + +#### 3.3:SysUserImportListener 用户导入监听器 + +`SysUserImportListener` 用户导入监听器是在用户导入时调用的监听器。 + +该监听器重写了 `invoke` 反射接口,对导入的用户数据进行了校验;重写了 `getExcelResult` 获取结果接口,返回结果数据。 + +#### 3.4:ExportDemoListener 带下拉框的导入监听器 + +`ExportDemoListener` 是对带有下拉框的 Excel 进行处理的导入监听器。 + +## Easy Excel 常用注解 + +`Easy Excel` 提供了丰富的注解可以对导出对象进行定制化操作,这里的注解说明针对的是原生注解。 + +| 类型 | 注解名称 | 使用举例 | 说明 | +|-------|-------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| 格式化注解 | @DateTimeFormat | @DateTimeFormat(value=格式化值) | 对字符串进行日期格式化 (参照 `java.text.SimpleDateFormat` 书写即可) | +| 格式化注解 | @NumberFormat | @NumberFormat(value=格式化值, roundingMode=舍入模式) | 对字符串进行数值格式化 (参照 `java.text.DecimalFormat` 书写即可, `roundingMode` 默认 `RoundingMode.HALF_UP`) | +| 属性注解 | @ExcelIgnore | @ExcelIgnore | 导出忽略该字段 | +| 属性注解 | @ExcelIgnoreUnannotated | @ExcelIgnoreUnannotated | 默认不管加不加 `@ExcelProperty` 的注解的所有字段都会参与读写,加了 `@ExcelIgnoreUnannotated` 注解以后,不加 `@ExcelProperty` 注解的字段就不会参与 | +| 属性注解 | @ExcelProperty | @ExcelProperty(value=值, order=排序值, index=下标, converter=转换器) | 默认按照对象属性顺序导出,如果设置了 `order` 以及 `index`,优先级 `index` > `order` > 默认;converter 可以自定义 | + +## 扩展使用 + +### 扩展一:自定义转换器实现 + +由于业务需要,原生注解不一定能够符合需要,因而衍生出了自定义转换器。能够实现定制化的内容转换需要。 +以下以框架中的字典转换器 `ExcelDictConvert` 为例进行说明。 + +字典转换器 `ExcelDictConvert`,字典转换器使用了自定义注解 `@ExcelDictFormat` 配合使用。 + +_**注:自定义转换器并非一定需要自定义注解,也可以针对已有的注解进行自定义转换实现。**_ + +#### 实现方式 + +自定义转换器需要实现 `com.alibaba.excel.converters.Converter` 接口,实现接口中的方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700104014304819918/33eb0c42_4959041.png "屏幕截图") + +转换方法 `ExcelDictConvert#convertToJavaData` : + +![输入图片说明](https://foruda.gitee.com/images/1700182975516396213/d3c020f9_4959041.png "屏幕截图") + +### 扩展二:自定义监听器实现 + +自定义监听器主要用于在读取解析 Excel 数据时进行自定义操作。 +以下以框架中的用户导入监听器 `SysUserImportListener` 为例进行说明。 + +#### 实现方式 +1. 继承分析事件监听器 `AnalysisEventListener` 以及实现 Excel 监听器 `ExcelListener`。 + +![输入图片说明](https://foruda.gitee.com/images/1700184652693497753/09333dac_4959041.png "屏幕截图") + +2. 显示使用构造函数,否则将导致空指针。 + +![输入图片说明](https://foruda.gitee.com/images/1700184759075616584/cf05b0ed_4959041.png "屏幕截图") + +3. 实现 `invoke` 方法,对数据进行解析操作,可以在此方法对数据进行合法性判断。 + +4. 实现 `getExcelResult` 方法,对结果进行操作,例如返回成功、失败的统计数据。 + +## 更多功能 + +更多导入功能使用可以参照 `Easy Excel` [官方文档](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read)。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/oss.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/oss.md new file mode 100644 index 00000000..4cb42769 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/oss.md @@ -0,0 +1,124 @@ +# 关于OSS模块使用 +- - - +## 重点注意事项 + +`桶/存储区域` 系统会根据配置自行创建分配权限
+~~如手动配置需要设置 `公有读` 权限 否则文件无法访问~~(`aliyun` 还需开通跨域配置)
+1.4.0 版本支持配置`公有/私有`权限(`aliyun` 还需开通跨域配置)
+访问站点 后严禁携带其他 `url` 例如: `/`, `/ruoyi` 等
+**阿里云与腾讯云SDK访问站点中不能包含桶名 系统会自动处理**
+**minio 站点不允许使用 localhost 请使用 127.0.0.1**
+**访问站点与自定义域名 都不要包含 `http` `https` 前缀 设置`https`请使用选项处理** + +## 代码使用 + +> 参考 `SysOssService.upload` 用法
+> 使用 `OssFactory.instance()` 获取当前启用的 `OssClient` 实例
+> 进行功能调用 获取返回值后 存储到对应的业务表 + +![输入图片说明](https://foruda.gitee.com/images/1678978345529639839/d350ec0b_1766278.png "屏幕截图") + + +## 功能配置 + +### 配置OSS + +> 进入 `系统管理 -> 文件管理 -> 配置管理` 填写对应的OSS服务相关配置
+ +![输入图片说明](https://foruda.gitee.com/images/1678978349820700551/1f91a237_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978354387669856/3a91a3a9_1766278.png "屏幕截图")
**重点说明** + +> 云厂商只需修改 `访问站点`对应的域 切勿乱改(云厂商强烈建议绑定自定义域名使用 七牛云必须绑定[官方规定])
+ +![输入图片说明](https://foruda.gitee.com/images/1678978362358100362/5c2c4d20_1766278.png "屏幕截图") + +> 七牛云 访问站点
+ + +![输入图片说明](https://foruda.gitee.com/images/1678978366254745764/e93a65ff_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978369853348732/79e8950e_1766278.png "屏幕截图") + +> 阿里云 访问站点 + +![输入图片说明](https://foruda.gitee.com/images/1678978373981462025/56a70398_1766278.png "屏幕截图") + +> 腾讯云 访问站点 + +![输入图片说明](https://foruda.gitee.com/images/1678978378697093134/785517f3_1766278.png "屏幕截图") + +### MinIO 使用 https访问站点 + +**注意:S3 API 签名计算算法不支持托管 MinIO Server API 的代理方案** + +[ minio https 配置方式](https://blog.csdn.net/Michelle_Zhong/article/details/126484358) + +### 切换OSS + +> 再配置列表点击 `状态` 按钮开启即可(注意: 只能开启一个OSS默认配置)
+> 手动使用 `OssFactory.instance("configKey")`
+ +![输入图片说明](https://foruda.gitee.com/images/1678978383700118702/7f3fa0c5_1766278.png "屏幕截图") + +### 扩展分类 + +> 如有文件分类 建议创建多个 oss配置 进行切换存储
+ +例如: 创建一个 图片存储的 oss配置
+指定唯一的 `configKey` 与 `前缀目录` 或 直接使用独立的`桶`
+独立桶的特点 可以自定义访问权限
+例如: 创建一个私有文件存储桶 不对外开放
+ +![输入图片说明](https://foruda.gitee.com/images/1678978389139754119/140be1df_1766278.png "屏幕截图") + +> 指定需要使用的配置
+> 使用 `OssFactory.instance("image")` 获取的 `OssClient` 会加载上图的配置 从而达到上传不同的目录或桶 + + +![输入图片说明](https://foruda.gitee.com/images/1678978397550123641/1b536881_1766278.png "屏幕截图") + + +### 上传图片或文件 + +> 进入 `系统管理 -> 文件管理` 点击 `上传文件` 或 `上传图片` 根据选项选择即可 会对应上传到配置开启的OSS内
+ +![输入图片说明](https://foruda.gitee.com/images/1678978401028132972/445d058e_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978404388284503/5459da29_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978408761764835/c81651fc_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978412748494539/7bae621f_1766278.png "屏幕截图") + +### 列表展示 + +> 默认展示图片(可预览) 文件会展示路径
+ +![输入图片说明](https://foruda.gitee.com/images/1678978416327601385/af1ecb3b_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978422249633007/19d68eaa_1766278.png "屏幕截图") + +> 可以点击 `预览禁用启用` 按钮对是否展示进行更改 + +![输入图片说明](https://foruda.gitee.com/images/1678978426017014926/4f7fa3f3_1766278.png "屏幕截图") + +> 点击禁用后 图片会变成路径展示 + +![输入图片说明](https://foruda.gitee.com/images/1678978429692592556/0231d778_1766278.png "屏幕截图") + +> 也可再 `参数设置` 更改预览状态 将 `OSS预览列表资源` 改为 `false` 即可关闭预览 + +![输入图片说明](https://foruda.gitee.com/images/1678978433769403801/7d480e76_1766278.png "屏幕截图") + +### 删除功能 + +> 点击列表上方或后方 `删除` 按钮 会根据OSS服务商类型 调用对应的删除(注意: 需确保对应的服务商配置正确)
+> 可勾选多服务商类型的文件进行删除 系统会自动判断 + +![输入图片说明](https://foruda.gitee.com/images/1678978438265941745/f32edc72_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678978441938542080/43ed7c3d_1766278.png "屏幕截图") + +### 下载功能 + +> 点击列表后方对应资源的 `下载` 按钮 根据需求填写文件名 点击确认即可完成下载 + +![输入图片说明](https://foruda.gitee.com/images/1678978448927336261/409af888_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678978452761792483/ed0a4a72_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/page.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/page.md new file mode 100644 index 00000000..2aafe47b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/page.md @@ -0,0 +1,29 @@ +# 分页功能 +- - - + +## 重点说明 + +> 项目使用 `mybatis-plus` 分页插件 实现分页功能 大致用法与 MP 一致 [MP分页文档](https://baomidou.com/pages/97710a/)
+> 项目已配置分页合理化 页数溢出 例如: 一共5页 查了第6页 默认返回第一页
+ +![输入图片说明](https://foruda.gitee.com/images/1678977804058241635/b5cb362d_1766278.png "屏幕截图") + +## 代码用法 + +> `Controller` 使用 `PageQuery` 接收分页参数 具体参数参考 `PageQuery` + +![输入图片说明](https://foruda.gitee.com/images/1678977844048821356/1f994221_1766278.png "屏幕截图") + +> 构建 `Mybatis-Plus` 分页对象
+> 使用 `PageQuery#build()` 方法 可快速(基于当前对象数据)构建 `MP` 分页对象 + +![输入图片说明](https://foruda.gitee.com/images/1678977862816976499/b82c1638_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678977876194578744/eaa7b854_1766278.png "屏幕截图")
+ +具体用法与 `MP` 一致 + +> 自定义 `SQL` 方法分页
+> 只需在 `Mapper` 方法第一个参数和返回值 重点: 第一个参数 标注分页对象 + +![输入图片说明](https://foruda.gitee.com/images/1678977898181729571/6e102731_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678977906788451483/70979292_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/param_check.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/param_check.md new file mode 100644 index 00000000..95ee19d8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/param_check.md @@ -0,0 +1,158 @@ +# 参数校验 +- - - + +参数校验在日常开发中十分常见,在本框架中引入了 `spring-boot-starter-validation` 依赖,底层基于 `hibernate-validator`,可以对参数进行校验。 + +## 参数校验使用 + +### 方法一:使用 `@Validated` 注解 + +#### 步骤一:标注 `@Validated` + +`@Validated` 可以标注在类上,或者是参数前。 + +```Java +/** 标注在类上 **/ +@Validated +@RestController +@RequestMapping("/auth") +public class AuthController { + + @PostMapping("/login") + public R login(@RequestBody LoginBody body) { + // ... + } + +} +``` + +```Java +/** 标注在参数前 **/ +@PostMapping +public R add(@Validated @RequestBody SysUserBo user) { + // ... +} +``` + +#### 步骤二:标注校验注解 + +在参数中加入校验注解。 + +```Java +public class SysUserBo { + + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") + private String userName; + + @NotBlank(message = "用户昵称不能为空") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符") + private String nickName; + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符") + private String email; + +} +``` + +常见校验注解见文末附表。 + +_注:message 支持 EL 表达式,{max} 直接读取前面的参数值。_ + +### 方法二:使用校验工具类 `ValidatorUtils` + +`org.dromara.common.core.utils.ValidatorUtils` + +![输入图片说明](https://foruda.gitee.com/images/1700050047426137432/206bd032_4959041.png "屏幕截图") + +使用方式 1:校验所有带有校验注解的属性 + +```Java +// 校验所有带有校验注解的属性 +ValidatorUtils.validate(object); +``` + +使用方式 2:按照分组校验属性(可以传多个分组) + +```Java +// 按照分组校验属性(可以传多个分组) +ValidatorUtils.validate(object, group); +``` + +## 扩展使用 + +### 扩展一:自定义校验注解 + +除了已有的校验注解以外,可以结合业务进行自定义。 + +以框架中的 `@Xss` 注解为例进行说明。 + +```Java +@Xss(message = "用户账号不能包含脚本字符") +@NotBlank(message = "用户账号不能为空") +@Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") +private String userName; +``` + +#### 1:新增 `@Xss` 注解 + +`org.dromara.common.core.xss.Xss` + +![输入图片说明](https://foruda.gitee.com/images/1700048074014527096/b4e230c2_4959041.png "屏幕截图") + +#### 2:自定义校验器 + +自定义校验器实现 `jakarta.validation.ConstraintValidator` 接口。 + +`org.dromara.common.core.xss.XssValidator` + +![输入图片说明](https://foruda.gitee.com/images/1700048474563719650/f9172bdc_4959041.png "屏幕截图") + +### 扩展二:自定义分组校验 + +同一个对象在不同的请求中需要校验的参数不同,则可以使用分组校验。 + +#### 1:自定义分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049439236073123/9e0d2e16_4959041.png "屏幕截图") + +#### 2:`@Validated` 注解指定分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049302803077030/c2a985aa_4959041.png "屏幕截图") + +#### 3:校验注解中指定分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049205699437759/96babbd6_4959041.png "屏幕截图") + +## 附录:常用校验注解 + +| 注解 | 使用(只列举特殊参数值) | 参数类型 | 说明 | +|------------------|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------| +| @AssertFalse | @AssertFalse | boolean / Boolean | 元素值必须为 false | +| @AssertTrue | @AssertTrue | boolean / Boolean | 元素值必须为 true | +| @DecimalMax | @DecimalMax(value=值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须小于或等于指定的最大值 | +| @DecimalMin | @DecimalMin(value=值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须大于或等于指定的最小值 | +| @Digits | @Digits(integer=整数位值, fraction=小数位值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须符合整数位以及小数位范围值 | +| @Email | @Email(regexp=正则表达式, flags=标志) | CharSequence | 元素是否符合正则表达式(正则表达式非必传) | +| @Future | @Future | - java.util.Date
- java.util.Calendar
- java.time.Instant
- java.time.LocalDate
- java.time.LocalDateTime
- java.time.LocalTime
- java.time.MonthDay
- java.time.OffsetDateTime
- java.time.OffsetTime
- java.time.Year
- java.time.YearMonth
- java.time.ZonedDateTime
- java.time.chrono.HijrahDate
- java.time.chrono.JapaneseDate
- java.time.chrono.MinguoDate
- java.time.chrono.ThaiBuddhistDate | 元素必须是未来的时刻、日期或时间 | +| @FutureOrPresent | @FutureOrPresent | 同 @Future | 元素必须是当前或未来的时刻、日期或时间 | +| @Length | @Length(min=最小值, max=最大值) | - CharSequence | 验证字符串是否在包含的 min 和 max 之间 | +| @Max | @Max(value=值) | - BigDecimal
- BigInteger
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须小于或等于指定的最大值 | +| @Min | @Min(value=值) | - BigDecimal
- BigInteger
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须大于或等于指定的最小值 | +| @Negative | @Negative | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须是一个严格的负数(即 0 被视为无效值) | +| @NegativeOrZero | @NegativeOrZero | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须为负数或 0 | +| @NotBlank | @NotBlank | CharSequence | 元素不能为 null,并且必须至少包含一个非空白字符 | +| @NotEmpty | @NotEmpty | - CharSequence
- Collection
- Map
- Array | 元素不能为 null 或空集合 | +| @NotNull | @NotNull | 不限类型 | 元素不能为 null | +| @Null | @Null | 不限类型 | 元素必须为 null | +| @Past | @Past | 同 @Future | 元素必须是过去的瞬间、日期或时间 | +| @PastOrPresent | @PastOrPresent | 同 @Future | 元素必须是过去或现在的瞬间、日期或时间 | +| @Pattern | @Pattern(regexp=正则表达式, flags=标志) | CharSequence | 元素必须与指定的正则表达式匹配(正则表达式遵循 Java 正则表达式约定) | +| @Positive | @Positive | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须是一个严格的正数(即 0 被视为无效值) | +| @PositiveOrZero | @PositiveOrZero | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须为正数或 0 | +| @Range | @Range(min=最小值, max=最大值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 验证元素是否在包含的 min 和 max 之间 | +| @Size | @Size(min=最小值, max=最大值) | - CharSequence
- Collection
- Map
- Array | 验证元素是否在包含的 min 和 max 之间 | +| @Valid | @Valid | 对象 | 级联验证 | + +更多注解可参考包: `org.hibernate.validator` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions.md new file mode 100644 index 00000000..62ce2946 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions.md @@ -0,0 +1,144 @@ +# 关于数据权限 +- - - +* 参考 demo 模块用法(需导入 test.sql 文件) + +### 新版数据权限功能: +1.支持自动注入 sql 数据过滤
+2.查询、更新、删除 限制
+3.支持自定义数据字段过滤
+4.模板支持 spel 语法实现动态 Bean 处理
+5.支持与菜单权限标识符联合使用(2.2.X新功能) + +### 数据权限相关代码 + +| 类 | 说明 | 功能 | +|-------------------------------|-----------------|----------------------------------------| +| DataScopeType | 数据权限模板定义 | 用于定义数据权限模板 | +| DataPermission | 数据权限组注解 | 用于标注开启数据权限 (默认过滤部门权限) | +| DataColumn | 具体的数据权限字段标注 | 用于替换数据权限模板内的 key 变量 | +| PlusDataPermissionInterceptor | 数据权限 sql 拦截器 | 用于拦截所有 sql 检查是否标注了 `DataPermission` 注解 | +| PlusDataPermissionHandler | 数据权限处理器 | 用于处理被拦截到的 sql 为其添加数据权限过滤条件 | +| DataPermissionHelper | 数据权限助手 | 操作数据权限上下文变量 | +| SysDataScopeService | 自定义 Bean 处理数据权限 | 用于自定义扩展 | + +## 忽略数据权限 + +1.如果需要指定单独 SQL 不开启过滤,可在对应的 Mapper 接口添加如下忽略注解: +``` +@InterceptorIgnore(dataPermission = "true") +``` + +2.如果需要在业务层忽略数据权限,可调用以下方法: +``` +# 无返回值 +DataPermissionHelper.ignore(() -> { 业务代码 }); +# 有返回值 +Class result = DataPermissionHelper.ignore(() -> { return 业务代码 }); +``` + +### 使用方式 `参考demo模块` +数据权限体系 `用户 -> 多角色 => 角色 -> 单数据权限` +> 例子: 用户A 拥有两个角色
+> 角色A 部门经理 可查看 本部门及以下部门的数据
+> 角色B 兼职开发 可查看 仅自己的数据 + +> 创建角色 test1 为 本部门及以下 + +![输入图片说明](https://foruda.gitee.com/images/1678978669666831574/b51ed0a3_1766278.png "屏幕截图") + +> 创建角色 test2 为 仅本人 + +![输入图片说明](https://foruda.gitee.com/images/1678978674159035056/69cf32ad_1766278.png "屏幕截图") + +> 将其分配给用户 test + +![输入图片说明](https://foruda.gitee.com/images/1678978680492570269/a47b6afc_1766278.png "屏幕截图") + +### 编写列表查询(注意: 数据权限注解只能在 Mapper 层使用) + +> 标注数据权限注解 `dept_id` 为过滤部门字段 `user_id` 为过滤创建用户 + +![输入图片说明](https://foruda.gitee.com/images/1678978687179608427/d6b83c30_1766278.png "屏幕截图") + +### 重点注意: 如下情况不生效 + +> 有自定义实现方法 最终执行的mapper不是这个方法 所以无法生效 +> +> 解决方案: 一直往下点 找到最终的执行mapper重写即可 + +![输入图片说明](https://foruda.gitee.com/images/1678978692558777291/78b0a3dd_1766278.png "屏幕截图") + +### 编写数据权限模板 + +![输入图片说明](https://foruda.gitee.com/images/1678978697141183499/cfc1cb6a_1766278.png "屏幕截图") + +1.`code` 为关联角色的数据权限 `code`
+2.`sqlTemplate` 为 sql 模板
+`#{#deptName}` 为模板变量 对应权限注解的 `key`
+`#{@sdss}` 为模板 Bean 调用 调用其 Bean 的处理方法
+3.`elseSql` 为兜底 sql 处理当前角色与标注的注解 无对应的情况
+例如 数据权限为仅本人 且 方法并未标注具体过滤注解 则 填充 `1 = 0` 使条件不满足 不允许查看
+更详细用法可以参考 `DataScopeType` 注释 + +### 测试代码 + +> 使用 `管理员` 用户优先测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978703250082481/e93a68a5_1766278.png "屏幕截图") + +> 使用 `test` 用户测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978710644676604/d7f80487_1766278.png "屏幕截图") + +> 使用 `test` 删除一条不属于自己的数据 +> sql执行为不满足条件 不允许删除 + +![输入图片说明](https://foruda.gitee.com/images/1678978715711122947/441d61f7_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678978720298532619/a35b1147_1766278.png "屏幕截图") + + +> 使用 `test` 修改与删除同理
+> 具体实现为 更新和删除方法 标注数据权限注解 + +![输入图片说明](https://foruda.gitee.com/images/1678978725329242504/a70491a1_1766278.png "屏幕截图") + +### 自定义SQL模板 + +> 1.首先在角色管理 数据权限下拉框 添加自定义模板
+> 为什么不放置到系统字典问题: 因数据权限与模板绑定 不应随意改动 最好事先定义好 + +![输入图片说明](https://foruda.gitee.com/images/1678978730563169865/3459ee17_1766278.png "屏幕截图") + +> 2.代码 `DataScopeType` 自定义一个SQL模板 + +![输入图片说明](https://foruda.gitee.com/images/1678978735588305505/3f030c67_1766278.png "屏幕截图") + +> 3.标注权限注解 + +![输入图片说明](https://foruda.gitee.com/images/1678978742259837391/eabe5caa_1766278.png "屏幕截图") + +> 4.设置数据权限变量 + +![输入图片说明](https://foruda.gitee.com/images/1678978746778429543/e211201f_1766278.png "屏幕截图") + +> 5.测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978751875467640/7d210cf4_1766278.png "屏幕截图") + +### mybatis-plus 原生方法 增加数据权限过滤 + +> 首先查看需要重写的方法源码 重点`方法源码` `方法源码` `方法源码`
+> 例如重写 `selectPage` 方法
+ +![输入图片说明](https://foruda.gitee.com/images/1678978757955000897/8315695c_1766278.png "屏幕截图") + +> 复制源码到自己的 `Mapper` 并增加数据权限注解 注意左边出现重写图标 即为重写成功
+ +![输入图片说明](https://foruda.gitee.com/images/1678978763224011694/bbea25a1_1766278.png "屏幕截图") + +### 支持类标注 + +> 获取规则 `方法 > 类` 注意: 类标注后 所有方法(包括父类方法) 都会进行数据权限过滤 + +![输入图片说明](https://foruda.gitee.com/images/1678978767336534896/fb13ee99_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions_control.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions_control.md new file mode 100644 index 00000000..6931ca7a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/permissions_control.md @@ -0,0 +1,178 @@ +# 权限控制 +- - - + +本文采用 `Sa-Token` 框架实现权限控制。[官方文档传送门](https://sa-token.cc/doc.html#/) + +## 权限校验 +权限校验指的是校验用户是否拥有访问某个 API 的能力。 + +通常情况下,一个 API 对应一个权限码,如果用户具备当前 API 的权限码,即代表有能力访问该 API。 + +### 1:权限标识 +在本系统中,每一个菜单功能都有对应的权限标识,可以在菜单管理中进行设置。 + +> 注: +> 1. 前后端的权限标识要保持一致。 +> 2. 权限标识可以使用通配符`*`。 + +![输入图片说明](https://foruda.gitee.com/images/1701086497939145368/133fb327_4959041.png "屏幕截图") + + +### 2:校验方法 +#### 2.1:使用 `@SaCheckPermission` 注解进行校验 +`@SaCheckPermission` 注解是由 `Sa-Token` 框架提供的角色校验注解,可以标注在方法上或类上。 + +- 单个权限校验: + +```Java +@SaCheckPermission("system:user:list") +``` + +- 多个权限校验(或模式,满足任意一个权限即可): + +```Java +@SaCheckPermission( + value = { + "system:user:list", + "system:user:query" + }, + mode = SaMode.OR +) +``` + +- 多个权限校验(与模式,必须满足所有权限): + +```Java +@SaCheckPermission( + value = { + "system:user:list", + "system:user:query" + }, + mode = SaMode.AND +) +``` + +#### 2.2:使用 `StpUtil` 工具类校验 +`StpUtil` 工具类是由 `Sa-Token` 框架提供的权限工具类,提供了常用的校验方法。 + +- 判断当前用户是否拥有某个权限(返回 `boolean`): + +```Java +StpUtil.hasPermission("system:user:list"); +``` + +- 单个权限校验: + +```Java +StpUtil.checkPermission("system:user:list"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +- 多个权限校验(或模式,满足任意一个权限即可): + +```Java +StpUtil.checkPermissionOr("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +- 多个权限校验(与模式,必须满足所有权限): + +```Java +StpUtil.checkPermissionAnd("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +## 角色校验 +角色校验指的是校验用户是否拥有某个指定角色。 + +### 1:权限标识 +在本系统中,每个角色都拥有唯一的权限字符。 + +除了超级管理员角色外,其他角色的权限字符可以通过角色管理进行设置。 + +![输入图片说明](https://foruda.gitee.com/images/1701085080527279823/3255961d_4959041.png "屏幕截图") + +### 2:校验方法 +#### 2.1:使用 `@SaCheckRole` 注解校验 +`@SaCheckRole` 注解是由 `Sa-Token` 框架提供的角色校验注解,可以标注在方法上或类上。 + +- 单个角色校验 + +```Java +@SaCheckRole("superadmin") +``` + +- 多个角色校验(或模式,满足任意一个角色即可): + +```Java +@SaCheckRole( + value = { + "superadmin", + "admin" + }, + mode = SaMode.OR +) +``` + +- 多个角色校验(与模式,必须满足所有角色): + +```Java +@SaCheckRole( + value = { + "superadmin", + "admin" + }, + mode = SaMode.AND +) +``` + +#### 2.2:使用 `StpUtil` 工具类校验 +`StpUtil` 工具类是由 `Sa-Token` 框架提供的权限工具类,提供了常用的校验方法。 + +- 判断当前用户是否拥有某个角色(返回 `boolean`): + +```Java +StpUtil.hasRole("superadmin") +``` + +- 单个权限校验: + +```Java +StpUtil.checkRole("system:user:list"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +- 多个权限校验(或模式,满足任意一个角色即可): + +```Java +StpUtil.checkRoleOr("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +- 多个权限校验(与模式,必须满足所有角色): + +```Java +StpUtil.checkRoleAnd("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +## 角色权限双重 `OR` 校验 +除了分开校验以外,权限和角色也可以进行组合,表示备选校验。 + +简单举个例子: + +假设某个 API 的权限码为 `system:user:list`,角色 `admin` 可以调用,则可以这样写: + +```Java +@SaCheckPermission(value = "system:user:list", orRole = "admin") +``` + +以上权限只需要满足任意一项即可。更多写法可以参考 `Sa-Token` [官方文档](https://sa-token.cc/doc.html#/use/at-check?id=_4%e3%80%81%e8%a7%92%e8%89%b2%e6%9d%83%e9%99%90%e5%8f%8c%e9%87%8d-or%e6%a0%a1%e9%aa%8c)。 + +## 当前用户的所有权限 +本系统中实现了 `StpInterface` 接口,可以对用户的权限以及角色进行管理,并且可以根据不同的用户类型进行设置。 + +具体参考类:`org.dromara.common.satoken.core.service.SaPermissionImpl` + +## 忽略权限校验 +请参考文档:[网关路由与放行](/ruoyi-cloud-plus/framework/basic/router_release?id=网关路由与放行) diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/router_release.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/router_release.md new file mode 100644 index 00000000..9b08f069 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/router_release.md @@ -0,0 +1,26 @@ +# 网关路由与放行 +- - - + +## 新增路由 +`ruoyi-gateway.yml` 配置文件 增加 `routers` 配置
+**注意: 路径格式为 `/服务路径/controller路径/接口方法路径` `*代表任意一级 **代表任意所有级`**
+下图代表 `resource/**` 将所有 `resource开头的路径` 都路由到 `ruoyi-resource` 服务
+例如: `/resource/sms/code` `resource路由到ruoyi-resource服务` `sms路由到对应的contrller` `code 路由到对应的接口`
+![输入图片说明](https://foruda.gitee.com/images/1669623462957266512/c282932b_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1669623527799049459/201a52db_1766278.png "屏幕截图") + +## 放行使用方式 +nacos 中 `ruoyi-gateway.yml` 白名单放行
+**注意: 放行路径格式为 `/服务路径/controller路径/接口方法路径` `*代表任意一级 **代表任意所有级`**
+示例: `/resource/sms/code` 代表 `ruoyi-resource服务 sms的controller code接口`
+![输入图片说明](https://foruda.gitee.com/images/1660622672461635175/屏幕截图.png "屏幕截图.png") + +## 注意事项 + +接口放行后不需要token即可访问
+但是没有token也就无法获取用户信息与鉴权 + +### 解决方案 +删除接口上的鉴权注解
+删除接口内获取用户信息功能
+删除数据库实体类 自动注入 `createBy` `updateBy` 因为会获取用户数据 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/social.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/social.md new file mode 100644 index 00000000..2e3cd739 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/social.md @@ -0,0 +1,68 @@ +# 第三方授权功能 +- - - +## 版本 >= 2.X + +## 前置说明 +1. 该功能基于 `JustAuth` 实现,支持多家平台实现第三方授权登录。 +2. 以 `Gitee` 授权登录为例进行本功能的使用说明。 +3. 其他第三方授权配置信息获取方式可参考 `JustAuth` [官方文档](https://www.justauth.cn/guide/)。
+ + ![输入图片说明](https://foruda.gitee.com/images/1690937097426867003/91d80587_4959041.png "屏幕截图") + +## 第三方授权配置 + +### 申请三方应用(以gitee为例) + +![输入图片说明](https://foruda.gitee.com/images/1700641775779304627/1cf1b56f_1766278.png "屏幕截图") + +### 更改后端配置 `application-dev.yml` + +![输入图片说明](https://foruda.gitee.com/images/1690936741844431943/580f8998_4959041.png "屏幕截图") + +**注:内网地址无法回调,请使用外网可以访问的地址。** + +![输入图片说明](https://foruda.gitee.com/images/1690940457570856867/ce22df18_4959041.png "屏幕截图") + +### 更改前端配置 `login.vue` + +![输入图片说明](https://foruda.gitee.com/images/1690937306197173754/5c1ece29_4959041.png "屏幕截图") + +## 授权登录(未绑定第三方平台) + +### 步骤一:个人中心授权第三方应用 + +![输入图片说明](https://foruda.gitee.com/images/1690938449386201097/ea375106_4959041.png "屏幕截图") + +### 步骤二:同意授权 + +![输入图片说明](https://foruda.gitee.com/images/1690938522418523183/81b327bf_4959041.png "屏幕截图") + +顶部出现授权成功,并跳转到系统首页。
+ +![输入图片说明](https://foruda.gitee.com/images/1690938559178527841/563168e4_4959041.png "屏幕截图")
+ +![输入图片说明](https://foruda.gitee.com/images/1690938636375977741/8ceb77cf_4959041.png "屏幕截图") + +查看第三方应用可看到授权成功的个人信息。
+ +![输入图片说明](https://foruda.gitee.com/images/1690938725512311321/5532a2a9_4959041.png "屏幕截图") + +## 授权登录(已绑定第三方平台) + +### 步骤一:点击登录页面图标 + +![输入图片说明](https://foruda.gitee.com/images/1690938908352243992/fd044381_4959041.png "屏幕截图") + +### 步骤二:同意授权 + +![输入图片说明](https://foruda.gitee.com/images/1690938522418523183/81b327bf_4959041.png "屏幕截图") + +## 解除授权绑定 + +### 步骤一:个人中心点击解绑第三方应用 + +![输入图片说明](https://foruda.gitee.com/images/1690939087877969002/4ef324e7_4959041.png "屏幕截图") + +### 步骤二:点击确定完成解绑 + +![输入图片说明](https://foruda.gitee.com/images/1690939108017661775/7236088d_4959041.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/tenant.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/tenant.md new file mode 100644 index 00000000..b57c3f63 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/tenant.md @@ -0,0 +1,121 @@ +# 多租户功能 +- - - +## 版本 >= 2.X + +## 前置说明(重要) +1. 本框架多租户功能的实现是基于 [MyBatis-Plus 多租户插件](https://baomidou.com/pages/aef2f2/#tenantlineinnerinterceptor) 的,只支持最简单的隔离。 +2. 本系统默认开启多租户功能。 +3. 多租户业务表建表需要加上租户id `tenant_id`,可参考其他系统表。 +4. 非多租户表可在配置文件进行配置排除。 +5. 只有超级管理员支持切换租户。 + +## 多租户使用流程(先说结论再展开!) +0. 开启多租户配置(系统默认已经开启) +1. 登录界面(可以选择不同租户) +> 注:如果为租户设置了绑定域名,则只能选择当前域名相关的租户列表。 +2. 设置多租户套餐 +3. 新增/修改租户(需要选择套餐) +4. 切换租户(仅超级管理员可操作) + +## 多租户配置 +`application-common.yml`
+ +> 开关 `enable` 节点不用废话。
+> 如果不需要过滤租户的表可在 `excludes` 节点下添加。 + +**注意: 如果已经基于租户模式启动了程序 关闭租户必须删除mysql与redis内的相关数据重新导入sql** + +![输入图片说明](https://foruda.gitee.com/images/1680168468127690787/2cd3279e_4959041.png "屏幕截图") + +## 忽略租户 + +1.如果需要指定单独 SQL 不开启过滤,可在对应的 Mapper 接口添加如下忽略注解: +``` +@InterceptorIgnore(tenantLine = "true", dataPermission = "false") +``` +**此处注意事项 使用此注解如果需要开启数据权限 dataPermission = "false" 必须添加 mp的注解默认是忽略数据权限的 会导致数据权限失效** + +2.如果需要在业务层忽略多租户,可调用以下方法(推荐使用): +``` +# 无返回值 +TenantHelper.ignore(() -> { 业务代码 }); +# 有返回值 +Class result = TenantHelper.ignore(() -> { return 业务代码 }); +``` + +## 动态切换租户 + +**仅适用于特殊需求业务(例如: 创建租户时, 对该租户操作一些数据, 或者需要去其他租户查一些数据等) 禁止乱用后果自负** + +``` +# 无返回值 +TenantHelper.dynamic(租户id, () -> { 业务代码 }); +# 有返回值 +Class result = TenantHelper.dynamic(租户id, () -> { return 业务代码 }); +``` + +## 登录界面 + +![输入图片说明](https://foruda.gitee.com/images/1680173982933030545/bca146d7_4959041.png "屏幕截图") + +> 注:如果为租户设置了绑定域名,则只能选择当前域名相关的租户列表。 + +## 租户套餐管理 +### 租户套餐新增 +![输入图片说明](https://foruda.gitee.com/images/1680174317475230288/352957a1_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680174602877523112/fc194f17_4959041.png "屏幕截图") + +> 注: +> 1、先新增套餐再新增租户,因为租户新增之后无法修改所选套餐。 +> 2、租户所关联的套餐如果后续有修改可以进行同步。 + + +## 租户管理 +### 默认租户 +> 注:默认租户无法修改 + +![输入图片说明](https://foruda.gitee.com/images/1680174738913576400/b6aca11a_4959041.png "屏幕截图") + +### 新增租户 +#### 填写表单 +![输入图片说明](https://foruda.gitee.com/images/1680174945220618443/f7181b51_4959041.png "屏幕截图") + +#### 选择新增的租户套餐 +![输入图片说明](https://foruda.gitee.com/images/1680174991869792688/0dbaadd6_4959041.png "屏幕截图") + +#### 新增完成 +![输入图片说明](https://foruda.gitee.com/images/1680175033853525725/42e64b4d_4959041.png "屏幕截图") + +#### 登录租户 +![输入图片说明](https://foruda.gitee.com/images/1680176145378931134/e05f347e_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176208161104366/44a935f1_4959041.png "屏幕截图") + +### 修改租户 +#### 配置域名 +![输入图片说明](https://foruda.gitee.com/images/1680175251192690133/141fa6a6_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680175431036971650/db522d39_4959041.png "屏幕截图") + +#### 没有配置域名 +![输入图片说明](https://foruda.gitee.com/images/1680175541165540240/95e211f7_4959041.png "屏幕截图") + +#### 强调一下:这不是bug! +> 注:域名的配置就是为了绑定特定租户! + +### 同步套餐 +应用场景:租户套餐进行了修改,配置的菜单需要同步到特定租户。 +(不是所有租户都有更新套餐的权利, 这是跟钱挂钩的) + +> 点一下按钮的事,图略。 + +## 切换租户(仅超级管理员) +> 注:管理员切换租户不是切换用户,切换的只是数据,管理员拥有所有权限。 + +![输入图片说明](https://foruda.gitee.com/images/1680176324802967804/5c5d6fc3_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176431031189788/0c3f924c_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176496555243569/624ec677_4959041.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/user.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/user.md new file mode 100644 index 00000000..99050fa7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/basic/user.md @@ -0,0 +1,85 @@ +# 系统用户相关 +- - - + +> 框架采用sa-token控制权限 并对sa-token的api做了一定的业务封装
+ +## 用户登录 + +> 参考自带多种登录实现 不限制用户数据来源 只需要构建 LoginUser 即可完成登录
+> 例如: `同表不同类型` `不同表` `同表+扩展表`
+ +![输入图片说明](https://foruda.gitee.com/images/1699590555824776931/63d493fc_1766278.png "屏幕截图") + +## 获取用户信息 + +> 完成登录后会生成登录token返回给前端 前端需要再请求头携带token 后端方可获取到对应的用户信息 + +请求头传递格式: `Authorization: Bearer token` + +后端获取用户信息: +```java +LoginUser user = LoginHelper.getLoginUser(); +``` + +## 获取用户信息(基于token) +```java +LoginUser user = LoginHelper.getLoginUser(token); +``` + +## 获取登录用户id +```java +Long userId = LoginHelper.getUserId(); +``` + +## 获取登录用户账户名 +```java +String username = LoginHelper.getUsername(); +``` + +## 获取登录用户所属租户id +```java +String tenantId = LoginHelper.getTenantId(); +``` + +## 获取登录用户所属部门id +```java +Long deptId = LoginHelper.getDeptId(); +``` + +## 获取登录用户类型 +```java +UserType userType = LoginHelper.getUserType(); +``` + +## 获取登录用户其他扩展属性 +```java +Object obj = LoginHelper.getExtra(key); +``` + +## 设置登录用户其他扩展属性 + +参考登录设置 `clientId` 属性 + +![输入图片说明](https://foruda.gitee.com/images/1699591164562734430/42730add_1766278.png "屏幕截图") + +## 判断用户是否为超级管理员 + +```java +// 判断当前登录用户 +boolean b = LoginHelper.isSuperAdmin(); +// 判断用户基于id +boolean b = LoginHelper.isSuperAdmin(userId); +``` + +## 判断用户是否为租户管理员 + +```java +// 判断当前登录用户 +boolean b = LoginHelper.isTenantAdmin(); +// 判断用户基于角色组 +boolean b = LoginHelper.isSuperAdmin(rolePermission); +``` + +## 其他更多操作 +[Sa-Token 官方文档 - 登录认证](https://sa-token.cc/doc.html#/use/login-auth) + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/about_join.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/about_join.md new file mode 100644 index 00000000..77cde6ef --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/about_join.md @@ -0,0 +1,12 @@ +# 关于多表查询 +- - - +## 建议单表查询 + +文章连接: [大连接查询分解好处](https://java.isture.com/db/mysql/mysql-x-optimize-decompose-connection.html) + +![输入图片说明](https://foruda.gitee.com/images/1678979482724037085/1e74f3e1_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1666336728402711844/52788205_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1666336945935088277/f60e3288_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1666336954686520161/c6c83adc_1766278.png "屏幕截图")
+**(上图出自 <高性能MySql>)** \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/key.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/key.md new file mode 100644 index 00000000..c960140d --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/key.md @@ -0,0 +1,19 @@ +# 主键使用说明 +- - - +## 关于如何使用分布式id或雪花id + +参考 `MybatisPlusConfig` 如需自定义 修改 `Bean` 实现即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979401707903546/e25f6c06_1766278.png "屏幕截图") + +框架默认集成 雪花ID 只需全局更改 主键类型即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979411517764918/1470df04_1766278.png "屏幕截图") + +如单表使用 可单独配置注解 + +![输入图片说明](https://foruda.gitee.com/images/1678979416033986923/2a4c3736_1766278.png "屏幕截图") + +### 重点说明 +* 由于雪花id位数过长 `Long` 类型在前端会失真 +* 框架已配置序列化方案 超越 `JS` 最大值自动转字符串 参考 `BigNumberSerializer` 类 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/test.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/test.md new file mode 100644 index 00000000..4c521ad1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/test.md @@ -0,0 +1,6 @@ +# 单元测试 +- - - +## 参考文章 +[SpringBoot 2.X 整合 JUnit5 及全方位使用手册](https://lionli.blog.csdn.net/article/details/127576604) +## 参考代码(1.4.0新增) +![输入图片说明](https://foruda.gitee.com/images/1666973151030696086/6d44f4c2_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/transaction.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/transaction.md new file mode 100644 index 00000000..2b4966dc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/explain/transaction.md @@ -0,0 +1,45 @@ +# 事务相关 +- - - +若依文档对事务注解的描述 [关于事务](https://doc.ruoyi.vip/ruoyi/document/htsc.html#%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86) 以下对多数据源事务做补充: + + +## 多服务多数据源事务(框架已默认对接 直接使用seata注解即可) + +框架支持对接 `seata` 保证分布式多数据源事务
+详情参考多数据源框架文档连接: https://www.kancloud.cn/tracy5546/dynamic-datasource/2268607 + +## 本地多数据源事务 +请使用 `@DSTransactional` 注解 会代理 `@DS` 注解切换后的数据源事务做回滚处理
+只要 `@DSTransactional` 注解下任一环节发生异常,则全局多数据源事务回滚。
+如果BC上也有 `@DSTransactional` 会有影响吗?答:没有影响的。 + +```java +//如AService调用BService和CService的方法,A,B,C分别对应不同数据源。 + +public class AService { + + @DS("a")//如果a是默认数据源则不需要DS注解。 + @DSTransactional + public void dosomething(){ + BService.dosomething(); + CService.dosomething(); + } +} + +public class BService { + + @DS("b") + public void dosomething(){ + //dosomething + } +} + +public class CService { + + @DS("c") + public void dosomething(){ + //dosomething + } +} +``` + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/api_encrypt.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/api_encrypt.md new file mode 100644 index 00000000..be4ce445 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/api_encrypt.md @@ -0,0 +1,39 @@ +# 数据加解密 +- - - + +## 1:API 加密注解 `@ApiEncrypt` +1. 对于标注了 `@ApiEncrypt` 注解的接口,请求参数都必须进行加密。 +2. 注解的参数 `response` 为响应加密标识,默认 `false` 不加密,为 `true` 表示响应加密。 +3. 加密解密逻辑由过滤器实现,详情可参考 `org.dromara.common.encrypt.filter.CryptoFilter`。 + +## 2:API 加密配置 +`application-common.yml` + +![输入图片说明](https://foruda.gitee.com/images/1701133628809355269/8979704a_4959041.png "屏幕截图") + +`.env.development` / `.env.production` + +![输入图片说明](https://foruda.gitee.com/images/1709533252413969800/1d0dff25_1766278.png "屏幕截图") + +> 注: +> 1. 注意修改 Nacos 配置。 +> 2. 公私钥与前端配置文件互为配对,如果需要更换请一同更换。 +> 3. 后端公钥对应前端私钥;后端私钥对应前端公钥。 + +## 3:前端开启加密 +如果需要开启 API 加密,则需要修改 `request` 的 `headers` 内容: +```Javascript +headers: { + isEncrypt: true +} +``` + +![输入图片说明](https://foruda.gitee.com/images/1701137141916998346/5e839bbe_4959041.png "屏幕截图") + +## 4.关于请求响应参数加解密说明 + +如何加解密请求响应参数看这里 -> [关于请求响应参数解密](/questions/api_encrypt.md) + +## 密钥生成说明 + +![输入图片说明](https://foruda.gitee.com/images/1675577852271308699/9b30258e_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/dynamic_datasource.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/dynamic_datasource.md new file mode 100644 index 00000000..21114cd4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/dynamic_datasource.md @@ -0,0 +1,45 @@ +# 多数据源 +- - - + +### 框架默认 mysql 其他数据库使用说明 + +找到 `ruoyi-common-mybatis` 模块在 pom 文件内增加对应的jdbc依赖 + +![输入图片说明](https://foruda.gitee.com/images/1721098535176969987/d42870ca_1766278.png "屏幕截图") + + +### 关于多数据源事务 具体参考 `事务相关` 文档说明 + +### 多数据源框架功能介绍 +多数据源框架官方文档: [dynamic-datasource文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611) + +* 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 +* 支持数据库敏感配置信息 加密 ENC()。 +* 支持每个数据库独立初始化表结构schema和数据库database。 +* 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。 +* 支持 自定义注解 ,需继承DS(3.2.0+)。 +* 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。 +* 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。 +* 提供 自定义数据源来源 方案(如全从数据库加载)。 +* 提供项目启动后 动态增加移除数据源 方案。 +* 提供Mybatis环境下的 纯读写分离 方案。 +* 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。 +* 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。 +* 提供 基于seata的分布式事务方案。 +* 提供 本地多数据源事务方案。 附:不能和原生spring事务混用。 + +### 用法说明 + +> 加载顺序 `方法 => 类 => 默认`
+ +![输入图片说明](https://foruda.gitee.com/images/1678979069737596299/abe8ae7f_1766278.png "屏幕截图") + +### 配置方式 + +![输入图片说明](https://foruda.gitee.com/images/1678979074000345758/b9238f0b_1766278.png "屏幕截图") + +### 数据库异构 + +例如: `mysql + oracle` 参考对应多数据源框架文档 [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource) + +![输入图片说明](https://foruda.gitee.com/images/1678979078387192317/2de94a78_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/encrypt.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/encrypt.md new file mode 100644 index 00000000..729a3039 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/encrypt.md @@ -0,0 +1,38 @@ +# 数据加解密 +- - - +## 框架版本 >= 1.6.0 + +## 引入依赖 + +```xml + + com.ruoyi + ruoyi-common-encrypt + +``` + +## 功能说明 + +数据库 数据存储加密 查询解密功能
+支持加密算法: `BASE64` `AES` `RSA` `SM2` `SM4` + +## 注解 `@EncryptField` + +![输入图片说明](https://foruda.gitee.com/images/1675577493013639395/cd920f15_1766278.png "屏幕截图") + +## 用法说明 + +**详细用法可参考案例 TestEncryptController 测试数据库加解密功能** + +全局默认加密配置(如果注解不配置则使用全局配置) + +![输入图片说明](https://foruda.gitee.com/images/1675577674063566357/dee94786_1766278.png "屏幕截图") + +注解可自定义算法与配置 + +![输入图片说明](https://foruda.gitee.com/images/1675577725117970708/7ee7a833_1766278.png "屏幕截图") + +## 密钥生成说明 + +![输入图片说明](https://foruda.gitee.com/images/1675577852271308699/9b30258e_1766278.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/idempotent.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/idempotent.md new file mode 100644 index 00000000..5f706710 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/idempotent.md @@ -0,0 +1,29 @@ +# 防重幂等 +- - - +## 功能介绍 + +防重功能为防止两条相同的数据重复提交导致脏数据或业务错乱
+**注意: 重复提交属于小概率事件 请不要拿并发压测与之相提并论**
+框架防重功能参考 `美团GTIS防重系统` 使用 请求参数与用户Token或URL 生成全局业务ID
+有效防止 `同一个用户` 在 `限制时间` 内对 `同一个业务` 提交 `相同的数据` + +框架防重处理 `支持业务失败或异常` 快速释放限制
+业务处理成功后 会在设置时间内 限制同一条数据的提交
+**注意: 只对同一个用户的同一个接口提交相同的数据有效** + + + + +### 美团GTIS系统流程图 + +[美团 分布式系统互斥性与幂等性问题的分析与解决](https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html) + +![输入图片说明](https://foruda.gitee.com/images/1678979231862359032/34f030c5_1766278.png "屏幕截图") + +### 使用方法 + +在Controller标注 `@RepeatSubmit` 注解即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979236772683145/9fa27e5b_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678979240831458322/8e1fac4b_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/mail.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/mail.md new file mode 100644 index 00000000..ce089802 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/mail.md @@ -0,0 +1,15 @@ +# 邮件功能 +- - - +## 配置功能 + +修改配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1663555260932007318/fabb2bfa_1766278.png "屏幕截图") + +* `enabled` 为邮件功能开关 + +## 功能使用 + +参考 `demo` 模块 `MailController` 邮件演示案例 + +![输入图片说明](https://foruda.gitee.com/images/1663555374113593089/885b4db2_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sensitive.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sensitive.md new file mode 100644 index 00000000..3e2d92ba --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sensitive.md @@ -0,0 +1,33 @@ +# 数据脱敏 +- - - +## 功能说明 + +系统使用 `Jackson` 序列化策略 对标注了 `Sensitive` 注解的属性进行脱敏处理 + +## 使用教程 + +> 使用注解标注需要脱敏的字段 选择对应的策略 + +![输入图片说明](https://foruda.gitee.com/images/1699523591703893602/ffd6dba2_1766278.png "屏幕截图") + +* strategy 脱敏策略 +* roleKey 角色code(判断用户是否拥有角色权限) +* perms 权限code(判断用户是否拥有标识符权限) + +![输入图片说明](https://foruda.gitee.com/images/1678979315796014155/614adf91_1766278.png "屏幕截图") + +> 可再 `SensitiveStrategy` 内自定义策略 + +![输入图片说明](https://foruda.gitee.com/images/1678979319996224858/3b3e3c8b_1766278.png "屏幕截图") + +## 脱敏逻辑修改 + +> 系统使用通用接口处理是否需要脱敏 多个系统可以自定义不同的脱敏逻辑实现 + +![输入图片说明](https://foruda.gitee.com/images/1678979325448998856/b262e425_1766278.png "屏幕截图") + +> 系统默认处理逻辑为 根据角色与标识符或非管理员脱敏 可自行修改默认实现 + +![输入图片说明](https://foruda.gitee.com/images/1699523752627488891/f82f2f50_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sms.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sms.md new file mode 100644 index 00000000..81fb345a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sms.md @@ -0,0 +1,51 @@ +# 短信模块 +- - - + +# 配置功能 + +### 版本: >= v2.1.0 + +已完成 sms4j 项目整合 文档地址: https://sms4j.com/doc3 + +配置方式 具体厂商配置扩展 可以查看sms4j文档 + +![输入图片说明](https://foruda.gitee.com/images/1705573035997239848/2ca8512d_1766278.png "屏幕截图") + +使用方式 参考文档各种写法 下方为 demo 模块提供示例 + +![输入图片说明](https://foruda.gitee.com/images/1705573001447394180/2bd726d0_1766278.png "屏幕截图") + +### 版本: v1.2.0 提供短信模块 + +短信模块采用SPI加载
+使用哪家的短信 引入哪家的依赖 即可动态加载
+目前支持: `阿里云` `腾讯云` 欢迎扩展PR其他 + +> 参考 `ruoyi-demo` pom文件写法 + +![输入图片说明](https://foruda.gitee.com/images/1678979157797419426/cc9b7444_1766278.png "屏幕截图") + +> 修改配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1678979163029635375/e5fd6e20_1766278.png "屏幕截图") + +* `enabled` 为短信功能开关 +* `endpoint` 为域名 各厂家域名固定 按照文档配置即可 +* `accessKeyId` 密钥id +* `accessKeySecret` 密钥密匙 +* `signName` 签名 +* `sdkAppId` 应用id 腾讯专用 + +## 功能使用 + +参考 `demo` 模块 `SmsController` 短信演示案例
+功能采用 `模板模式` 动态加载对应厂家的工具模板
+引入 `SmsTemplate` 即可使用 + +![输入图片说明](https://foruda.gitee.com/images/1678979168699323982/e9301e84_1766278.png "屏幕截图") + +## 重点须知 + +由于各厂家参数解析不一致 请遵守以下规则 + +![输入图片说明](https://foruda.gitee.com/images/1678979172581090456/ac1f10e8_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sse.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sse.md new file mode 100644 index 00000000..ec51df7c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/sse.md @@ -0,0 +1,24 @@ +# SSE功能 +- - - + +## 框架版本 >= 2.2.1 + +## 配置说明 + +配置在 `ruoyi-resource` 目录下 远程调用可直接使用 `RemoteMessageService` 接口 + +![输入图片说明](https://foruda.gitee.com/images/1721986989993234455/4214cbbd_1766278.png "屏幕截图") + +* enabled 是否开启此功能 +* path 应用路径 + +## 使用方法 + +前端连接方式: `http://后端ip:端口/resource/sse?clientid=import.meta.env.VITE_APP_CLIENT_ID&Authorization=Bearer eyJ0eXAiO......` + +其中 `Authorization` 为请求token需要登录后获取 连接成功之后 与框架内其他获取登录用户方式一致 + +`SseMessageUtils.sendMessage` 推送单机消息(特殊需求使用)
+`SseMessageUtils.subscribeMessage` 订阅分布式消息(框架初始化已订阅)
+`SseMessageUtils.publishMessage` 发布分布式消息(推荐使用 所有集群内寻找到接收人)
+`SseMessageUtils.publishAll` 群发消息给所有连接人
\ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/translation.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/translation.md new file mode 100644 index 00000000..d0834ab8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/translation.md @@ -0,0 +1,44 @@ +# 翻译功能 +- - - +## 框架版本 >= 1.6.0 + +## 引入依赖包 + +```xml + + com.ruoyi + ruoyi-common-translation + +``` + +## 注解 + +![输入图片说明](https://foruda.gitee.com/images/1675575648043199227/d04b3e21_1766278.png "屏幕截图") + +`@Translation` 翻译注解 用于实体类字段上
+`@TranslationType` 翻译类别注解 用于实现类上标注与 `@Translation` 注解相同的 `type` 类型 实现翻译功能 + + +## 用法说明 + +默认提供功能 `用户id转账号(用户名)` `部门id转名称` `字典type转label` `ossId转url` + +![输入图片说明](https://foruda.gitee.com/images/1675575977860232549/143b74f8_1766278.png "屏幕截图") + +用户名翻译(映射翻译) 根据另一个映射字段 翻译保存到此字段 + +![输入图片说明](https://foruda.gitee.com/images/1675576044011477847/13eb9f57_1766278.png "屏幕截图") + +ossUrl翻译(直接翻译) 直接根据此字段值翻译后替换此字段值 + +![输入图片说明](https://foruda.gitee.com/images/1675576265894720924/70792f66_1766278.png "屏幕截图") + +字典翻译(其他扩展条件翻译) 根据`other`条件 自行定义如何使用 例如字典翻译`other`条件就是字典的唯一值 + +![输入图片说明](https://foruda.gitee.com/images/1675576391012282823/f95c5d78_1766278.png "屏幕截图") + +## 自定义扩展 + +实现接口 `TranslationInterface` 标注注解 `@TranslationType` 可参考框架默认实现 + +![输入图片说明](https://foruda.gitee.com/images/1676735454308997001/cfcf3590_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/websocket.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/websocket.md new file mode 100644 index 00000000..55145bad --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/extend/websocket.md @@ -0,0 +1,39 @@ +# WebSocket功能 +- - - + +## 框架版本 >= 2.1.0 + +## 配置说明 + +配置在 `ruoyi-resource` 目录下 + +![输入图片说明](https://foruda.gitee.com/images/1688356273985385949/5e4d1de8_1766278.png "屏幕截图") + +* enabled 是否开启此功能 +* path 应用路径 +* allowedOrigins 设置访问源地址 + +**重点: 如关闭ws功能需连同前端ws开关一同关闭 不然前端启动会报错** + +![输入图片说明](https://foruda.gitee.com/images/1700644877512019497/052d2f46_1766278.png "屏幕截图") + +## 使用方法 + +前端连接方式: `ws://后端ip:端口/resource/websocket?clientid=import.meta.env.VITE_APP_CLIENT_ID&Authorization=Bearer eyJ0eXAiO......` + +**由于js不支持请求头传输故而采用参数传输 如支持请求头传输建议使用请求头传输** + +传输方式: +```js +headers: { + Authorization: "Bearer " + getToken(), + clientid: import.meta.env.VITE_APP_CLIENT_ID +} +``` + +其中 `Authorization` 为请求token需要登录后获取 连接成功之后 与框架内其他获取登录用户方式一致 + +`WebSocketUtils.sendMessage` 推送单机消息(特殊需求使用)
+`WebSocketUtils.subscribeMessage` 订阅分布式消息(框架初始化已订阅)
+`WebSocketUtils.publishMessage` 发布分布式消息(推荐使用 所有集群内寻找到接收人)
+`WebSocketUtils.publishAll` 群发消息给所有连接人
\ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/tree.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/tree.md new file mode 100644 index 00000000..f649197c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/framework/tree.md @@ -0,0 +1,91 @@ +# 项目结构 +- - - +## 目录结构 +v2.2.1 +~~~ +RuoYi-Cloud-Plus +├─ ruoyi-api // api模块 +│ └─ ruoyi-api-bom // api模块依赖管理 +│ └─ ruoyi-api-resource // 资源api模块 +│ └─ ruoyi-api-system // 系统api模块 +│ └─ ruoyi-api-workflow // 工作流api模块 +├─ ruoyi-auth // 认证服务 [9210] +├─ ruoyi-common // 通用模块 +│ └─ ruoyi-common-alibaba-bom // alibaba 依赖管理 +│ └─ ruoyi-common-bom // common 依赖管理 +│ └─ ruoyi-common-bus // 消息总线模块 +│ └─ ruoyi-common-core // 核心功能模块 +│ └─ ruoyi-common-dict // 字典集成模块 +│ └─ ruoyi-common-doc // 文档集成模块 +│ └─ ruoyi-common-dubbo // dubbo集成模块 +│ └─ ruoyi-common-elasticsearch // ES集成模块 +│ └─ ruoyi-common-encrypt // 数据加解密模块 +│ └─ ruoyi-common-excel // excel集成模块 +│ └─ ruoyi-common-idempotent // 幂等功能模块 +│ └─ ruoyi-common-job // job定时任务集成模块 +│ └─ ruoyi-common-json // json集成模块 +│ └─ ruoyi-common-loadbalancer // 团队负载均衡集成模块 +│ └─ ruoyi-common-log // 日志集成模块 +│ └─ ruoyi-common-logstash // elk日志集成模块 +│ └─ ruoyi-common-mail // 邮件集成模块 +│ └─ ruoyi-common-mybatis // mybatis数据库相关集成模块 +│ └─ ruoyi-common-oss // oss相关集成模块 +│ └─ ruoyi-common-prometheus // prometheus监控 +│ └─ ruoyi-common-redis // redis集成模块 +│ └─ ruoyi-common-satoken // satoken集成模块 +│ └─ ruoyi-common-seata // seata分布式事务集成模块 +│ └─ ruoyi-common-security // 框架权限鉴权集成模块 +│ └─ ruoyi-common-sensitive // 脱敏功能模块 +│ └─ ruoyi-common-sentinel // sentinel集成模块 +│ └─ ruoyi-common-skylog // skywalking日志收集模块 +│ └─ ruoyi-common-sms // 短信集成模块 +│ └─ ruoyi-common-social // 社交三方功能模块 +│ └─ ruoyi-common-sse // sse流推送模块 +│ └─ ruoyi-common-tenant // 租户功能模块 +│ └─ ruoyi-common-translation // 通用翻译功能 +│ └─ ruoyi-common-web // web服务集成模块 +│ └─ ruoyi-common-websocket // websocket服务集成模块 +├─ ruoyi-example // 例子模块 +│ └─ ruoyi-demo // 演示模块 [9401] +│ └─ ruoyi-test-mq // mq演示模块 [9402] +├─ ruoyi-gateway // 网关模块 [8080] +├─ ruoyi-modules // 功能模块 +│ └─ ruoyi-gen // 代码生成模块 [9202] +│ └─ ruoyi-job // 任务调度模块 [9203,9901] +│ └─ ruoyi-resource // 资源模块 [9204] +│ └─ ruoyi-system // 系统模块 [9201] +│ └─ ruoyi-workflow // 工作流模块 [9205] +├─ ruoyi-visual // 可视化模块 +│ └─ ruoyi-monitor // 服务监控模块 [9100] +│ └─ ruoyi-nacos // nacos服务模块 [8848,9848,9849] +│ └─ ruoyi-seata-server // seata服务模块 [7091,8091] +│ └─ ruoyi-sentinel-dashboard // sentinel控制台模块 [8718] +│ └─ ruoyi-snailjob-server // 任务调度控制台模块 [8800,17888] +├─ plus-ui // 前端框架 [80] +├─ config/nacos // nacos配置文件(需复制到nacos配置中心使用) +│ └─ sentinel-ruoyi-gateway.json // sentinel对接gateway限流配置文件 +│ └─ seata-server.properties // seata服务配置文件 +│ └─ application-common.yml // 所有应用主共享配置文件 +│ └─ datasource.yml // 所有应用共享数据源配置文件 +│ └─ ruoyi-auth.yml // auth 模块配置文件 +│ └─ ruoyi-gateway.yml // gateway 模块配置文件 +│ └─ ruoyi-gen.yml // gen 模块配置文件 +│ └─ ruoyi-job.yml // job 模块配置文件 +│ └─ ruoyi-monitor.yml // monitor 模块配置文件 +│ └─ ruoyi-resource.yml // resource 模块配置文件 +│ └─ ruoyi-sentinel-dashboard.yml // sentinel 控制台 模块配置文件 +│ └─ ruoyi-snailjob-server.yml // snailjob 控制台 模块配置文件 +│ └─ ruoyi-system.yml // systen 模块配置文件 +│ └─ ruoyi-workflow.yml // workflow 模块配置文件 +├─ config/grafana // grafana配置文件(需复制到grafana使用) +│ └─ Nacos.json // Nacos监控页面 +│ └─ SLS JVM监控大盘.json // JVM监控页面 +│ └─ Spring Boot 2.1 Statistics.json // SpringBoot监控页面 +├─ sql // sql脚本 +├─ docker // docker 配置脚本 +├─ .run // 执行脚本文件 +├─ .editorconfig // 编辑器编码格式配置 +├─ LICENSE // 开源协议 +├─ pom.xml // 公共依赖 +├─ README.md // 框架说明文件 +~~~ \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/home.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/home.md new file mode 100644 index 00000000..290a4f9c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/home.md @@ -0,0 +1,137 @@ + +
+ +- - - +# 平台简介 +
+ +[![码云Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Cloud-Plus) +[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Cloud-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Cloud-Plus) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus/blob/master/LICENSE) +[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Cloud-Plus) +
+[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.2.1-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.1-blue.svg)]() +[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() +[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]() + +> RuoYi-Cloud-Plus `微服务通用权限管理系统` 重写 RuoYi-Cloud 全方位升级(不兼容原框架) + +> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可
+活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源 + +> 系统演示: [传送门](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4836388&doc_id=1469725) 分布式集群版本(功能一致) + +# 本框架与RuoYi的功能差异 + +| 功能 | 本框架 | RuoYi | +|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| 前端项目 | 采用 Vue3 + TS + ElementPlus 重写 | 基于Vue2/Vue3 + JS | +| 后端项目结构 | 采用插件化 + 扩展包形式 结构解耦 易于扩展 | 模块相互注入耦合严重难以扩展 | +| 后端代码风格 | 严格遵守Alibaba规范与项目统一配置的代码格式化 | 代码书写与常规结构不同阅读障碍大 | +| 分布式注册中心 | 采用 Alibaba Nacos 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控 | 采用 Alibaba Nacos 自行搭建纯官方版本不可靠 | +| 分布式配置中心 | 采用 Alibaba Nacos 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控 | 采用 Alibaba Nacos 自行搭建纯官方版本不可靠 | +| 服务网关 | 采用 SpringCloud Gateway 框架扩展了多种功能
例如:内网鉴权、请求体缓存、跨域配置、请求响应日志等 | 采用 SpringCloud Gateway 功能单一 | +| 负载均衡 | 采用 SpringCloud Loadbalancer 扩展支持了开发团队路由 便于多团队开发调试 | 采用 SpringCloud Loadbalancer 功能单一 | +| RPC远程调用 | 采用 全新 Apache Dubbo 3.X 历史悠远不用多说 | 采用 feign 功能有限编写方式 网络波动大 不稳定 | +| 分布式限流熔断 | 采用 Alibaba Sentinel 源码集成便于调试扩展与二次开发 框架还为其增加了各种监控 | 采用 Alibaba Sentinel 自行搭建纯官方版本不可靠 | +| 分布式事务 | 采用 Alibaba Seata 源码集成对接了Nacos与各种监控 简化了搭建部署流程 | 采用 Alibaba Seata 自行搭建纯官方版本 搭建繁琐与Nacos不挂钩 代码内使用方式怪异等 | +| Web容器 | 采用 Undertow 基于 XNIO 的高性能容器 | 采用 Tomcat | +| 权限认证 | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展 | Spring Security 配置繁琐扩展性极差 | +| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验
角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 | +| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer
可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 | +| 缓存数据库 | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 | +| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具
支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan
支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐
连接池采用 common-pool Bug多经常性出问题 | +| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能
例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 | +| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多
例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL | +| SQL监控 | 采用 p6spy 可输出完整SQL与执行时间监控 | log输出 需手动拼接sql与参数无法快速查看调试问题 | +| 数据分页 | 采用 Mybatis-Plus 分页插件
框架对其进行了扩展 对象化分页对象 支持多种方式传参 支持前端多排序 复杂排序 | 采用 PageHelper 仅支持单查询分页 参数只能从param传 只能单排序 功能扩展性差 体验不好 | +| 数据权限 | 采用 Mybatis-Plus 插件 自行分析拼接SQL 无感式过滤
只需为Mapper设置好注解条件 支持多种自定义 不限于部门角色 | 采用 注解+aop 实现 基于部门角色 生成的sql兼容性差 不支持其他业务扩展
生成sql后需手动拼接到具体业务sql上 对于多个Mapper查询不起作用 | +| 数据脱敏 | 采用 注解 + jackson 序列化期间脱敏 支持不同模块不同的脱敏条件
支持多种策略 如身份证、手机号、地址、邮箱、银行卡等 可自行扩展 | 无 | +| 数据加解密 | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密
支持多种策略 如BASE64、AES、RSA、SM2、SM4等 | 无 | +| 数据翻译 | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译
支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现 | 无 | +| 多数据源框架 | 采用 dynamic-datasource 支持市面大部分数据库
通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源
支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 | +| 多数据源事务 | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚 | 不支持 | +| 数据库连接池 | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下 | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般 | +| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 | +| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 | +| SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 | +| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 | +| 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 | +| 分布式任务调度 | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 | +| 分布式日志中心 | 采用 ELK 业界成熟解决方案 实时收集所有服务的运行日志 快速发现定位问题 | 无 | +| 分布式搜索引擎 | 采用 ElasticSearch、Easy-Es 以 Mybatis-Plus 方式操作 ElasticSearch | 无 | +| 分布式消息队列 | 采用 支持 Kafka、RocketMQ、RabbitMQ 各种 延迟消息 事务消息 流消息 | 无 | +| 分布式消息总线 | 采用 SpringCloud Bus 实现事件总线 跨服务通知 支持 Kafka、RocketMQ、RabbitMQ | 无 | +| 分库分表功能 | 采用 Apache Sharding-Proxy 代理服务无入侵支持分库分表 只需编写分库分表规则即可 | 无 | +| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储
支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 | +| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 | +| 短信 | 支持 阿里、腾讯 只需在yml配置好厂家密钥即可使用 接口化支持扩展其他厂家 | 不支持 | +| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 | +| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释
只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 | +| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 | +| Excel框架 | 采用 Alibaba EasyExcel 基于插件化
框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 | +| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 | +| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 | +| 服务监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制
实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 | +| 全方位监控报警 | 采用 Prometheus、Grafana 多样化采集 多模板大屏展示 实时报警监控 提供详细的搭建文档 | 无 | +| 链路追踪 | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗
用了它即可实时查看请求经过的每一处每一个节点 | 无 | +| 代码生成器 | 只需设计好表结构 一键生成所有crud代码与页面
降低80%的开发量 把精力都投入到业务设计上
框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成 | 代码生成原生结构 只支持单数据源生成 | +| 部署方式 | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼 | 原生jar部署 其他环境需手动下载安装 自行搭建 | +| 项目路径修改 | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的 | 需要做很多改造 文档说明有限 | +| 国际化 | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化 | 只提供基础功能 其他需自行编写扩展 | +| 代码单例测试 | 提供单例测试 使用方式编写方法与maven多环境单测插件 | 只提供基础功能 其他需自行编写扩展 | +| Demo案例 | 提供框架功能的实际使用案例 单独一个模块提供了很多很全 | 无 | + +## 本框架与RuoYi的业务差异 + +| 业务 | 功能说明 | 本框架 | RuoYi | +|--------|-----------------------------------------|-----|------------------| +| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 | +| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 | +| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 | +| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 | +| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 | +| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 | +| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 | +| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 | +| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 | +| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 | +| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 | +| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 | +| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 | +| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 | +| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 | +| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 | +| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 | +| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 | +| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 | +| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 | +| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 | +| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 | + +## 演示图例 + +| | | +|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") | + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/1.Xinit.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/1.Xinit.md new file mode 100644 index 00000000..19492b1e --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/1.Xinit.md @@ -0,0 +1,87 @@ +# 1.X项目初始化 +- - - +### 项目分支说明 +`master` 主分支 稳定发布分支
+`dev` 开发分支 代码随时更新 不推荐使用 经测试后会发布到主分支
+`2.X` 新2.X大版本分支
+`future/*` 新功能预览分支 + +### 项目必备环境 +> 推荐使用 `docker` 安装 项目内置 `docker` 编排文件 + +* oracle jdk 8 11 (暂时不支持 17 不支持大于 jdk8_202 因为202是最后一个免费版本) +* mysql 5.7 8.0 (5.6未适配可能会有问题) +* oracle 11g 12c +* postgres 13 14 +* redis 5.X 6.X 7.X 由于框架大量使用了redis特性 版本必须 >= 5.X ([win redis 下载地址](https://github.com/zkteco-home/redis-windows)) +* minio 本地文件存储 或 阿里云 腾讯云 七牛云等一切支持S3协议的云存储 +* maven 3.6.3 3.8.X +* nodejs >= 12 +* npm 6.X 8.X (7.X确认有问题) +* nacos >= 2.X(框架1.3.0内置nacos) +* sentinel 框架内置 +* seata 框架内置 + +### 需勾选 maven 对应环境 +![输入图片说明](https://foruda.gitee.com/images/1678976284045210056/a2f28d33_1766278.png "屏幕截图") + +### 默认 `JDK8` 如有变动 需更改以下配置 + +![输入图片说明](https://foruda.gitee.com/images/1686813181851830778/2dd7954c_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1686813189749486666/c526486c_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1686813196981178511/cd218705_1766278.png "屏幕截图") + +### sql导入 +将sql导入到与sql文件名对应的数据库(不要放到一个库下)
+ +![输入图片说明](https://foruda.gitee.com/images/1678981513725772842/8097a816_1766278.png "屏幕截图") + +### 使用内置 `ruoyi-naocs` 服务 从这开始 + +> 更改 ruoyi-nacos 数据库地址 + +![输入图片说明](https://foruda.gitee.com/images/1664422006264405180/cac5afc6_1766278.png "屏幕截图") + +**其余流程同下方步骤一致** + +### 自建 Nacos 从这开始 + +**Nacos 数据库指向 ry-config 数据库(此处重点: 此数据库为定制数据 未使用此库会无法读取配置)** + +> 将项目 `config/nacos` 下所有配置 复制到 `nacos` 内(建议手动复制内容 防止编码不一致问题) + +![输入图片说明](https://foruda.gitee.com/images/1678979826345958752/913142c9_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678979856705927770/75cc1e8c_1766278.png "屏幕截图") + +> 更改 `主pom文件` 对应环境的 `nacos` 地址 + +![输入图片说明](https://foruda.gitee.com/images/1678979881888833924/7e6a191f_1766278.png "屏幕截图") + +### 更改 `Nacos` 自定义配置 + +**忠告: 微服务配置相当复杂 请勿在不懂原理的情况下乱改** + +> `application-common.yml` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979889410167794/100db4ab_1766278.png "屏幕截图") + +> `datasource.yml` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979894464784408/0d020c07_1766278.png "屏幕截图") + +> `seata-server.properties` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979902433843257/12da2839_1766278.png "屏幕截图") + +### 使用内置 `ruoyi-seata-server` 服务 从这开始 + +执行 `ry-seata.sql` 文件 初始化服务端数据库
+修改 `nacos` 内的 `seata-server.properties` 的数据库地址
+启动 `ruoyi-seata-server` 服务即可 + +### 服务启动顺序说明 + +1. 必须启动基础建设: mysql redis nacos
+2. 可选启动基础建设: minio(影响文件上传) seata(影响分布式事务 默认开启) sentinel(影响熔断限流) monitor(影响监控) xxljob(影响定时任务)
+3. 必须启动应用服务: gateway auth system
+4. 可选启动应用服务: resource(影响资源使用 文件上传 邮件 短信等) gen(代码生成) job(影响定时任务) demo(影响demo使用) diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/deploy.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/deploy.md new file mode 100644 index 00000000..bdc653bc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/deploy.md @@ -0,0 +1,118 @@ +# 应用部署 +- - - +## 版本 >= 1.3.0 + +### 请优先阅读 [idea环境配置](/ruoyi-cloud-plus/quickstart/idea_environment.md) + +## 手动部署 + +在服务器安装 `mysql` `redis` `nginx` `minio` 等其他组件 + +将项目内 `docker/` 文件夹下的文件内容 放到对应的组件内
+例如: 将项目内 `docker/nginx/nginx.conf` 配置文件 复制到 `nginx` 配置内
+将项目内 `docker/redis/redis.conf` 配置文件 复制到 `redis` 配置内
+ +并修改相关参数如 `前端页面存放位置` `后端Ip地址` 等使其生效 + +jar包部署后端服务 打包命令如下 +```mvn +mvn clean install -D maven.test.skip=true -P prod +``` +前端参考下方前端部署章节 + + +## docker 后端部署 + +### 请优先阅读 [idea环境配置](https://gitee.com/dromara/RuoYi-Cloud-Plus/wikis/pages?sort_id=5985190&doc_id=2056143) + +**重点: 一知半解的必看** +> [docker安装](https://lionli.blog.csdn.net/article/details/83153029)
+> [docker-compose安装](https://lionli.blog.csdn.net/article/details/111220320)
+> [docker网络模式讲解](https://lionli.blog.csdn.net/article/details/109603785)
+> [docker 开启端口 2375 供外部程序访问](https://lionli.blog.csdn.net/article/details/92627962) + +### 将配置使用FTP上传到根目录 +idea拖拽文件到远程目录即可上传
+![输入图片说明](https://foruda.gitee.com/images/1662109450908169859/eaac9299_1766278.png "屏幕截图") + +### 给docker分配文件夹权限 +**重点注意: 一定要确保目录 `/docker` 及其所有子目录 具有写权限 如果后续出现权限异常问题 重新执行一遍分配权限** +![输入图片说明](https://foruda.gitee.com/images/1662109847279259882/3a2202c1_1766278.png "屏幕截图") +```shell +chmod -R 777 /docker +``` +### 构建应用镜像 + +**1.需要先使用maven打包成jar包**
+![输入图片说明](https://foruda.gitee.com/images/1662110477410977621/c6931c42_1766278.png "屏幕截图") + +**2.执行构建**
+> 项目初始化后会自动生成构建镜像的运行配置
+配置好docker连接之后 运行如下即可构建对应的应用镜像 + +**重点注意: idea2024及以上版本要求必须在本地安装docker才可以执行如下操作** + +![输入图片说明](https://foruda.gitee.com/images/1662110192257483752/0f754b47_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1662120029312793237/89dee3e5_1766278.png "屏幕截图") + +**3.结构讲解**
+右键编辑 即可看到内部配置
+ +![输入图片说明](https://foruda.gitee.com/images/1662458355500139498/eaa26036_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1662458446794722159/32c086a7_1766278.png "屏幕截图") + + +### 创建基础服务 + +```shell +docker-compose up -d mysql nginx-web redis minio +``` + +### 创建中心服务(需要先构建服务镜像) + +1.X +```shell +docker-compose up -d nacos seata-server sentinel ruoyi-monitor ruoyi-xxl-job-admin +``` + +2.X +```shell +docker-compose up -d nacos seata-server sentinel ruoyi-monitor ruoyi-snailjob-server +``` + +### 创建业务服务(需要先构建服务镜像) + +```shell +docker-compose up -d ruoyi-gateway ruoyi-auth ruoyi-system ruoyi-resource +``` + +### docker其他操作(idea的docker插件 推荐使用) +![输入图片说明](https://foruda.gitee.com/images/1662458296425228696/90b4b4f8_1766278.png "屏幕截图") + + +## 前端部署 + +执行打包命令 +```shell +# 打包正式环境 +npm run build:prod +``` +打包后生成打包文件在 `ruoyi-ui/dist` 目录
+将 `dist` 目录下文件(不包含 `dist` 目录) 上传到部署服务器 `docker/nginx/html` 目录下(手动部署放入自己配置的路径即可)
+![输入图片说明](https://foruda.gitee.com/images/1662110914769648699/07f344c4_1766278.png "屏幕截图")
+重启 `nginx` 服务即可 + + +### 如需更改后端代理路径或者后端ip地址的话往下看 + +更改`nginx.conf`配置文件代理路径(注意: /开头/结尾) + +![输入图片说明](https://foruda.gitee.com/images/1660185698211067202/屏幕截图.png "屏幕截图.png") + +更改前端`.env.环境` 文件内的 `VITE_APP_BASE_API` + +![输入图片说明](https://foruda.gitee.com/images/1724318035232137124/5d035a09_1766278.png "屏幕截图") + +更改`nginx.conf`配置文件后端ip地址 + +![输入图片说明](https://foruda.gitee.com/images/1660185711265558730/屏幕截图.png "屏幕截图.png") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/extend_project.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/extend_project.md new file mode 100644 index 00000000..8d68fa4d --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/extend_project.md @@ -0,0 +1,42 @@ +# 基于 RuoYi-Cloud-Plus 的扩展项目列表 +- - - +### 精品PR 欢迎投稿 +| 功能介绍 | PR地址 | +|-------------------------------------|------------------------------------------------------| +| 拖拽图片调整显示顺序 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/173 | +| 增加Jasypt加密库对配置文件加密 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/177 | +| 使用富文本wangeditor5替换Quill | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/213 | +| sentinel持久化nacos动态更改 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/37 | +| 集成screw数据库文档功能模块 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/42 | +| Excel导入模板增加批注支持 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/222 | +| 压缩包处理工具 支持本地文件/目录+oss文件/网络文件混合 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/44 | +| 添加websocket模块 支持satoken鉴权 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/65 | +| 数据库字段加解密(支持 base64 aes rsa sm2 sm4) | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/274 | +| 增加liquibase迁移数据库 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/299 | +| 增加OSS模块支持本地环境 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/353 | +| 扩展模块独立集成flyway | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/439 | +| 扩展模块独立集成go-view大屏看板 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/445 | +| 基于AmazonS3协议的分片上传 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/130 | +| 扩展forest http客户端 声明式http请求 二次封装像工具类 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/547 | +| 增加短链接生成工具 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/515 | +| 新增oss预签名上传工具组合使用异步客户端分片 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/522 | +| 新增规则引擎LiteFlow,SQL持久化接入,支持可视化页面 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/552 | +| 一键部署到私有Nexus仓库 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/181 | +| 服务状态监控发送邮件钉钉等 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/568 | +| 登录验证支持2FA验证 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/578 | + +### 欢迎投稿 项目介绍+项目地址 + + +| 项目介绍 | 项目地址 | +|--------------------------------|-------------------------------------------------------------------------| +| 分布式集群扩展 | https://gitee.com/dromara/RuoYi-Vue-Plus | +| Plus学习笔记(常规功能) | https://zhonglingyuxiu1028.github.io/zlyx-space/#/ruoyi-vue-plus/home | +| Plus学习笔记(微服务组件) | https://zhonglingyuxiu1028.github.io/zlyx-space/#/ruoyi-cloud-plus/home | +| 基于uniapp+TmUI从0开发 支持H5/小程序/安卓 | https://gitee.com/dapppp/ruoyi-plus-miniapp | +| 基于RuoYi-App框架二次修改使用Uniapp+Vue3 | https://gitee.com/wangying110166/ruo-yi-uni-app-plus | +| 基于RuoYi-App框架对接Plus后端 | https://gitee.com/FnTop/RuoYi-App-Plus | +| 基于vben(ant-design-vue)前端项目 | https://gitee.com/dapppp/ruoyi-plus-vben | +| 基于vue-next-admin的vue3+ts前端 | https://gitee.com/thiszhc/RuoYi-Vue3-UI | +| MybatisFlex版本 | https://gitee.com/yhan219/ruoyi-cloud-flex | + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/idea_environment.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/idea_environment.md new file mode 100644 index 00000000..25a5fa3c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/idea_environment.md @@ -0,0 +1,38 @@ +# idea环境配置 +- - - +## 配置项目编码 +![输入图片说明](https://foruda.gitee.com/images/1662107706295343419/e27065a9_1766278.png "屏幕截图") + +## 配置运行看板 +![输入图片说明](https://foruda.gitee.com/images/1662108673306567278/8af97b47_1766278.png "屏幕截图") +### 配置spring与docker看板 +![输入图片说明](https://foruda.gitee.com/images/1662111392476935892/6b6760fb_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1662108865191892425/3c045999_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1662108877322329668/ddb6d93d_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1662108894122798039/6a53a38c_1766278.png "屏幕截图") + +## 配置服务器SSH连接 +进入 `Settings -> Tools -> SSH Configurations` 点击加号创建SSH连接配置
+填写 服务器IP 用户名 密码 端口号 点击 Test Connection 测试连接
+![输入图片说明](https://foruda.gitee.com/images/1662107776533098115/bd78467b_1766278.png "屏幕截图") +使用Terminal 工具 点击箭头找到上方创建的SSH连接配置
+选择即可进入SSH连接界面 在这里可以对服务器进行命令操作
+![输入图片说明](https://foruda.gitee.com/images/1662108010120640495/c70f9f9a_1766278.png "屏幕截图") + +## 配置服务器FTP连接 +进入 `Settings -> Build-> Deployment` 点击加号 选择SFTP 创建 FTP 连接配置
+选择之前创建好的SSH配置 点击 Test Connection 测试连接
+![输入图片说明](https://foruda.gitee.com/images/1662107899553257979/e2eeb7fd_1766278.png "屏幕截图") +在IDEA上方工具栏 找到 `Tools -> Deployment -> Browse Remote Host` 打开远程界面
+点击箭头找到我们上方配置的SFTP连接配置 即可连接到服务器的文件目录
+![输入图片说明](https://foruda.gitee.com/images/1662107974682787233/b8a601fd_1766278.png "屏幕截图") + +## 配置Docker连接 +### 可操作远程docker与构建上传docker镜像(代替原来maven docker插件) +tcp连接需要开放服务器2375端口
+ssh需要使用上方的SSH连接配置
+建议使用SSH连接
+![输入图片说明](https://foruda.gitee.com/images/1662108188005932060/75872bf8_1766278.png "屏幕截图") +配置好之后 在运行窗口会多出一个Docker图标 双击即可连接远程docker
+可以查看容器实时日志 启动 重启 停止 等操作
+![输入图片说明](https://foruda.gitee.com/images/1662108250902891875/b82d022b_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/init.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/init.md new file mode 100644 index 00000000..f6d4fadc --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/init.md @@ -0,0 +1,102 @@ +# 2.X项目初始化 +- - - +### 项目分支说明 + +`2.X` 主分支 新2.X版本 稳定发布分支
+`dev` 开发分支 代码随时更新 不推荐使用 经测试后会发布到主分支
+`future/*` 新功能预览分支 + +### 项目必备环境 +> 推荐使用 `docker` 安装 项目内置 `docker` 编排文件 + +**注意: 禁止使用 `oraclejdk`(由于spring的bug导致打包运行会报错)** + +**Spring官方推荐使用JDK https://bell-sw.com/pages/downloads/** + +![输入图片说明](https://foruda.gitee.com/images/1720080025744223375/0213a652_1766278.png "屏幕截图") + +* openjdk-17/21 或 graalvm-community-jdk-17/21 [下载地址](https://github.com/graalvm/graalvm-ce-builds/releases) 版本 +* mysql 5.7 8.0 (其他版本未测试 如其他版本没问题 可以告知咱们) +* oracle >= 12c (其他版本未测试 如其他版本没问题 可以告知咱们) +* postgres 13 14 (其他版本未测试 如其他版本没问题 可以告知咱们) +* redis 5.X 6.X 7.X 由于框架大量使用了redis特性 版本必须 >= 5.X ([win redis 下载地址](https://github.com/zkteco-home/redis-windows)) +* minio 本地文件存储 或 阿里云 腾讯云 七牛云等一切支持S3协议的云存储 +* maven >= 3.8.X +* nodejs 18.18 (其他版本未测试 如其他版本没问题 可以告知咱们) +* npm >= 8.X (7.X确认有问题) +* idea 2022 2024 (一定不要使用2023后果自负 bug太多影响项目开发) +* nacos >= 2.X(框架已经内置 采用nacos官方jar包) +* sentinel 框架内置(采用sentinel官方jar包) +* seata 框架内置(采用seata官方jar包) + +### 需勾选 maven 对应环境 + +![输入图片说明](https://foruda.gitee.com/images/1678976284045210056/a2f28d33_1766278.png "屏幕截图") + +### 默认 `JDK17` 如有变动 需更改以下配置 + +![输入图片说明](https://foruda.gitee.com/images/1678941027820943505/c688e01e_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678941120518807034/4d56fcc9_1766278.png "屏幕截图") + +### sql导入 + +将sql导入到与sql文件名对应的数据库(不要放到一个库下)
+默认数据库为mysql如需使用其他数据库 看这里 => [多数据库数据源](../framework/extend/dynamic_datasource.md)
+ +![输入图片说明](https://foruda.gitee.com/images/1717122730708924506/7f3aaecf_1766278.png "屏幕截图") + +### 使用内置 `ruoyi-naocs` 服务 从这开始 + +> 更改 ruoyi-nacos 数据库地址 + +![输入图片说明](https://foruda.gitee.com/images/1664422006264405180/cac5afc6_1766278.png "屏幕截图") + +**其余流程同下方步骤一致** + +### 自建 Nacos 从这开始 + +**Nacos 数据库指向 ry-config 数据库(此处重点: 此数据库为定制数据 未使用此库会无法读取配置)** + +> 将项目 `config/nacos` 下所有配置 复制到 `nacos` 内(建议手动复制内容 防止编码不一致问题) + +**注意: 不懂就不要乱改配置文件内容 框架内所有功能都是配置好的!!!不要画蛇添足**
+**注意: 不懂就不要乱改配置文件内容 框架内所有功能都是配置好的!!!不要画蛇添足**
+**注意: 不懂就不要乱改配置文件内容 框架内所有功能都是配置好的!!!不要画蛇添足**
+ +![输入图片说明](https://foruda.gitee.com/images/1678979826345958752/913142c9_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678979856705927770/75cc1e8c_1766278.png "屏幕截图") + +> 更改 `主pom文件` 对应环境的 `nacos` 地址 + +![输入图片说明](https://foruda.gitee.com/images/1678979881888833924/7e6a191f_1766278.png "屏幕截图") + +### 更改 `Nacos` 自定义配置 + +**忠告: 微服务配置相当复杂 请勿在不懂原理的情况下乱改**
+**忠告: 微服务配置相当复杂 请勿在不懂原理的情况下乱改**
+**忠告: 微服务配置相当复杂 请勿在不懂原理的情况下乱改**
+ +> `application-common.yml` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979889410167794/100db4ab_1766278.png "屏幕截图") + +> `datasource.yml` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979894464784408/0d020c07_1766278.png "屏幕截图") + +> `seata-server.properties` 更改 + +![输入图片说明](https://foruda.gitee.com/images/1678979902433843257/12da2839_1766278.png "屏幕截图") + +### 使用内置 `ruoyi-seata-server` 服务 从这开始 + +执行 `ry-seata.sql` 文件 初始化服务端数据库
+修改 `nacos` 内的 `seata-server.properties` 的数据库地址
+启动 `ruoyi-seata-server` 服务即可 + +### 服务启动顺序说明 + +1. 必须启动基础建设: mysql redis nacos
+2. 可选启动基础建设: minio(影响文件上传) seata(影响分布式事务 默认开启) sentinel(影响熔断限流) monitor(影响监控) snailjob(影响定时任务)
+3. 必须启动应用服务: gateway auth system
+4. 可选启动应用服务: resource(影响资源使用 websocket 文件上传 邮件 短信等) workflow(工作流) gen(代码生成) job(影响定时任务) demo(影响demo使用) diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/power_job_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/power_job_init.md new file mode 100644 index 00000000..907c7fc7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/power_job_init.md @@ -0,0 +1,33 @@ +# 搭建PowerJob任务调度中心(2.X分支已废弃) +- - - +### 废弃原因 + +接到大量投诉 使用困难 用法诡异 各种问题等 + +### 配置调度中心客户端 +> 查看ruoyi-job配置文件(默认情况下无需做任何更改) +> +![输入图片说明](https://foruda.gitee.com/images/1688013407489024239/9b619e0d_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `server-address` 为调度中心地址 +* `server-name` 为调度中心服务名 +* `app-name` 为执行器组账户名(需在调度中心注册方可登录查看) + +### 启用调度中心 +**需执行 ry-job.sql 默认账号密码 `ruoyi-worker` `123456` 账号在数据库里 可以在页面修改密码** +
+ +![输入图片说明](https://foruda.gitee.com/images/1688634898607827011/8853b387_1766278.png "屏幕截图") + +> 在 `ruoyi-visual -> ruoyi-powerjob-server` 启动 +> +![输入图片说明](https://foruda.gitee.com/images/1688013606234848334/cf2028cd_1766278.png "屏幕截图") + +> 需修改配置文件数据库连接地址(**注意: 此处为ruoyi-powerjob-server服务的配置文件**) +> +![输入图片说明](https://foruda.gitee.com/images/1688013663152608235/6c5d6a9c_1766278.png "屏幕截图") + +> 也可配置邮件发送 钉钉推送 和 mongodb存储 +> +![输入图片说明](https://foruda.gitee.com/images/1687335842722317559/f875c07a_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/snail_job_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/snail_job_init.md new file mode 100644 index 00000000..25c21ebb --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/snail_job_init.md @@ -0,0 +1,38 @@ +# 搭建SnailJob任务调度中心(2.2.0新功能) +- - - + +### 视频介绍 + +[Snail job任务调度中心:轻松掌握任务管理、重试机制和任务编排](https://www.bilibili.com/video/BV19i421m7GL/) + +### 配置调度中心客户端 +> 修改主服务配置文件 +> + +![输入图片说明](https://foruda.gitee.com/images/1716175076777941469/db565dc1_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `server.server-name` 为调度中心服务名(自动从Nacos获取服务 支持动态扩容调度中心) +* `server.address` 为调度中心地址(服务名优先 ip垫底) +* `server.port` 为调度中心通信端口 +* `token` 为组通信校验token(可在调度中心组配置更换) +* `group-name` 为执行器组 +* `namespace` 作用域(不同作用域相互隔离请勿填错) + +### 启用调度中心 +**需执行 ruoyi-job.sql 默认账号密码 `admin` `admin` 账号在数据库里 可以在页面修改密码** +
+ +![输入图片说明](https://foruda.gitee.com/images/1688634898607827011/8853b387_1766278.png "屏幕截图") + +> 在 `ruoyi-visual -> ruoyi-snailjob-server` 模块启动 +> +![输入图片说明](https://foruda.gitee.com/images/1716175119324078438/ca667a0c_1766278.png "屏幕截图") + +> 需修改配置文件数据库连接地址(**注意: 此处为ruoyi-snailjob-server服务的配置文件 支持多种不同数据库**) +> +![输入图片说明](https://foruda.gitee.com/images/1688013663152608235/6c5d6a9c_1766278.png "屏幕截图") + +### 快速入门 + +[Snailjob快速入门 基本使用介绍](https://juejin.cn/post/7412955032092442675) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/worker_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/worker_init.md new file mode 100644 index 00000000..121a094d --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-cloud-plus/quickstart/worker_init.md @@ -0,0 +1,52 @@ +# 工作流初始化 +- - - + +### 注意事项 + +设置 RabbitMQ 配置 `application-common.yml` 配置文件 (可使用其他例如 kafka、rocketmq 详见 ruoyi-common-bus 模块) + +此功能用于跨服务同步流程与业务状态 MQ安装方式可参考文档扩展功能 + +![输入图片说明](https://foruda.gitee.com/images/1718728432072816698/47eadbb1_1766278.png "屏幕截图") + + +### 工作流使用及配置方式 + +1.找到项目中bpmn文件夹 + +![输入图片说明](https://foruda.gitee.com/images/1714211764058540441/5c8b97af_5363069.png "屏幕截图") + +2.启动项目找到流程定义通过**部署流程文件**将bpmn文件夹下**模型.zip**上传 + +![输入图片说明](https://foruda.gitee.com/images/1714211950485333575/1e2b3ff4_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212113004821592/96586e69_5363069.png "屏幕截图") + +3.导入**模型.zip**后将会出现以下列表,默认使用**leave1**,test_leave为请假申请表名称 + +![输入图片说明](https://foruda.gitee.com/images/1714212222766335759/1227bbd6_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212493602552742/9e0258b1_5363069.png "屏幕截图") + +**此处表名由来与表单源码内编写的表名保持一致方可互相绑定** + +![输入图片说明](https://foruda.gitee.com/images/1716447357161482917/2c9b1639_1766278.png "屏幕截图") + + +4.新增一条请假申请,提交后将会得到如下信息 + +![输入图片说明](https://foruda.gitee.com/images/1714212617432902105/3609f6ef_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212630860787365/2922d38e_5363069.png "屏幕截图") + +5.关于如何切换一个新的流程使用,当前默认使用得KEY为leave1 ,我们切换到leave2使用,我们只需点击绑定业务将表名绑定,重新发起一个新的请假申请就可以得到一个新的流程信息 + +![输入图片说明](https://foruda.gitee.com/images/1714212876442323110/4554ea95_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714213037864274694/613149f5_5363069.png "屏幕截图") + +**此处表名由来与表单源码内编写的表名保持一致方可互相绑定** + +![输入图片说明](https://foruda.gitee.com/images/1716447357161482917/2c9b1639_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212963457174382/add768db_5363069.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/_sidebar.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/_sidebar.md new file mode 100644 index 00000000..9fd7aad1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/_sidebar.md @@ -0,0 +1,64 @@ + +- **特别赞助** +- [![输入图片说明](https://foruda.gitee.com/images/1704162419429172656/d0521e59_1766278.png "2024-01-02=>2028-01-02")](http://ccflow.org/?frm=ryPlus) +- [![输入图片说明](https://foruda.gitee.com/images/1705569347386939952/3f187980_1766278.jpeg "2024-01-18=>2025-01-18")](http://www.shuduokeji.com) +- [![输入图片说明](https://foruda.gitee.com/images/1711681233267310022/2ffbcff2_1766278.png "2024-03-29=>2025-03-29")](https://www.jnpfsoft.com/index.html?from=plus-doc) + + +* **简介** + * [项目简介](/ruoyi-vue-plus/home.md) + * [更新日志](/ruoyi-vue-plus/changlog.md) +* **快速开始** + * [项目初始化](/ruoyi-vue-plus/quickstart/init.md) + * [5.X新功能介绍](/ruoyi-vue-plus/quickstart/5.Xnew.md) + * [4.X项目初始化](/ruoyi-vue-plus/quickstart/4.Xinit.md) + * [工作流初始化](/ruoyi-vue-plus/quickstart/worker_init.md) + * [搭建Admin监控](/ruoyi-vue-plus/quickstart/admin_init.md) + * [搭建SnailJob调度中心](/ruoyi-vue-plus/quickstart/snail_job_init.md) + * [(废弃)搭建PowerJob调度中心](/ruoyi-vue-plus/quickstart/power_job_init.md) + * [(废弃)搭建XXLJob调度中心](/ruoyi-vue-plus/quickstart/xxl_job_init.md) + * [idea环境配置](/ruoyi-vue-plus/quickstart/idea_environment.md) + * [应用部署](/ruoyi-vue-plus/quickstart/deploy.md) + * [扩展项目](/ruoyi-vue-plus/quickstart/extend_project.md) +* **框架功能** + * [项目结构](/ruoyi-vue-plus/framework/tree.md) + * [软件架构图](/ruoyi-vue-plus/framework/architecture_diagram.md) + * 框架相关 + * [创建新模块](/ruoyi-vue-plus/framework/association/new_module.md) + * [修改包名](/ruoyi-vue-plus/framework/association/update_package_name.md) + * [接口文档](/ruoyi-vue-plus/framework/association/doc.md) + * [修改应用路径](/ruoyi-vue-plus/framework/association/update_url.md) + * [国际化](/ruoyi-vue-plus/framework/association/i18n.md) + * 基础功能 + * [系统用户相关](/ruoyi-vue-plus/framework/basic/user.md) + * [权限控制](/ruoyi-vue-plus/framework/basic/permissions_control.md) + * [导出功能](/ruoyi-vue-plus/framework/basic/export.md) + * [导入功能](/ruoyi-vue-plus/framework/basic/import.md) + * [参数校验](/ruoyi-vue-plus/framework/basic/param_check.md) + * [代码生成](/ruoyi-vue-plus/framework/basic/code_generate.md) + * [分页功能](/ruoyi-vue-plus/framework/basic/page.md) + * [OSS功能](/ruoyi-vue-plus/framework/basic/oss.md) + * [数据权限](/ruoyi-vue-plus/framework/basic/permissions.md) + * [接口放行](/ruoyi-vue-plus/framework/basic/interface_release.md) + * [多租户功能](/ruoyi-vue-plus/framework/basic/tenant.md) + * [第三方授权功能](/ruoyi-vue-plus/framework/basic/social.md) + * [客户端管理功能](/ruoyi-vue-plus/framework/basic/client.md) + * 扩展功能 + * [多数据源](/ruoyi-vue-plus/framework/extend/dynamic_datasource.md) + * [短信模块](/ruoyi-vue-plus/framework/extend/sms.md) + * [邮件功能](/ruoyi-vue-plus/framework/extend/mail.md) + * [防重幂等](/ruoyi-vue-plus/framework/extend/idempotent.md) + * [数据脱敏](/ruoyi-vue-plus/framework/extend/sensitive.md) + * [API加解密](/ruoyi-vue-plus/framework/extend/api_encrypt.md) + * [数据加解密](/ruoyi-vue-plus/framework/extend/encrypt.md) + * [翻译功能](/ruoyi-vue-plus/framework/extend/translation.md) + * [WebSocket功能](/ruoyi-vue-plus/framework/extend/websocket.md) + * [SSE功能](/ruoyi-vue-plus/framework/extend/sse.md) + * [Skywalking链路监控](/ruoyi-vue-plus/framework/extend/skywalking.md) + * [对接MaxKey单点登录](/ruoyi-vue-plus/framework/extend/maxkey.md) + * [对接TOPIAM单点登录](/ruoyi-vue-plus/framework/extend/topiam.md) + * 功能说明 + * [事务相关](/ruoyi-vue-plus/framework/explain/transaction.md) + * [单元测试](/ruoyi-vue-plus/framework/explain/test.md) + * [主键使用说明](/ruoyi-vue-plus/framework/explain/key.md) + * [关于多表查询](/ruoyi-vue-plus/framework/explain/about_join.md) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/changlog.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/changlog.md new file mode 100644 index 00000000..ac8eb907 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/changlog.md @@ -0,0 +1,2028 @@ +# 更新日志 +- - - + +## v5.2.2 - 2024-08-26 + +### 重大改动 + +* 增加 ruoyi-common-sse 模块 支持SSE推送 比ws更轻量更稳定的推送 +* 增加 springboot snailjob 等 actuator 账号密码认证 杜绝内外网信息泄漏问题 +* 增加 重构代码生成器 集成anyline开源框架 支持400+种数据库适配 + +### 依赖升级 + +* update springboot 3.2.6 => 3.2.9 +* update snailjob 1.0.1 => 1.1.2 +* update mapstruct-plus 1.4.3 => 1.4.4 +* update hutool 5.8.27 => 5.8.31 解决hutool不兼容jakarta问题 +* update anyline 8.7.2-20240808 +* update sms4j 3.2.1 => 3.3.2 +* update redisson 3.31.0 => 3.34.1 +* update mapstruct-plus 1.3.6 => 1.4.3 +* update lombok 1.18.32 => 1.18.34 +* update easyexcel 3.3.4 => 4.0.2 +* update springdoc 2.5.0 => 2.6.0 +* update flowable 7.0.0 => 7.0.1 + +### 功能更新 + +* update 优化 去除日志部署环境判断 通过日志级别控制 +* update 优化 忽略租户与忽略数据权限支持嵌套使用(感谢 amadeus5201) +* update 优化 租户相关controller 增加租户开关配置控制是否注册 +* update 优化 移除 alibaba ttl 与线程池搭配有问题(可传递但无法清除与更新) +* update 优化 个人中心编辑 忽略数据权限 +* update 优化 兼容部分用户不想给用户分配角色与部门的场景 +* update 优化 租户套餐重名校验 +* update 优化 部门下存在岗位不允许删除 +* update 优化 角色编辑状态未校验问题 +* update 优化 用户脱敏增加编辑权限标识符 +* update 优化 代码生成器 自动适配oss翻译 +* update 优化 临时升级 undertow 版本 解决虚拟线程溢出问题 +* update 优化 支持通过配置文件关闭工作流 +* update 优化 增加mybatis-plus填充器兜底策略 +* update 优化 TenantSpringCacheManager 处理逻辑 +* update 优化 角色权限判断 +* update 优化 增加删除标志位常量优化查询代码 +* update 优化 监控使用独立web依赖 +* update 优化 更多脱敏策略(感谢 hemengji) +* update 优化 设置nginx sse相关代理参数 +* update 优化 调整默认推送使用SSE +* update 优化 Monitor监控服务通知分类打印(感谢 AprilWind) +* update 优化 限流注解 又写key又不是表达式的情况 +* update 优化 WorkflowUtils查询用户信息发送消息未查询邮件和手机号(感谢 yanzy) +* update 优化 注释掉其他数据库 jdbc 依赖 由用户手动添加 +* update 优化 oracle snailjob 兼容低版本oracle索引名称长度限制 +* update 优化 数据权限支持通过菜单标识符获取数据所有权 +* update 优化 数据权限支持自定义连接符 +* update 优化 TestDemo 删除前校验数据权限 +* update 优化 更换docker镜像底层系统 避免无字体情况 + +### 问题修复 + +* fix 修复 三方登录构建去除无用代码 +* fix 修复 多线程对同一个session发送ws消息报错问题 +* fix 修复 依赖漏洞 限制部分依赖版本 +* fix 修复 excel 基于其他字段 合并错误问题 +* fix 修复 一级缓存key未区分租户问题 +* fix 修复 id字符串格式转换错误问题 +* fix 修复 登出无法正确删除对应的租户数据问题 +* fix 修复 登录错误锁定不区分租户问题 +* fix 修复 转换模型缺少分类字段 +* fix 修复 权限标识符处理未设置成功状态问题 +* fix 修复 无法导入 bpmn 类型文件问题 + +### 前端改动 + +* update element-plus 2.7.5 => 2.7.8 +* update vue 3.4.25 => 3.4.34 +* update vite 5.2.10 => 5.2.12 +* add 增加 使用 vueuse 编写 sse 推送功能 +* update 优化 使用匹配模式简化预编译配置 +* update 优化 时间搜索组件统一 +* update 优化 oss 配置按钮 使用ossConfig权限标识符与oss权限分离 +* update 优化 类型报错问题 +* update 优化 切换租户后刷新首页 +* update 优化 实现表格行选中切换 +* update 优化 使用 vueuse 重构 websocket 实现 +* update 优化 代码生成器编辑页禁用缓存 防止同步后页面不更新问题 +* update 优化 调整默认推送使用SSE +* fix 修复 租户套餐导出路径错误问题 +* fix 修复 登出后重新登录 sse推送报错问题 + + +## v5.2.1 - 2024-07-09 + +### 功能更新 + +* update 优化 更改prod环境 snailjob状态 默认启用 +* update 优化 替换过期方法 +* update 优化 租户列表接口 避免登录之后列表被域名过滤 +* update 优化 获取用户账户方法 LoginHelper#getUsername(感谢 AprilWind) +* update 优化 用户ID查询角色列表代码实现(感谢 AprilWind) +* update 优化 大数据量下join卡顿问题 使用子查询提高性能 +* update 优化 修改路由name命名规则 防止重复路由覆盖问题(感谢 玲娜贝er) +* update 优化 修改 snailjob 默认端口 避免与系统内置端口冲突问题 +* update 优化 isTenantAdmin 空校验 +* update 优化 webscoket 配置与异常拦截 +* update 优化 更新 redis 密码策略(密码必填 升级需注意) +* update 优化 更新使用 Spring 官方推荐 JDK +* update 优化 StreamUtils 抽取 findFirst findAny 方法 +* update 优化 工作流相关代码方法 + +### 问题修复 + +* fix 修复 postgres flowable sql 缺失字段问题 +* fix 修复 新版上传未设置acl问题 +* fix 修复 get路径特殊规则 导致 actuator 泄漏问题 [issue#4f9ceb0a](https://gitee.com/dromara/RuoYi-Vue-Plus/commit/4f9ceb0a8057284a0d9d69da58df630d8bc2e84f) +* fix 修复 pg数据库 用户查询报错问题 +* fix 修复 isLogin 方法抛异常无法正常返回值问题 + +### 前端改动 + +* update 优化 工作流选人改为懒加载窗口 +* update 优化 路由name重复检查 +* update 优化 eslint 语法 +* update 优化 动态创建组件实例时, 设置路由name为组件名 解决缓存问题 +* fix 修复 由于没有await 导致执行顺序不可控 +* fix 修复 富文本编辑器 添加之后内容未清理问题 + +## v5.2.0 - 2024-06-20 + +### 重大改动 + +* 集成 flowable 增加工作流相关功能(感谢 May) +* 集成 snailjob 移除 powerjob(投诉的人太多使用成本太高)(感谢 dhb52) +* 升级 aws s3 升级到 2.X 性能大幅提升 +* 优化 数据权限 数据加密 使用预扫描mapper注解提升代码性能(感谢 老马) +* 新增 caffeine 减少将近90%的redis查询提高性能 + +### 依赖升级 + +* update springboot 3.1.7 => 3.2.6 支持虚拟线程 +* update springboot-admin 3.1.8 => 3.2.3 +* update mybatis-plus 3.5.4 => 3.5.7 适配更改代码 +* update springdoc 2.2.0 => 2.5.0 +* update easyexcel 3.3.3 => 3.3.4 +* update redisson 3.24.3 => 3.31.0 +* update lombok 1.18.30 => 1.18.32 +* update sms4j 2.2.0 => 3.2.1 支持自定义配置key 可用于多厂商多租户等 +* update satoken 1.37.0 -> 1.38.0 +* update hutool 5.8.22 => 5.8.26 +* update mapstruct-plus 1.3.5 => 1.3.6 +* update lock4j 2.2.5 => 2.2.7 +* update dynamic-ds 4.2.0 => 4.3.1 + +### 功能更新 + +* update 优化 三方登录不同域名问题 采用新方案 +* update 优化 获取aop代理的方式 减少与其他使用aop的功能冲突的概率 +* update 优化 token无效时关闭ws连接(感谢 AprilWind) +* update 优化 移除表单构建菜单(没有可用组件 用处不大以后再考虑) +* update 优化 切换动态租户 默认线程内切换(如需全局 手动传参) +* update 优化 代码生成注释,删除无用引入(感谢 AprilWind) +* update 优化 代码生成 el-radio 标签过期属性 +* update 优化 异常处理器自动配置 +* update 优化 文件下载使用对流下载降低内存使用(感谢 PhoenixL) +* update 优化 去除gc日志参数(有需要自己加) +* update 优化 拆分异常处理器 +* update 优化 常规web异常状态码 +* update 优化 设置静态资源路径防止所有请求都可以访问静态资源 +* update 优化 redis 对Long值的存储类型不同问题 +* update 优化 去除加密请求类型限制 +* update 优化 mp多租户插件注入逻辑 +* update 优化 RedisUtils 支持忽略租户 +* update 优化 更新ip地址xdb文件 +* update 优化 验证码背景色改为浅灰色 +* update 优化 mybatis依赖设置为可选依赖 避免出现不应该注入的情况 +* update 优化 GET 方法响应体支持加密 +* update 优化 excel插件合并策略 去除被合并单元格的非首行内容(感谢 司猫子) +* update 优化 下拉选接口数据权限 +* update 优化 OssFactory 获取实例锁性能 +* update 优化 使用翻译注解简化用户查询 调整用户查询逻辑 +* update 优化 框架整体提高查询性能 +* update 优化 将p6spy配置文件统一放置到 common-mybatis 插件包内 + +### 新增功能 + +* add 新增 分布式锁Lock4j异常拦截器 +* add 新增 个人中心-在线设备管理 +* add 新增 岗位编码与部门编码并将岗位调整到部门下(感谢 AprilWind) +* add 新增 BaseMapperPlus提供可选是否抛异常selectVoOne方法(感谢 秋辞未寒) +* add 新增 用户、部门、角色、岗位 下拉选接口与代码实现优化 +* add 增加 StringUtils.isVirtual 方法 +* add 增加 JustAuth 整合 TopIam 单点登录 + +### 问题修复 + +* fix 修复 websocket clientid 参数不走mvc拦截器 无法生效问题 +* fix 修复 oss未使用租户 拼接租户id null问题 +* fix 修复 用户昵称修改后未清除对应缓存问题(感谢 zhuweitung) +* fix 修复 图片预览问题(感谢 AprilWind) +* fix 修复 三方账号可以绑定多平台账号问题 +* fix 修复 主建错别字(感谢 good) +* fix 修复 兼容redis5.0出现的问题 +* fix 修复 部分浏览器无法获取加密响应头问题 +* fix 修复 用户未设置部门 登录报错问题 +* fix 修复 excel 表达式字典 下拉框导出格式错误 +* fix 修复 提升锁的作用域 并采用双重校验锁(感谢 fanc) +* fix 修复 用户登录查询部门缓存无法获取租户id问题 +* fix 修复 关闭租户功能 三方登录报错问题 + + +### 前端改动 + +* update element-plus 2.7.5 +* update vite 5.2.10 +* update vue 3.4.25 +* update vue-router 4.3.2 +* update nodejs 升级到最低 18.18.0 +* update 优化 跟密码相关的默认前端关闭防重功能 +* update 优化 点击左边菜单时页面空白或者刷新整个页面的问题 +* update 优化 el-select 与 el-input 全局样式 +* update 优化 首页打开topNav不展开菜单问题 +* update 优化 支持全局开启或关闭接口加密功能 +* update 优化 密码校验策略增加非法字符限制 +* update 优化 图片上传组件增加压缩功能支持 可自行开关(感谢 fengheguai) +* update 优化 request请求类判断请求头方式 +* update 优化 更改客户端状态接口 使用clientId传参 +* update 优化 ws开关改为常开(vite5修复了崩溃bug) +* fix 修复 移动端下 无法展开菜单问题 +* fix 修复 面板因为min width原因收缩不全 +* fix 修复 文件预览大写后缀不展示的问题(感谢 北桥) +* fix 修复 i18n无感刷新问题 +* fix 修复 websocket 非index页面刷新无法重连问题 + +## v5.1.2 - 2023-12-22 + +### 依赖升级 + +* update springboot 3.1.5 => 3.1.7 +* update mybatis-boot 3.0.2 => 3.0.3 优化依赖传递 +* update powerjob 4.3.3 => 4.3.6 +* update easyexcel 3.3.2 => 3.3.3 +* update transmittable-thread-local 2.14.2 => 2.14.4 +* update justauth 1.16.5 => 1.16.6 +* update redisson 3.24.1 => 3.24.3 修复订阅重启连接超时问题 + +### 功能更新 + +* update 优化 为 admin 模块 单独增加 ratelimiter 模块 +* update 优化 验证码接口 增加限流配置 +* update 优化 excel合并注解会根据第一合并列的结果来决定后续的列合并 (感谢 Simple) +* update 优化 SocialUtils 代码 +* update 优化 删除无用异常类 +* update 优化 补全三方登录校验国际化 +* update 优化 sms组件 预留自动配置类 +* update 更新 关于数据库的说明 +* update 优化 sms组件 预留自动配置类 +* update 优化 将 OSS配置 改为全局模式 降低使用难度 保留sql便于用户自行扩展(常规项目用不上配置分多租户) +* update 优化 细化oss配置管理权限控制 +* update 优化 开启 redisson 脚本缓存 减少网络传输 +* update 优化 删除 hikaricp 官方不推荐使用的配置 jdbc4 协议自带校验方法 +* update 优化 减少 PlusSaTokenDao 不必要的查询优化性能 +* update 优化 更新用户异常提示 使用登录账号 +* update 优化 使用登录用户判断是否登录 提高效率 +* update 优化 重构 LoginHelper 将本地存储代码操作封装 +* update 优化 getTenantId 判断是否开启多租户 +* update 优化 Dockerfile 使用shell模式 支持环境变量传入jvm参数 +* update 优化 WebSocketUtils 连接关闭改为警告 +* update 优化 excel多sheet页导出 (感谢 May) +* update 优化 删除无用接口实现 +* update 优化 jvm参数调整 全面启用zgc +* update 优化 使用动态租户重构业务对租户的逻辑 +* update 优化 TenantHelper 动态租户支持函数式方法 +* update 优化 支持多租户绑定相同的三方登录 +* update 优化 更新用户登录信息方法忽略数据权限 +* update 优化 补全三方绑定时间字段 删除无用excel注解 +* update 优化 将登录记录抽取到监听器统一处理 +* update 优化 租户插件 ignoreTable 方法支持动态租户 + +### 新增功能 + +* add 新增 RedisUtils.setObjectIfExists 如果存在则设置方法 +* add 新增 丰富RedisUtils对List Set类型的操作 +* add 新增 翻译组件 用户昵称翻译实现 +* add 新增 响应加密功能 支持注解强制加密接口数据 (感谢 MichelleChung) + +### 问题修复 + +* fix 修复 selectDictTypeByType 查询方法错误问题 +* fix 修复 一些不正常类无法加载报错问题 +* fix 修复 powerjob sql脚本针对其他数据库转义符问题 (感谢 branches) +* fix 修复 MybatisSystemException 空指针问题 +* fix 修复 excel合并注解会根据第一合并列的结果来决定后续的列合并 +* fix 修复 session 多账号共用覆盖问题 改为 tokenSession 独立存储 +* fix 修复 token 失效后 登录获取用户null问题 +* fix 修复 powerjob部署方案 高版本nginx不生效问题 +* fix 修复 OssFactory 并发多创建实例问题 +* fix 修复 延迟队列在投递消息未到达时间的时候 服务死机导致重启收不到消息 + +### 前端改动 + +* update 优化 用户头像 img 变量无确定类型问题 +* update 优化 细化oss配置管理权限控制 +* update 优化 明确打包命令 +* update 优化 代码中存在的警告 +* update 优化 前端白名单页面放行逻辑 +* update 优化 页面关于权限标识符说明 +* fix 修复 append-to-body 编写错误 (感谢 Ai3_刘小龙) +* fix 关闭动态路由tab页签时不清理组件缓存 (感谢 NickLuo) +* fix 删除重复环境变量ElUploadInstance (感谢 棉花) +* fix 修复 在线用户 强推按钮点击取消控制台警告问题 +* fix 修复 字典使用 default 样式报警告问题 + +## v4.8.2 - 2023-11-27 + +### 依赖升级 + +* update springboot 2.7.17 => 2.7.18 升级到2.X最终版本(官方停更) +* update mybatis-plus 3.5.3.2 => 3.5.4 +* update springboot 2.7.14 => 2.7.17 +* update satoken 1.36.0 => 1.37.0 +* update aws-java-sdk-s3 1.12.400 => 1.12.540 +* update vue-quill 1.1.0 => 1.2.0 + +### 功能更新 + +* update 优化 页面关于权限标识符说明 +* update 优化 数据权限拦截器优先判断方法是否有效 提高性能减少无用sql解析 +* update 优化 部门数据权限使用默认兜底方案 +* update 优化 更改默认日志等级为info 避免日志过多(按需开启debug) +* update 优化 补全代码生成 columnList 接口参数注解缺失 +* update 优化 操作日志 部门信息完善 vue3页面 +* update 优化 AddressUtils 兼容linux系统本地ip +* update 优化 操作日志 部门信息完善 (感谢 柏竹) +* update 优化 数据权限 减少二次校验查询 +* update 优化 vue3 版本用户初始密码从字典查询 +* update 优化 富文本Editor组件检验图片格式 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 全局数据存储用户编号 +* update 优化 菜单管理类型为按钮状态可选 + +### 问题修复 + +* fix 修复 OssFactory 并发多创建实例问题 +* fix 修复 demo的form字段有误 (感谢 dhb52) +* fix 修复 延迟队列在投递消息未到达时间的时候 服务死机导致重启收不到消息 +* fix 修复 数据权限优化后 update delete 报null问题 +* fix 修复 五级路由缓存无效问题 +* fix 修复 oss服务无法连接 导致业务异常问题 查询不应该影响业务 +* fix 修复 内链iframe没有传递参数问题 +* fix 修复 外链带端口出现的异常 +* fix 修复 普通角色编辑使用内置管理员code越权问题 +* fix 修复 代码生成 是否必填与数据库不匹配问题 +* fix 修复 HeaderSearch组件跳转query参数丢失问题 +* fix 修复 树结构代码生成新增方法赋值错误 (感谢 这夏天依然平凡) + +## v5.1.1 - 2023-11-14 + +### 依赖升级 + +* update springboot 3.1.3 => 3.1.5 +* update springboot 2.7.14 => 2.7.17(扩展服务) +* update springboot-admin 3.1.5 => 3.1.7 +* update satoken 1.35.0.RC => 1.37.0 +* update mybatis-plus 3.5.3.2 => 3.5.4 适配mp新版本改动 +* update dynamic-ds 4.1.3 => 4.2.0 +* update bouncycastle 1.72 => 1.76 +* update poi 5.2.3 => 5.2.4 +* update redisson 3.23.2 => 3.24.1 +* update hutool 5.8.20 => 5.8.22 +* update lombok 1.18.26 => 1.18.30(适配支持jdk21) +* update vue-quill 1.1.0 => 1.2.0 + +### 功能更新 + +* update 优化 数据权限拦截器优先判断方法是否有效 提高性能减少无用sql解析 +* update 优化 适配 maxkey 新版本 +* update 优化 @Sensitive脱敏增加角色和权限校验 (感谢 盘古给你一斧) +* update 优化 部门数据权限使用默认兜底方案 +* update 优化 更改默认日志等级为info 避免日志过多(按需开启debug) +* update 优化 登录策略代码优化(感谢 David Wei) +* update 优化 补全代码生成 columnList 接口参数注解缺失 +* update 优化 nginx 配置支持 websocket +* update 优化 notice 新增通知公告发送ws推送 +* update 优化 websocket 模块减少日志输出 增加登录推送 +* update 优化 重构登录策略增加扩展性降低复杂度 +* update 优化 addressUtils 兼容linux系统本地ip +* update 优化 补全操作日志部门数据 +* update 优化 支持数据库操作在非web环境下切换租户 +* update 优化 排除powerjob无用的依赖 减少打包30M体积 +* update 优化 删除 satoken yml 时间配置 此功能已迁移至客户端管理 +* update 优化 redis 集群模式注释说明 +* update 优化 客户端禁用限制 +* update 优化 登录日志, 在线用户展示信息(增加 客户端, 设备类型)(感谢 MichelleChung) +* update 优化 限制框架中的fastjson版本 +* update 优化 数据权限 减少二次校验查询 +* update 优化 将部门id存入token避免过度查询redis +* update 优化 增加租户ID为Null错误日志 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 通过参数键名获取键值接口的返回体(感谢 David Wei) +* update 优化 为 sys_grant_type 字典增加样式 +* update 优化 代码生成 页面输入框样式 +* update 优化 全业务分页查询增加排序规则避免因where条件导致乱序问题 +* update 优化 登录接口租户id被强制校验问题 +* update 优化 加密模块 支持父类统一使用加密注解(感谢 Tyler Ge) +* update 优化 将graalvm镜像更新为openjdk镜像 需要的人自行切换即可 +* update 优化 部分使用者乱设权限导致无法获取用户信息 增加权限提示 +* update 优化 表格列的显示与隐藏小组件(感谢 bestrevens) +* update 优化 增加表单构建不能使用说明 +* update 优化 富文本Editor组件检验图片格式 +* update 优化 操作日志列表新增IP地址查询 +* update 优化 菜单管理类型为按钮状态可选 +* update 优化 用户初始密码从参数配置查询 +* update 优化 通过参数键名获取键值接口的返回体(感谢 David Wei) +* update 优化 字典标签支持数组和多标签(感谢 抓蛙师) + +### 新增功能 + +* add 新增 websocket 群发功能 +* add 新增 前端接入websocket接收消息(感谢 三个三) + +### 问题修复 + +* fix 修复 oss服务无法连接 导致业务异常问题 查询不应该影响业务 +* fix 修复 租户id为null 无法匹配字符串导致的嵌套key问题 +* fix 修复 部门管理orderNum排序失效问题 +* fix 修复 外链带端口出现的异常 +* fix 修复 普通角色编辑使用内置管理员code越权问题 +* fix 修复 代码生成 是否必填与数据库不匹配问题 +* fix 修复 用户注册接口校验用户名不区分租户问题 +* fix 修复 错误增加组导致的校验不生效问题 +* fix 修复 新增校验主键id问题 +* fix 修复 powerjob 使用 nginx 部署无法访问的问题 +* fix 修复 SysUserMapper 内标签使用错误(不影响使用) +* fix 修复 新增或编辑 SysOssConfig 数据后 推送到 redis 数据不完整 +* fix 修复 树表生成查询变量使用错误 +* fix 修复 个人信息修改密码接口隐藏新旧密码参数明文(感谢 bleachtred) +* fix 修复 删除字段后 * update sql 未更新问题 +* fix 修复 三方登录支付宝source与实际支付宝业务code不匹配问题 +* fix 修复 五级路由缓存无效问题 +* fix 修复 内链iframe没有传递参数问题 +* fix 修复 绑定第三方帐号参数“wechar”更正为“wechat” (感谢 scmiot) +* fix 修复 用户注册缺失 clientid 问题 +* fix 修复 HeaderSearch组件跳转query参数丢失问题 +* fix 修复 自定义字典样式不生效的问题 +* fix 修复 login 页面 loading 未关闭问题 + +## v4.8.1 - 2023-09-25 + +### 依赖升级 + +* update springboot 2.7.15 => 2.7.16 +* update springboot-admin 2.7.10 => 2.7.11 +* update satoken 1.35.0.RC => 1.36.0 +* update lombok 1.18.26 =. 1.18.30 +* update mybatis-plus 3.5.3.1 => 3.5.3.2 +* update easyexcel 3.3.1 => 3.3.2 +* update hutool 5.8.18 => 5.8.20 + +### 功能更新 + +* update 优化 重置密码注释参数中文解释错误 +* update 优化 getTokenActivityTimeout => getTokenActiveTimeout +* update 优化字典标签支持传分隔符分隔的字符串和数组,优化渲染效果 +* update 优化 控制台debuger位置错误问题 +* update 优化 TopNav 菜单样式 +* update 优化 全局异常处理器 业务异常不输出具体堆栈信息 减少无用日志存储 +* update 优化 用户管理 只查询未禁用的部门角色岗位数据 +* update 优化 岗位如果绑定了用户则不允许禁用 +* update 优化 部门与角色如果绑定了用户则不允许禁用 +* update 优化 加密实现 使用 EncryptUtils 统一处理 +* update 优化 excel导出字典转下拉框 无需标记index自动处理 +* update 优化 excel 导出字典默认转为下拉框 +* update 优化 删除一些跟 swagger 有关的字眼 避免误解 +* update 优化 角色权限支持仅本人权限查看 解决无法查看自己创建的角色问题 +* update 优化 RedisCacheController 注释错误 +* update 优化 xxljob 端口随着主应用端口飘逸 避免集群冲突 +* update 优化 powerjob 端口随着主应用端口飘逸 避免集群冲突 + +### 问题修复 + +* fix 修复 代码生成后 vo 定义 'serialVersionUID' 字段的不可序列化类 +* fix 修复 自定义字典样式不生效的问题 +* fix 修复 布局配置失效问题 +* fix 修复 新建用户可能会存在的越权行为 +* fix 修复 字典缓存删除方法参数错误问题 +* fix 修复 修复树模板父级编码变量错误 +* fix 修复 有界队列与优先队列 错误使用问题 +* fix 修复 升级 mp 版本导致的问题 +* fix 修复 vue3 版本注册页验证码不显示问题 +* fix 修复 加密模块数据转换异常问题 +* fix 修复 动态设置 token 有效期不生效问题 +* fix 修复 token 过期登出无法清理在线用户问题 + + +## v5.1.0 - 2023-09-05 + +# 开发历程 + +* 2023年5月 开始 5.1.0 计划 历经1个月的设计与讨论 +* 2023年6月 开始着手开发 历经2个多月的开发 特别感谢团队的小伙伴与一些热心的粉丝 参与功能开发与测试 +* 2023年8月 开始公测 历经将近1个月的公测与修复工作(期间成功支持多位使用者生产使用) +* 2023年9月初 正式发布(经过多个小伙伴的生产实践 已基本可尝试生产使用) +> 关于4.X的说明 由于SpringBoot2.X与vue2.X均在11月底停止维护
+> 故而咱们vue版本4.X也无法再继续更新
+> 介于4.X的用户量特别庞大 功能也非常的稳定
+> 计划于11月底同Boot2.X一同停止更新但还会持续维护修复bug(修复的形式为直接提交到4.X分支停止发版)
+ +# 视频介绍 + +为了更好的让大家了解 5.1.0 作者录制了相关的视频 供大家快速了解上手 + +* 5.1.0 新功能与变更介绍: https://www.bilibili.com/video/BV1fj411y71X/ +* 搭建与运行: https://www.bilibili.com/video/BV1Fg4y137JK/ +* 生产环境搭建部署: https://www.bilibili.com/video/BV1mL411e7ha/ + +# 更新日志 + +### 重大更新 + +* [重大更新] 优化 相关代码 完成代码生成多数据源统一存储(感谢 WangBQ !pr349) +* [不兼容更新] 移除 原短信功能 集成更强大的 sms4j 短信工具包(感谢 友杰 !pr367) +* [不兼容更新] 对接 powerjob 实现分布式任务调度 删除原有 xxljob 原因为社区不更新功能太少只支持mysql(感谢 yhan219 !pr359) +* [重大更新] 新增 三方授权绑定登录功能 基于 justauth 支持市面上大部分三方登录(感谢 三个三 !pr370) +* [不兼容更新] 新增 客户端授权功能 不需要更改任何代码即可完成多端动态对接(感谢 Michelle.Chung !pr379) +* [重大更新] 新增 前后端接口请求加密传输 基于AES+RSA动态高强度加密(感谢 wdhcr !pr377) +* [重大更新] 新增 三方授权登录 对接 maxkey 单点登录 +* [不兼容更新] 优化 redis序列化配置 更改为通用格式(升级需清除redis所有数据) + +### 依赖升级 + +* update springboot 3.0.7 => 3.1.3 +* update springboot-admin 3.1.3 => 3.1.5 +* update springdoc 2.1.0 => 2.2.0 +* update spring-mybatis 3.0.1 => 3.0.2 +* update mybatis-plus 3.5.3.1 => 3.5.3.2 +* update easyexcel 3.2.1 => 3.3.2 +* update mapstruct-plus 1.2.3 => 1.3.5 解决修改实体类 idea不编译问题 +* update satoken 1.34.0 => 1.35.0.RC 优化过期配置 支持多端token自定义有效期 +* update dynamic-ds 3.6.1 => 4.1.3 支持 SpringBoot3 +* update sms4j 2.2.0 +* update hutool 5.8.18 => 5.8.20 +* update redisson 3.20.1 => 3.23.4 +* update lock4j 2.2.4 => 2.2.5 +* update aws-java-sdk-s3 1.12.400 => 1.12.540 +* update maven-surefire-plugin 3.0.0 => 3.1.2 + +### 功能更新 + +* update 优化 excel 导出合并 在初始化类时进行数据的处理 +* update 优化 简化 flatten 插件语法写法 +* update 优化 支持本地虚拟域名调试(感谢 代星登 !pr363) +* update 重构 将框架内的 swagger 命名更改为 springdoc 命名避免误解 +* update 重构 将系统内置配置放置到 common 包内独立加载 不允许用户随意修改 +* update 优化 切换 maven 仓库到 华为云(aliyun依赖不更新拉取不到) +* update 优化 升级 satoken 支持多端 token 自定义有效期功能 +* update 优化 RepeatSubmitAspect 逻辑避免并发请求问题 +* update 优化 在全局异常拦截器中增加两类异常处理 +* update 优化 提供powerjob完整sql脚本 降低用户使用难度 +* update 优化 StreamUtils 其他方法过滤null值(感谢 bleachtred !pr390) +* update 优化 powerjob 端口随着主应用端口飘逸 避免集群冲突 +* update 优化 角色权限支持仅本人权限查看 解决无法查看自己创建的角色问题 +* update 修改代码生成模版,日期范围统一采用addDateRange方法(感谢 LiuHao !pr397) +* update 优化 树表生成前端缺少 children 字段 +* update 优化 CryptoFilter null判断工具 +* update 优化 websocket 路径与 cloud 版本保持一致 +* update 优化 更新登录策略返回值(感谢 zlyx) +* update 修改代码生成模板,调整列表打开对话框和接口请求顺序 +* update 优化 SaInterceptor 拦截器判断 token 客户端id是否有效(感谢 zlyx !pr402) +* update 优化 excel 导出字典默认转为下拉框 +* update 优化 兼容 clientid 通过 param 传输 +* update 优化 excel导出字典转下拉框 无需标记index自动处理(感谢 一夏coco) +* update 优化 简化线程池配置 +* update 优化 屏蔽 powerjob 无用的心跳日志 +* update 优化 适配 mysql 8.0.34 升级连接机制 +* update 优化 加密实现 使用 EncryptUtils 统一处理 +* update 优化 删除字典无用状态字段(基本用不上 禁用后还会导致回显问题) +* update 优化 部门与角色如果绑定了用户则不允许禁用 +* update 优化 岗位如果绑定了用户则不允许禁用 +* update 优化 用户管理 只查询未禁用的部门角色岗位数据 +* update 优化 登录用户增加昵称返回 +* update 优化 将部门管理 负责人选项改为下拉框选择(感谢 Lionel !pr410) +* update 优化 全局异常处理器 业务异常不输出具体堆栈信息 减少无用日志存储 +* update 优化 登录用户缓存 去除冗余统一存储 +* update 优化 放宽菜单权限 角色关联菜单无需管理员 + +### 新增功能 + +* add 增加 RedisUtils 批量删除 hash key 方法 +* add 新增 Oss 上传 File 文件方法(感谢 jenn !pr362) +* add 增加 excel 导出下拉框功能 +* add 新增 RedisUtils.setObjectIfAbsent 如果不存在则设置方法 + +### 修复问题 + +* fix 修复 脱敏注解标记位置错误 +* fix 修复 OssClient 实例多租户相同key缓存覆盖问题 +* fix 修复 关闭多租户 脱敏判断问题 +* fix 修复 OssClient 切换服务 实例不正确问题(感谢 jenn !pr360) +* fix 修复 传参类型不正确导致 postgreSql 同步套餐报错问题 +* fix 修复 参数类型修改 未修改校验注解 +* fix 修复 登录校验错误次数未达到上限时 错误次数缓存未设置有效时间问题(感谢 konbai !pr366) +* fix 修复 common-core 包使用aop注解 但未添加aop实现类导致单独使用报错问题 +* fix 修复 Mapper 多参数未加 @Param 注解问题 +* fix 修复 邮箱登录 查询值错误问题 +* fix 修复 用户篡改管理员角色标识符越权问题 +* fix 修复 字典缓存注解使用错误问题 +* fix 修复 查询部门下拉树未过滤数据权限问题 +* fix 修复 CacheName 缓存key存储错误问题 +* fix 修复 代码生成 前端添加或修改表单修改列生成问题 +* fix 修复 新增角色使用内置管理员标识符问题 +* fix 修复 代码生成 前端添加或修改表单修改列生成问题 +* fix 修复 token 过期登出无法清理在线用户问题 +* fix 修复 加密模块数据转换异常问题 +* fix 修复 可能导致异常类无法反序列化问题 +* fix 修复 代码生成 编辑按钮刷新列表问题 +* fix 修复 客户端编辑时授权类型变更未保存的问题(感谢 David Wei !pr400) +* fix 修复 有界队列与优先队列 错误使用问题 +* fix 修复 修复树模板父级编码变量错误 +* fix 修复 部署部分系统出现乱码问题 +* fix 修复 一级菜单无法显示问题 +* fix 修复 可能会存在的越权行为(感谢 丶Stone !pr416) +* fix 修复 代码生成页面参数缺少逗号问题 + +### 移除功能 + +* remove 移除原有短信功能(建议使用sms4j) +* remove 移除xxljob功能(建议使用powerjob) + + +## v4.8.0 - 2023-07-10 + +### 重大更新 + +* [重大更新] 新增 sms4j 短信融合框架整合(支持数十种短信厂商接入、发送限制、负载均衡等功能) +* [不兼容更新] 移除 原短信功能(建议使用新 sms4j 功能) +* [重要迁移] 迁移 vue3 前端到主仓库统一维护 + +### 依赖升级 + +* update springboot 2.7.11 => 2.7.13 +* update satoken 1.34.0 => 1.35.0.RC +* update easyexcel 3.2.1 => 3.3.1 +* update sms4j 2.2.0 + +### 功能更新 + +* update 优化 StreamUtils 方法过滤null值 +* update 优化 页签在Firefox浏览器被遮挡 +* update 优化 在全局异常拦截器中增加两类异常处理 +* update 优化 下载zip方法增加遮罩层(感谢@梁剑锋) +* update 优化 用户昵称非空校验 +* update 优化 修改角色如果未绑定用户则无需清理 +* update 优化 RepeatSubmitAspect 逻辑避免并发请求问题 +* update 优化 satoken 过期配置 支持多端token自定义有效期 +* update 优化 加密注解注释错误 +* update 优化 切换 maven 仓库到华为云(aliyun 不可用) +* update 优化 excel 导出存在合并项时在初始化类时进行数据的处理避免多次调用(感谢@yueye) +* update 优化 重构 CellMergeStrategy 支持多级表头修复一些小问题 整理代码结构 + +### 新增功能 + +* add 新增 RedisUtils.setObjectIfAbsent 不存在则设置方法 +* add 新增 Excel 导出附带有下拉框(字典自动导出为下拉框) 可自定义多级下拉框(感谢@Emil.Zhang) +* add 新增 OssClient File 文件上传方法 +* add 增加 RedisUtils 批量删除 hash key 方法 + +### 问题修复 + +* fix 修复 新增角色使用内置管理员标识符问题 +* fix 修复 缓存监控图表 支持跟随屏幕大小自适应调整(感谢@抓蛙师) +* fix 修复 防重组件 错删注解问题 +* fix 修复 CacheName 缓存key存储错误问题 +* fix 修复 字典缓存注解使用错误问题 +* fix 修复 用户篡改管理员角色标识符越权问题 +* fix 修复 登录校验错误次数未达到上限时 错误次数缓存未设置有效时间问题 +* fix 修复 OssClient 切换服务 实例不正确问题 +* fix 修复 element ui 因版本而未被工具识别问题(感谢@梁剑锋) +* fix 修复 admin监控 切换tab页需要重复登录问题 + +## v5.0.0 - 2023-05-19 + +# 开发历程 + +* 2022年11月 开始5.X计划 历经2个月的设计与讨论 +* 2023年1月 开始着手开发 历经3个月的开发 特别感谢团队的小伙伴与一些热心的粉丝 参与功能开发与测试 +* 2023年4月 开始公测 历经将近2个月的公测与修复工作(期间成功支持多位使用者生产使用) +* 2023年5月底 正式发布 虽然已经有生产实践 但是springboot3.0与jdk17使用者还处于少数 另外5.X后续还有一些不兼容更新 求稳者建议在等一等 +* 关于4.X的说明 由于springboot2.X 与 vue2.X 匀在年底停止维护 故此4.X也将于年底同boot2一同停止维护 + +# 视频介绍 + +为了更好的让大家了解 5.X 作者录制了相关的视频 供大家快速了解上手 + +* 搭建与运行: https://www.bilibili.com/video/BV1Fg4y137JK/ +* 新功能与变更介绍: https://www.bilibili.com/video/BV1Us4y1m7ky/ +* 生产环境搭建部署: https://www.bilibili.com/video/BV1mL411e7ha/ + +# 更新日志 + +### 重大更新 + +* [不兼容升级] java 版本从 jdk 8 升级到 jdk 17 且需要使用 graalvm 运行(暂时未解决原生jdk存在的问题) +* [不兼容升级] springboot 升级 3.0 版本 +* [不兼容升级] 重构 项目模块结构 采用插件化结构 易扩展易解耦 +* [不兼容升级] com.sun.mail 更改为 jakarta.mail 修改最新写法 +* [不兼容升级] javax.servlet 替换为 jakarta.servlet 更新所有代码 +* [简化性升级] 默认开启复杂结构 resultMap 自动映射 简化xml编码(多结构实体需带上主键id) +* [数据库改动] 更新 create_by update_by 字段类型 (保存用户id) +* [数据库改动] 新增 create_dept 字段 (保存创建部门id) +* [不兼容更新] system 模块 所有实体类均使用 bo|vo 规范化 +* [重大更新] 新增 多租户功能设计 整体框架代码结构与数据库更改 +* [重大更新] 新增 mapstruct-plus 替换 BeanUtil 与 BeanCopyUtils 工具 +* [不兼容更新] 重构 登录注解接口与cloud版本统一接口路径 +* [不兼容更新] 重构 BaseMapperPlus接口 去除 `@param Mapper` 泛型 +* [不兼容更新] 移除 vue2 前端工程 全面启用 vue3 +* [重大更新] 新增 vue3 + TS 版本前端(独立仓库后续与Cloud版本共用) +* [重大更新] 增加 websocket 模块 支持token鉴权 支持分布式集群消息同步 +* [重大更新] 框架文档全面翻新 https://plus-doc.dromara.org + +### 依赖升级 + +* update java 1.8 => 17 +* update springboot 2.7.7 => 3.0.7 +* update springboot-admin 2.7.10 => 3.0.4 +* update springdoc 1.6.14 => 2.1.0 +* update lock4j 2.2.3 => 2.2.4 +* update dynamic-ds 3.5.2 => 3.6.1 +* update easyexcel 3.1.5 => 3.2.1 +* update hutool 5.8.11 => 5.8.18 +* update redisson 3.19.2 => 3.20.1 +* update lombok 1.18.24 => 1.18.26 +* update spring-boot.mybatis 2.2.2 => 3.0.1 +* update mapstruct-plus 1.2.3 +* update maven-compiler-plugin 3.10.1 => 3.11.0 +* update maven-surefire-plugin 3.0.0-M7 => 3.0.0 +* update docker mysql 8.0.31 => 8.0.33 +* update docker nginx 1.22.1 => 1.32.4 +* update docker redis 6.2.7 => 6.2.12 +* update docker minio RELEASE.2023-04-13T03-08-07Z + +### 功能更新 + +* update 适配 AsyncConfig 替换过期继承类改为实现 AsyncConfigurer 接口 +* update 适配 redis 新版本配置文件写法 +* update 适配 获取redis 监控参数接口 替换过期语法 +* update 适配 sa-token 替换新依赖 sa-token-spring-boot3-starter +* update 适配 springboot-admin 改为最新 spring-security 写法 +* update 适配 springdoc 新版本配置方式 +* update 适配 ServletUtils 更换继承 JakartaServletUtil +* update 适配 新序列化注解 +* update 优化 利用 resultMap 自动映射配置 简化 xml (非嵌套) +* update 优化 调整 system entity 实体与 controller 包结构 +* update 优化 实体类中校验注解的提示信息 +* update 优化 使用 jdk17 语法优化代码 +* update 优化 所有 properties 文件改为注解启用 +* update 更新 docker 基础镜像 graalvm java17 +* update 优化 用户头像 改为存储 ossId 使用转换模块转为 url 展示 +* update 优化 重构 CellMergeStrategy 支持多级表头修复一些小问题 整理代码结构 +* update 优化 登录流程代码注释 + +### 新增功能 + +* add 新增 flatten-maven-plugin 插件统一版本号管理 +* add 新增 ip2region 实现离线IP地址定位库 + +### 移除功能 + +* remove 移除 BeanCopyUtils 工具类 与 JDK17 不兼容 +* remove 移除 devtools 依赖 并不好用(建议直接用idea自带的热更) +* remove 移除 vue2 前端工程 统一使用 vue3 工程 + +## v4.7.0 - 2023-05-08 + +### 依赖升级 + +* update springboot 2.7.9 => 2.7.11 修复 DoS 漏洞 +* update xxljob 2.3.1 => 2.4.0 +* update minio 升级至最新版 避免低版本信息泄漏问题 +* update hutool 5.8.15 => 5.8.18 +* update redisson 3.20.0 => 3.20.1 + +### 功能更新 + +* update 优化 更改 sys_oss_config 表注释 避免误解 +* update 项目正式入驻 dromara 开源社区 更改项目地址 +* update 全新 logo 全新背景图(设计师打造) +* update 优化代码生成 同步操作使用批量处理 +* update 重写项目 readme 说明 +* update 修改controller中校验直接返回R.fail +* update 更换默认用户头像 +* update 优化 限流注解 key 支持简单 spel 表达式 +* update 优化弹窗后导航栏偏移的问题 +* update 优化$tab.closePage后存在非首页页签时不应该跳转首页 +* update delete build style +* update 优化选择图标组件 +* update 移除vue-multiselect样式 +* update 优化固定头部页签滚动条被隐藏的问题 +* update 按代码规范补全重写注解 +* update 优化 极端情况获取LoginUser可能为null问题 +* update 优化 更改系统所有服务日志配置文件命名为 logback-plus.xml 避免与其他框架默认配置冲突 +* update 优化 加解密模块 将null判断下推防止任何可能的null出现 +* update 优化 调整配置文件错误注释 +* update 优化 在线用户token获取方式 +* update 优化 用户更改角色 踢掉角色相关所有在线用户 +* update 优化 下拉图标选择组件优化:1.已选择图标高亮回显 2.滚动条采用el-scrollbar +* update 优化 Vue的DictTag组件 当value没有匹配的值时 展示空value +* update 优化 恢复翻页/切换路由滚动功能 + +### 新增功能 + +* add 新增 ip2region 实现离线IP地址定位库 +* add 增加 邮箱验证码发送接口 +* add 增加 邮箱登陆接口 +* add 增加 EncryptUtils 加解密安全工具类 可以处理base64,aes,sm4,sm2,rsa,md5,sha256加解密 +* add 增加 EncryptUtils 类中增加国密sm3的不可逆加密算法 +* add 新增 忽略数据权限写法 防止异常不执行关闭问题 + +### 问题修复 + +* fix 修复 代码生成 点选按钮不生效问题 +* fix 修复 用户密码更新无效问题 +* fix 修复 findInSet 在mysql下方法搜索非数字字段时 无引号报错问题 +* fix 修复 oracle postgres 数据库日志表索引创建错误 +* fix 角色列表关联多表sort值都一样 导致排序不稳定、临时表没有原来的主键顺序 +* fix 修复 DefaultExcelResult 单词拼写错误 +* fix 修复页面切换时布局错乱的问题 +* fix 修复tab栏“关闭其他”异常的问题 +* fix 修复 加解密拦截器 对象属性为null问题 +* fix 修复 取消oss预览状态修改 图标变化不正常问题 +* fix 修复 开启TopNav后一级菜单路由参数设置无效问题 +* fix 修复 路由跳转被阻止时vue-router内部产生报错信息问题 +* fix 修复 缓存列表:多次清除操作,提示不变的问题 + +## v4.6.0 - 2023-03-13 + +### 重大更新 + +[重大更新] add 新增 基于 Mybatis 实现数据库字段加解密功能 +[重大更新] add 新增 通用翻译注解及实现(部门名、字典、oss、用户名) + +### 依赖升级 + +* update springboot 2.7.7 => 2.7.9 +* update easyexcel 3.1.5 => 3.2.1 +* update redisson 3.19.1 => 3.20.0 +* update hutool 5.8.11 => 5.8.15 (13与14有问题勿使用) +* update springdoc 1.6.14 => 1.6.15 +* update aws-java-sdk-s3 1.12.373 => 1.12.400 +* update element-ui 2.15.10 => 2.15.12 +* update lombok 1.18.24 => 1.18.26 + +### 功能更新 + +* update 优化 实体类中校验注解的提示信息 +* update 优化 修改 oss 配置页面开关说明 避免造成误解 +* update 优化 框架代码书写格式 +* update 优化 调整连接池默认参数 +* update 优化 `DictDataMapper` 注解标注过期 推荐使用 `@Translation` 注解 +* update 优化 部门更新接口 清理缓存 +* update 优化 获取菜单数据权限接口 删除无用角色属性与逻辑 +* update 优化 调整连接池最长生命周期 防止出现警告 +* update 优化 连接池增加 `keepaliveTime` 探活参数 +* update 优化 `DataPermissionHelper` 增加 `开启/关闭` 忽略数据权限功能 +* update 重构 `OssFactory` 加载方式 改为每次比对配置做实例更新 +* update 优化 `SaToken` 自定义扩展类 改为配置类注入 便于扩展 +* update 优化 启用 `sqlserver` 高版本语法 简化sql脚本语法 +* update 优化 更新角色后踢掉所有相关的登录用户 用户量过大会导致redis阻塞卡顿(应粉丝要求) +* update 优化 翻译组件 支持返回值泛型 支持多种类型数据翻译(例如: 根据主键翻译成对象) +* update 优化 限流注解使用 `SpringEl` 表达式动态定义 Key 与 message 国际化支持 +* update 优化 限流功能 `redis key` 生成规则 以 `功能头+url+ip+key` 格式 +* update 优化 只拦截系统内存在的路径 减少不必要的拦截造成的性能消耗 +* update 优化 `tagsView` 右选框,首页不应该存在关闭左侧选项 +* update 优化 `copyright 2023` +* update 优化 监控页面图标显示 +* update 优化 日志注解支持排除指定的请求参数 +* update 优化 业务校验优化代码 +* update 优化 日志管理使用索引提升查询性能 +* update 优化 框架时间检索使用时间默认值 `00:00:00 - 23:59:59` +* update 优化 oss 预览使用 `ImagePreview` 组件 + + +### 新增功能 + +* add 新增 `BeanCopyUtils#mapToMap` 方法 +* add 新增 `StringUtils` `splitTo` 与 `splitList` 方法 优化业务代码 +* add 新增 `EasyExcel` `@ExcelEnumFormat` 枚举类数据翻译注解 + + +### 问题修复 + +* fix 修复 新版本 `Redisson` 存在与 `springboot 2.X` 的兼容性问题 +* fix 修复 vue3 模板点击删除按钮后弹框显示`[object Object]`或控制台报错的问题 +* fix 修复 接口问题开关不生效问题 +* fix 修复 前端优化文件下载出现的异常 +* fix 修复 修改密码日志存储明文问题 +* fix 修复 用户密码注解误删暴露问题 +* fix 修复 代码生成 使用 `postgreSQL` 数据库查出已删除的字段 + + +## v4.5.0 - 2023-01-12 + +### 重大更新 + +* [重大更新] 使用 spring 事件发布机制 重构登录日志与操作日志 支持多事件监听无入侵扩展 +* 例如: 可以增加一个监听者将日志上传至ES等存储 对原有逻辑无影响 + +### 依赖升级 + +* update springboot 2.7.6 => 2.7.7 +* update springboot-admin 2.7.7 => 2.7.10 +* update mybatis-plus 3.5.2 => 3.5.3.1 +* update redisson 3.18.0 => 3.19.1 +* update sa-token 1.33.0 => 1.34.0 +* update easyexcel 3.1.3 => 3.1.5 +* update springdoc 1.6.13 => 1.6.14 +* update snakeyaml 1.32 => 1.33 +* update hutool 5.8.10 => 5.8.11 +* update aws-s3 1.12.349 => 1.12.373 +* update aliyun-sms 2.0.22 => 2.0.23 +* update tencent-sms 3.1.635 => 3.1.660 +* update echarts 4.9.0 => 5.4.0 +* update vue3 element-plus 2.2.21 => 2.2.27 + +### 功能更新 + +* update 优化 BaseMapperPlus 使用 MP V3.5.3 新工具类 Db 简化批处理操作实现 +* update 优化 将环境配置放到 pom 文件上方 便于查看使用 +* update 优化 代码生成与框架主体使用相同的主键生成器 全局统一避免问题 +* update 优化 系统登录 使用单表查询校验用户 避免多次 join 查询 +* update 优化 删除 vue3 模板无用参数 +* update 优化 xss 包装器 变量命名错误 +* update 优化 重构 ExcelUtil 全导出方法支持 OutputStream 流导出 不局限于 response +* update 优化 maven 地址切换回 aliyun 仓库 +* update 优化 去除无用 guava 依赖管理 项目中已无此依赖 +* update 优化 springdoc 配置鉴权头写死问题 增加持久化鉴权头配置 +* update 优化 验证码结果使用 spel 引擎自动计算 +* update 优化 弹窗内容过多展示不全问题 +* update 优化 删除 fuse 无效选项 maxPatternLength +* update 优化 minio 安装警告 使用新版本参数 +* update 优化 使用 spring 事件机制 重构 OssConfig 缓存更新 +* update 优化 抽取 SysLoginService recordLogininfor 记录登录信息方法 简化日志记录 +* update 优化 使用 spring 事件发布机制 重构登录日志与操作日志 +* update 优化 单元格合并判断 cellValue 是否相等方法调整 +* update 优化 去除 RedisConfig 无用继承 + +### 新增功能 + +* add 增加 GET 请求提交日期参数 默认格式化配置 +* add 增加 RedisUtils 检查缓存对象是否存在方法 + +### 问题修复 + +* fix 修复 根据 key 更新参数配置报null问题 +* fix 修复 树形下拉不能默认选中 +* fix 修复 读取 generator.yml 中文乱码问题 +* fix 修复 代码生成图片/文件/单选时选择必填无法校验问题 +* fix 修复 修改参数键名时 未移除过期缓存配置 +* fix 修复 用户注册 用户类型字段书写错误 +* fix 修复 文件名包含特殊字符(+、-、*...)的文件无法下载问题 +* fix 修复 短信校验模板参数传参错误 +* fix 修复 vue3 closeSidebar 这个方法定义的参数没有解构问题 + +## v4.4.0 - 2022-11-28 + +### 重大更新 +* [重大更新] 优化支持 oss 私有库功能(数据库字段改动) #cd9c3c3f +* [重大更新] 连接池由 druid 修改为 hikari 更新相关配置(原因可看文档) #1f42bd3d +* [重大更新] 移除 tlog(不支持UI界面 使用的人太少) 建议使用 skywalking +* [重大更新] 增加 skywalking 集成 默认注释不开启(使用看文档) + +### 依赖升级 +* update springboot 2.7.5 => 2.7.6 +* update springboot-admin 2.7.6 => 2.7.7 +* update satoken 1.31.0 => 1.33.0 +* update spring-doc 1.6.12 => 1.6.13 +* update easyexcel 3.1.1 => 3.1.3 +* update hutool 5.8.8 => 5.8.10 +* update redisson 3.17.7 => 3.18.0 +* update lock4j 2.2.2 => 2.2.3 +* update s3-adk 1.12.324 => 1.12.349 +* update mysql-docker 8.0.29 => 8.0.31 + +### 功能更新 +* update 优化 oss 云厂商增加 华为obs关键字 +* update 优化 冗余的三元表达式 +* update 优化 重置时取消部门选中 +* update 优化 新增返回警告消息提示 +* update 优化 hikari 参数顺序 最常用的放上面 删除无用 druid 监控页面 +* update 优化 p6spy 排除健康检查 sql 执行记录 +* update 优化 Dockerfile 创建目录命令 +* update 优化 将空‘catch’块形参重命名为‘ignored’ +* update 优化 使用本地缓存优化 excel 导出 数据量大字典转换慢问题 +* update 优化 字典转换实现 去除字符串查找拼接优化效率 +* update 优化 减小腾讯短信引入jar包的体积 +* update 消除Vue3控制台出现的警告信息 +* update 忽略不必要的属性数据返回 +* update 替换 mysql-jdbc 最新坐标 + +### 新增功能 +* add 新增 junit5 单元测试案例 #6e8ef308 +* add 增加 sys_oss_config access_policy 桶权限类型字段 +* add 增加 4.3-4.4 更新 sql 文件 +* add 新增 字典数据映射注解 #da94e898 +* add 增加 RedisUtils 获取缓存Map的key列表 + +### 问题修复 +* fix 修复 上传png透明图片 生成头像透明部分变成黑色 +* fix 修复 sqlserver sql文件 重复主键数据问题 +* fix 修复 sqlserver 特定情况下报 ssl 证书问题 默认关闭 ssl 认证 +* fix 修复 table中更多按钮切换主题色未生效修复问题 +* fix 修复 菜单激活无法修改其填充颜色 去除某些svg图标的fill="#bfbfbf"属性 +* fix 修复 使用缓冲流 导致上传异常问题 +* fix 修复 过滤器链使用IoUtil.read方法导致request流关闭 +* fix 修复 Log注解GET请求记录不到参数问题 +* fix 修复 某些特性的环境生成代码变乱码TXT文件问题 +* fix 修复 开启TopNav没有子菜单隐藏侧边栏 +* fix 修复 回显数据字典数组异常问题 + +### 移除功能 +* remove 移除过期 Anonymous 注解与其实现代码 +* remove 移除 tlog(不支持UI界面 使用的人太少) 建议使用 skywalking + +## v4.3.1 - 2022-10-24 + +### 依赖升级 +* update springboot 2.7.3 => 2.7.5 +* update springboot-admin 2.7.4 => 2.7.6 +* update sa-token 1.30.0 => 1.31.0 +* update springdoc 1.6.11 => 1.6.12 +* update poi 5.2.2 => 5.2.3 +* update hutool 5.8.6 => 5.8.8 +* update aws-s3 1.12.300 => 1.12.324 +* update aliyun-sms 2.0.18 => 2.0.22 +* update tencent-sms 3.1.591 => 3.1.611 +* update tlog 1.4.3 => 1.5.0 安全性升级 +* update snakeyaml 1.30 => 1.32 存在漏洞 +* update redisson 3.17.6 => 3.17.7 +* update nginx 1.21.6 => 1.22.1 存在漏洞 +* update element-ui 2.15.8 => 2.15.10 +* update core-js 3.19.1 => 3.25.3 + +### 功能更新 +* update 修改 差异命名与镜像名同步 +* update 优化 通用下载方法新增config配置选项 +* update 优化 日志操作中重置按钮时重复查询的问题 +* update 优化 `@Anonymous` 注解标注过期 使用 `@SaIgnore` 替换 +* update 优化 前端可以配置多排序参数支持依次排序 +* update 优化 oss管理 支持时间排序 +* update 优化 替换 sa-token 过期配置 +* update 优化 sa-token 拦截器注册 `SaTokenConfig#addInterceptors` 排除拦截路径配置 +* update 优化 vue3说明文件 编码问题 +* update 优化 导入更新用户数据前校验数据权限 +* update 优化 `R` 类 `isError` 和 `isSuccess` 改为静态方法 +* update 优化 获取用户信息getInfo接口 使用缓存数据获取 +* update 优化 选择按钮宽度 + +### 问题修复 +* fix 修复 用户导入存在则更新不生效 +* fix 修复 日志转换非json数据导致报错 +* fix 修复 控制台SQL日志打印时间格式化问题 +* fix 修复 不同网段因reset请求头导致下载导出跨域问题 +* fix 修复 在线用户设置永不过期 被过滤问题 +* fix 修复 在线用户设置永不过期 超时时间-1推送redis无效问题 +* fix 修复 snakeyaml 漏洞 强制升级依赖版本(临时处理等boot升级) +* fix 修复 开启账号同端互斥登录 被顶掉后登出报null异常问题 +* fix 修复 Redisson 设置 `NameMapper` 导致队列功能异常问题 +* fix 修复 文件上传组件格式验证问题 +* fix 修复 内部调用缓存不生效问题 +* fix 修复 主题颜色在Drawer组件不会加载问题 +* fix 修复 小屏幕上修改头像界面布局错位的问题 +* fix 修复 内链域名特殊字符替换 合并错误导致问题 +* fix 修复 nginx 漏洞 https://www.oschina.net/news/214309 + +## v4.3.0 - 2022-09-14 + +### 重大更新 +* [重大更新] 整合 springdoc 基于 javadoc 实现无注解零入侵生成接口文档 +* [重大更新] 重写 spring-cache 实现 更人性化的操作 支持注解指定ttl等一些参数 +* [不兼容更新] 移除 swagger 所属所有功能 建议使用 springdoc +* [重大更新] 移除maven docker插件 过于老旧功能缺陷大 使用idea自带的docker插件替代 + +### 依赖升级 +* update springboot 2.6.9 => 2.7.3 +* update springboot-admin 2.7.2 => 2.7.4 +* update redisson 3.17.4 => 3.17.6 +* update hutool 5.8.3 => 5.8.6 +* update okhttp 4.9.1 => 4.10.0 +* update lock4j 2.2.1 => 2.2.2 +* update aws-java-sdk-s3 1.12.248 => 1.12.300 修复依赖安全漏洞 +* update aliyun.sms 2.0.9 => 2.0.18 +* update tencent.sms 3.1.537 => 3.1.591 +* update guava 30.0-jre => 31.1-jre +* update springdoc 1.6.9 => 1.6.11 +* update druid 1.2.11 => 1.2.12 +* update dynamic-ds 3.5.1 => 3.5.2 + +### 功能更新 +* update 优化 短信接口实现类 `@Override` 注解 +* update 优化 登出方法代码逻辑 +* update 优化 代码中的一些魔法值 +* update 优化 使用 StreamUtils 简化业务流操纵 +* update 修改 oss 客户端自定义域名 统一使用https开关控制协议头 +* update 更新 监控过时配置 WebSecurityConfigurerAdapter 改为 bean 注入 +* update 修改 生成错误注释 +* update 优化 docker 部署方式 使用 host 模式简化部署流程 降低使用成本 +* update 修改 验证码开关变量名 +* update 优化 DateColumn 支持单模板多key场景 +* update 优化 redission 处理增加前缀 +* update 优化 缓存监控 相关代码 +* update 优化 部署脚本 防止出现权限问题 +* update 优化 多个相同角色数据导致权限SQL重复问题 +* update 优化 字典数据使用store存取 +* update 优化 布局设置使用el-drawer抽屉显示 +* update 更新框架文档 专栏与视频 链接地址 +* update 优化 OSS文件上传 主动设置文件公共读 适配天翼云OSS +* update 优化 表格上右侧工具条(搜索按钮显隐&右侧样式凸出) +* update 优化 前后端多环境部署保持一致 删除无用环境文件 +* update 优化 错误登录锁定与新增解锁功能 +* update 优化 getLoginId 增加必要参数空校验 +* update 使用 SpringCache注解 优化参数管理、字典管理、在线用户等业务缓存 +* update 优化 多角色数据权限匹配规则 +* update 优化 页面内嵌iframe切换tab不刷新数据 +* update 优化 调整 oss表key 与 ossconfig的service 字段长度不匹配 +* update 优化 操作日志密码脱敏 +* update 重构 QueueUtils 抽取通用方法 统一使用 适配优先队列新用法 + +### 新功能 +* add 增加 StreamUtils 流工具 简化 stream 流操纵 +* add 新增 缓存列表菜单功能 +* add 新增 获取oss对象元数据方法 +* add 增加 QueueUtils 操作普通队列的方法 + +### 问题修复 +* fix 修复 mysql sys_notice 与 sys_config 表主键类型长度不够问题 +* fix 修复 获取 SensitiveService 空问题 增加空兼容 +* fix 修复 代码生成首字母大写问题 +* fix 修复 minio 上传自定义域名回显路径错误问题 +* fix 修复 短信功能返回实体 SysSms 序列化问题 +* fix 修复 sqlserver 更新sql错误提交 +* fix 修复 RedisUtils 并发 set ttl 错误问题 +* fix 修复 防止主键字段名与'row'或'ids'一致导致报错的问题 +* fix 修复 幂等组件 逻辑问题导致线程变量未清除 +* fix 修复 脱敏没有实现类导致返回数据异常问题 +* fix 修复 用户导出字典使用错误 +* fix 修复 用户登录与短信登录 国际化格式不一致 +* fix 修复 BaseMapperPlus 方法命令不一致问题 +* fix 修复 短信功能是否启用判断不生效BUG +* fix 修复 xxljob prod 环境配置文件 数据库ip漏改 +* fix 修复 部署脚本 cp 命令缺少参数问题 +* fix 修复 菜单管理的一些操作问题 +* fix 修复 国际化文件提交为特殊编码问题 +* fix 修复 minio配置https遇到的问题 +* fix 修复 点击删除后点击取消控制台报错问题 +* fix 修复 文件/图片上传组件 第一次上传报错导致后续上传无限loading问题 +* fix 修复 postgresql 时间查询类型转换报错问题 +* fix 修复 部门与角色 状态导出字典使用错误 +* fix 修复 openapi结构体 因springdoc缓存导致多次拼接接口路径问题 +* fix 修复 没有权限的用户编辑部门缺少数据 +* fix 修复 oss配置删除内部数据id匹配类型问题 +* fix 修复 用户导入存在则更新不生效 +* fix 修复 日志转换非json数据导致报错 + +## v4.2.0 - 2022-06-28 +### 重大更新 +* [重大更新] 增加 `ruoyi-sms` 短信模块 整合 阿里云、腾讯云 短信功能 +* [重大改动] 基于 `AWS S3` 协议重新实现 OSS模块 支持自定义域名 +* [安全性] 优化 nginx 限制外网访问内网 actuator 相关路径(建议升级) +* [不兼容] 优化 文件与图片上传组件 使用id存储回显(升级的用户需要注意 上传组件返回值变成了 ossid 便于关联) +* [不兼容] 升级 mybatis-plus 3.5.2 解决新版本兼容性问题 关键字冲突修改(新增了很多关键字 升级的需要注意 冲突的关键字建议换一个命名) + +### 依赖升级 +* update springboot-admin 2.6.6 => 2.6.9 +* update springboot-mybatis 2.2.0 => 2.2.2 +* update sa-token 1.29.0 => 1.30.0 +* update hutool 5.7.22 => 5.8.3 +* update druid 1.2.8 => 1.2.11 +* update tlog 1.3.6 => 1.4.3 +* update easyexcel 3.0.5 => 3.1.1 去除cglib 支持jdk17 +* update xxl-job 2.3.0 => 2.3.1 +* update redisson 3.17.0 => 3.17.4 +* update mybatis-plus 3.5.1 => 3.5.2 +* update poi 4.1.2 => 5.2.2 性能大幅提升 +* update docker mysql 8.0.27 => 8.0.29 +* update docker nginx 1.21.3 => 1.21.6 +* update docker redis 6.2.6 => 6.2.7 +* update docker minio 2021-10-27 => 2022-05-26 + +### 功能更新 +* update 优化 redis 序列化 使用系统自带json工具 全局统一 +* update 优化 RedisUtils 重构过期方法 +* update 完善短信验证码发送接口 +* update 优化 弹窗点击遮罩层 默认不关闭 可在 main.js 修改 +* update 调整 CacheManager 使用系统 系统序列化器 +* update 调整 图片预览组件 去除无用根目录拼接 +* update 用户管理左侧树型组件增加选中高亮保持 +* update 优化 DataPermissionHelper 上下文存储 使用 SaToken 的请求存储器 +* update 优化 用户头像上传限制只能为图片格式 +* update 优化 redis 与 jackson 使用自动装配定制器简化配置 +* update 优化 getLoginUser 获取 使用一级缓存 +* update 增加 redis 无密码使用说明 +* update 手动配置 Undertow 缓冲池 消除运行警告 +* update 优化 表单构建按钮不显示正则校验 +* update 优化 oss 回显查询 使用 redis 缓存 +* update 优化 用户列表查询 剔除密码字段 +* update 优化 验证码 登录 登出 注册 等接口 使用匿名注解放行 +* update 修改 代码生成 controller 去除查询校验 由用户自行选择是否校验 +* update 优化 ExcelUtil 工具支持合并处理器 +* update 使用 SaStorage 优化 LoginHelper 一级缓存 避免 ThreadLocal 清理不干净问题 +* update 优化 新增用户与角色信息、用户与岗位信息逻辑 +* update 优化 代码生成 业务接口 增加事务回滚 +* update 优化 logback 删除无用配置 + +### 新功能 +* add 增加 MailUtils 邮件工具 +* add 增加 RedisUtils 操作原子值方法 +* add 增加 demo 短信演示案例 +* add 增加 获取短信验证码接口 +* add 新增 SpringUtils 获取配置文件中的属性值方法 +* add 新增 Anonymous 匿名访问不鉴权注解 +* add 新增 easyexcel 单元格合并注解与处理器 +* add 增加 ExcelUtil 模板导出方法 支持 单列表/多列表 +* add 增加 Excel 模板导出 测试类 + +### 问题修复 +* fix 修复 ExcelUtil 表达式解析 参数添反导致无法解析问题 +* fix 修复 全局线程池配置 核心线程与最大线程 参数填反问题 +* fix 修复 查询未分配用户角色列表 角色无绑定用户情况下 空列表问题 +* fix 修复 sqlserver 新增数据 id 错误 +* fix 修复 token 超时时间设置 -1 导致的单位转换问题 +* fix 修复 编辑 OssConfig 在 postgres 字段重复报错 补全 remark 字段 +* fix 修复 postgres 数据库 菜单部分字段类型无法转换问题 +* fix 修复 脱敏实现逻辑问题 +* fix 修复 登录未选部门报空问题 +* fix 修复 用户注销时记录注销日志异常问题 +* fix 修复 代码生成表字段类型不匹配 导致查询不准确问题 + +## v4.1.0 - 2022-04-24 +### 重大更新 +* [重大更新] 增加应用适配 oracle +* [重大更新] 增加应用适配 SQL Server +* [重大更新] 增加应用适配 postgresql +* [重大更新] 确保更好的适配 多数据库 主键策略统一改为 雪花ID + +### 依赖升级 +* update springboot 2.6.4 => 2.6.7 修复 CVE-2022-22965 漏洞 +* update springboot-admin 2.6.2 => 2.6.6 +* update hutool 5.7.21 => 5.7.22 +* update dynamic-datasource 3.5.0 => 3.5.1 +* update redisson 3.16.8 => 3.17.0 +* update qiniu 7.9.3 => 7.9.5 +* update qcloud 5.6.68 => 5.6.72 +* update minio 8.3.7 => 8.3.8 +* update okhttp 4.9.2 => 4.9.3 + +### 功能更新 +* update 简化查询 部门、菜单、角色、用户、代码生成列表 功能 +* update 优化 部门修改子元素关系 使用批量更新 +* update 优化去除sql差异化 时间范围统一使用 between 处理 +* update 优化 RepeatSubmit 注解 支持业务处理失败 与 异常快速放行 +* update 优化 防重 与 限流 功能支持国际化消息返回 +* update 开启TopNav没有子菜单情况隐藏侧边栏 +* update 更新minio压缩配置 +* update 重命名 菜单字段 query -> query_param 解决系统关键字问题 +* update 使用 in 优化 or 提升索引命中率 +* update 优化 TreeEntity 树实体 去除未知泛型 +* update 优化菜单名称过长悬停显示标题 +* update 优化固定Header后顶部导航栏样式问题 +* update 优化 logback 日志 异步输出 +* update 全局异常处理器引入DuplicateKeyException主键冲突异常拦截 +* update topNav自定义隐藏侧边栏路由 +* update 更名 SaInterfaceImpl 为 SaPermissionImpl 完善相关注释 +* update 优化 sa-token 路由拦截器语法 增加注释 避免误操作 +* update 优化文件上传、图片上传组件 文件列表展示文件原名便于后续处理, 完善组件删除功能 +* update 优化登录失败相关部分代码结构 +* update 使用 spring cglib 替换 停止维护的 cglib +* update 简化 全局线程池配置 使用cpu核心数自动处理 +* update 移除 重复提交 配置文件全局配置 使用注解默认值替代 + +### 新功能 +* add 增加 4.0 升级 4.1 的 sql 脚本(升级需执行此sql) +* add 增加 DataBaseHelper 数据库助手 用于屏蔽多类型数据库sql语句差异 +* add 增加 短信登录 与 小程序登录 示例 +* add 增加 Mybatis 全局异常处理 开启多数据源切换 严格模式 找不到数据源报错 + +### 问题修复 +* fix 修复 数据权限 从 aop 切换到 拦截器 导致获取代理失败问题 +* fix 修复表单清除元素位置未垂直居中问题 +* fix 修复 poi 组件漏洞 与 mysql jdbc 漏洞 +* fix 修复单独访问 接口文档 请求 favicon.ico 报错问题 +* fix 修复 minio 上传, 因 socket 导致 available 获取数值不精确问题 +* fix 修复 cos_api bcprov-jdk15on 漏洞 +* fix 修复 guava 漏洞 统一依赖版本 +* fix 修复 tlog 依赖漏洞 + +## v4.0.1 - 2022-03-01 +### 依赖升级 +* update springboot 2.6.3 => 2.6.4 +* update hutool 5.7.20 => 5.7.21 +* update qiniu 7.9.2 => 7.9.3 +* update minio 8.3.5 => 8.3.7 + +### 功能更新 +* update 图片上传 文件上传 支持并发上传 +* update 组件ImageUpload支持多图同时选择上传 +* udpate 组件fileUpload支持多文件同时选择上传 +* update 优化 R 默认返回 msg +* update 增加 用户注册 用户类型默认值 +* update 增加用户登出日志 +* update 更新 多用户多设备的注释说明 +* update 优化 是否为管理员的判断 +* update 优化 页面若未匹配到字典标签则返回原字典值 +* update 调整用户登录 将日志调整到最后 防止获取不到用户警告 +* update 优化随机数生成方式 避免容易生成两个相同随机数的问题 + +### 问题修复 +* fix 修复代码生成 基于路径生成 路径为空问题 +* fix 恢复误删 `@Async` 注解线程池配置类 +* fix 修复 minio 适配 https 导致的问题 +* fix 修复分页组件请求两次问题 + +## v4.0.0 - 2022-02-18 +### 重大更新 +* [重大更新] 重写项目整体结构 数据处理下沉至Mapper符合MVC规范 减少循环依赖 +* [重磅更新] 主分支与satoken分支合并 权限统一使用 sa-token +* [重磅更新] 适配升级 SpringBoot 2.6 +* [重磅更新] EasyExcel大版本升级3.X +* [重磅更新] 移除链式调用注解 因链式调用不符合java规范 导致很多问题 +* [重磅更新] 增加 轻量级 分布式队列 支持 +* [重磅更新] 增加 数据脱敏注解 使用序列化控制脱敏 支持多种表达式 +* [重磅更新] 重构 使用 Spring 简化 oss 模块代码 +* [重磅更新] 重构 调整返回类型为 R 精简 Controller 代码 + +### 依赖升级 +* update springboot 2.5.8 => 2.6.3 +* update mybatis-plus 3.4.3.4 => 3.5.1 +* update maven-jar-plugin 3.2.0 => 3.2.2 +* update maven-war-plugin 3.2.0 => 3.2.2 +* update maven-compiler-plugin 3.1 => 3.9.0 +* update hutool 5.7.18 => 5.7.20 +* update springboot-admin 2.6.0 => 2.6.2 +* update redisson 3.16.7 => 3.16.8 +* update qiniu 7.9.0 => 7.9.2 +* update aliyun 3.13.1 => 3.14.0 +* update qcloud 5.6.58 => 5.6.68 +* update minio 8.3.4 => 8.3.5 + +### 功能更新 +* update 用户管理部门查询选择节点后分页参数初始 +* update 防重复提交标识组合(key + url + header) +* update 接口文档增加 basic 账号密码验证 +* update 用户修改减少一次角色列表关联查询 +* update 优化部门修改缩放后出现的错位问题 +* update 指定 maven 资源过滤为具体文件 防止错误过滤 +* update hutool 引入改为 bom 依赖项引入 +* update 降低开发环境 redis连接池数量 +* update 升级 springboot 2.6.X 解决 springfox 兼容性问题 +* update 优化多用户体系处理 更名 LoginUtils 为 LoginHelper 支持 LoginUser 多级缓存 +* update 优化加载字典缓存数据 +* update 数据库更改 对接多用户体系 +* update 移除掉 StringUtils 语义不明确的api方法 使用特定工具替换 +* update 优化登录、注册在接口通过`@Validated`注解进行数据基础校验 +* update 优化 查询登录用户数据 统一走缓存 +* update 优化 redisson 配置 去除掉不常用的配置 使用默认配置 +* update 用户访问控制时校验数据权限,防止越权 +* update 修改用户注册报未登录警告 +* update 调整oss预览开关 使用前端直接调用更改配置参数 +* update 使用 satoken 自带的 BCrypt 工具 替换 Security 加密工具 减少依赖 +* update 优化 TreeBuildUtils 工具 使用反射自动获取顶级父id +* update 使用 hutool Dict 优化 JsonUtils 防止类型解析异常 +* update 优化代码生成 使用新 JsonUtils.parseMap 方法 +* update 更新 所有 oss 均支持 https 配置 + +### 新功能 +* add 增加 RedisUtils 工具 hasKey 检查key存在方法 +* add 增加 监控中心 自定义事件通知 +* add 增加 3.X update 4.0 更新sql + +### 问题修复 +* fix 修复登录失效后多次请求提示多次弹窗问题 +* fix 修复 StringUtils 通配符匹配无效 +* fix 修复选项卡点击右键刷新丢失参数问题 +* fix 修复 数据权限 缓存方法名错误问题 +* fix 修复自定义组件`file-upload`无法显示第一个文件,列表显示的文件比实际文件少一个的问题 +* fix 修复因升级 sa-token 导致 doLogin 无法获取 token 问题 +* fix 修复分页组件请求两次问题 + +### 移除功能 +* remove 移除过期代码 分页工具相关 +* remove 移除过期代码 多数据源切换 +* remove 移除过期代码 数据权限 + +### 其他 +* 3.X 版本进入维护阶段 不进行更新 只修复bug 持续维护到2022年10月 +* 4.X 版本公测将近一个月 大部分bug已修复 官网主分支更改为 4.X 版本 推荐使用 + + +## v3.5.0 - 2021-12-28 +### 重大更新 +* [重大更新] 重写数据权限实现 +* [重磅更新] 重构分页 简化使用 +* [重磅更新] 用户登录 支持校验错误次数锁定登录 +* [重磅更新] 增加 jdbc 批处理参数 大幅提升批量操作性能 对原生语句与 MP 均有效 + +### 依赖升级 +* update springboot 2.5.7 => 2.5.8 升级预防 log4j2 问题 +* update springboot-admin 2.5.4 => 2.5.5 +* update hutool 5.7.16 => 5.7.18 +* update redisson 3.16.4 => 3.16.7 +* update dynamic-ds 3.4.1 => 3.5.0 +* update qiniu 7.8.0 => 7.9.0 +* update minio 8.3.3 => 8.3.4 +* update tlog 1.3.4 => 1.3.6 启用 tlog 自动配置 +* update clipboard 2.0.6 => 2.0.8 + +### 功能更新 +* update 多数据源切换标注过期 3.6.0 移除 推荐使用原生注解 +* update 通用权限服务 迁移回 ruoyi-framework 模块 +* update 使用 hutool-jwt 替换老旧 jjwt 依赖 +* update 调整 OSS 表字段内容长度 +* update LoginUser 增加角色缓存 优化角色权限代码 +* update 使用 Cglib 重构 BeanCopyUtils 性能优异 +* update 禁止所有工具类实例化 优化代码书写规范 +* update 优化查询用户的角色组、岗位组代码 +* update 更新 RedisUtils 返回客户端实例 +* update 修改 健康检查权限 改为用户放行 提高安全性 +* update hutool 工具 改为单包引入 减少无用依赖 +* update ServicePlusImpl 功能 下沉到 BaseMapperPlus +* update 去除 jdk17 标签 由于很多组件还未适配 导致一些问题 +* udpate 代码生成预览支持复制内容 +* update 用户导入提示溢出则显示滚动条 +* update 路由支持单独配置菜单或角色权限 +* update 优化web拦截器 使用原生接口处理 默认非生产环境开启 +* update 调整监控依赖 从 common 迁移到 framework + +### 新功能 +* add 新增 Vue3 分支 与 代码生成模板(由于组件还未完善 仅供学习) +* add 增加 RedisUtils 注册监听器方法 +* add 增加 自定义 Xss 校验注解 用户导入增加 Bean 校验 +* add oss下载增加 loading 层 +* add 新增图片预览组件 +* add 集成compression-webpack-plugin插件实现打包Gzip压缩 +* add 新增 SqlUtils 检查关键字方法 + +### 问题修复 +* fix 修复 集群雪花id重复问题 使用网卡信息绑定生成 +* fix 修复 count 语法异常 +* fix 修复更改密码问题 +* fix 修复sql关键字处理 防止解析器报错 +* fix 修复 TreeBuildUtils 顶节点不为 0 问题 +* fix 修复 SysOssConfig 主键类型错误 +* fix 修复代码生成 导出注解错误 +* fix 修复 redisson 集群模式 路径未匹配协议头问题 +* fix 修复打包后字体图标偶现的乱码问题 +* fix 修复版本差异导致的懒加载报错问题 +* fix 修复代码生成字典组重复问题 + +### 移除功能 +* remove 删除 jjwt 无用依赖 +* remove 移除过期 用户导入 +* remove 移除过期工具 DictUtils + +## v3.4.0 - 2021-11-29 + +### 重磅更新 +* update [重磅更新] 重构 Excel 导入 支持 Validator 校验 支持自定义监听器 +* update [重磅更新] Validator 校验框架支持国际化 + +### 依赖升级 +* update springboot 2.5.6 => 2.5.7 +* update hutool 5.7.15 => 5.7.16 +* update okhttp 4.9.1 => 4.9.2 +* update spring-boot-admin 2.5.2 => 2.5.4 +* update redisson 3.16.3 => 3.16.4 +* update tlog 1.3.3 => 1.3.4 +* update axios 0.21.0 => 0.24.0 +* update core-js 3.8.1 => 3.19.1 +* update js-cookie 2.2.1 => 3.0.1 +* update velocity 1.7 => 2.3 +* update 升级 docker 基础镜像 + +### 功能更新 +* update 基于 hutool 封装树构建工具 重构部门与菜单树结构返回 +* update 减少使用特定数据库函数 +* update 配置应用前缀路径 改为配置文件统一配置 +* update 升级 swagger 配置 使用 knife4j 增强模式 +* update 监控中心 集成监控客户端 实现自监控 +* update 调度中心 集成监控客户端 注册到监控中心 +* update 优化 tab 对象简化页签操作 +* update 解耦 LoginUser 与 SysUser 强关联 +* update 更新 RepeatSubmit 注解 aop 处理 针对特殊参数进行过滤 +* update DictUtils 字典工具类 标记过期 3.5.0 版本移除 使用 DictService 代替 +* update 抽象 DictService 通用 字典服务 +* update 抽象 ConfigService 通用 参数配置服务 +* update 基于 DictService 重构 Excel 内字典查询功能 +* update OSS 模块 整体重命名 消除歧义 +* update 更新 redis.conf 存储策略 aof 与 rdb 配置参数 +* update 初始化数据转移到 ApplicationRunner 统一处理 +* update 优化时间查询语句 + +### 新功能 +* add 增加 框架缓存懒加载 开关 +* add 新增 监控中心 Bean 初始化 startup trace 监控插件 +* add 增加 ValidatorUtils 校验框架工具 用于在非 Controller 的地方校验对象 + +### 漏洞修复 +* fix 修复 SysOss、SysOssConfig 未继承 BaseEntity 基础实体问题 +* fix 修复 xxl-job-admin 部署问题 +* fix 修复 回显数据字典键值修正 +* fix 修复 Linux 清除临时目录 导致上传找不到目录报错问题 +* fix 修复通用实体 传参无法接收问题 +* fix 修复 SysLoginController 接口文档书写错误问题 +* fix 修复 用户逻辑删除 差异问题 +* fix 修复 OSS 七牛云 token 过期未刷新问题 +* fix 修复 分页工具 排序字段 null 处理 +* fix 修复 用户导入字典使用错误 +* fix 修复 关闭 xss 功能导致可重复读 RepeatableFilter 失效 +* fix 修复 使用 this.$options.data 报错问题 +* fix 修复 代码生成复选框字典遗漏问题 +* fix 修复 重复提交不生效问题 由于概念不同 使用 RedisUtils 重构 +* fix 修复 OSS 工厂 未实例化服务更新加载问题 + +### 功能移除 +* remove 移除 quartz 相关代码与依赖 +* remove 移除 feign 相关代码与依赖 +* remove 移除 MybatisPlusRedisCache 二级缓存 + +## v3.3.0 - 2021-10-29 + +### 重磅更新 +* add [重磅更新] 增加分布式日志框架 TLog +* add [重磅更新] 增加分布式任务调度系统 Xxl-Job +* add [重大更新] 增加 ruoyi-job 任务调度模块(基于xxl-job) +* update [重大更新]全业务 增加 接口文档注解 格式化代码 + +### 依赖更新 +* update springboot 2.5.5 => 2.5.6 +* update springboot-admin 2.5.1 => 2.5.2 +* update element-ui 2.15.5 => 2.15.6 +* update hutool 5.7.13 => 5.7.15 +* update qcloud.cos 5.6.55 => 5.6.58 +* update minio 8.3.0 => 8.3.3 + +### 功能更新 +* update 更新 element 2.15.6 表格样式 +* update 优化 代码生成常量 关于 BO VO 注释 +* update 优化代码生成 导入表 列表返回 主键默认选中 +* update MybatisPlusRedisCache 标记过期 推荐使用 spring-cache +* update Quartz 标记过期 推荐迁移至新框架 xxl-job +* update Feign 标记过期 +* update 前端增加默认国际化参数 +* update 更新 Admin 监控 注释 避免错误使用 +* update Admin 监控增加日志文件输出 +* update 优化 xxl-job-admin 增加格式化日志输出与 docker 镜像 +* update 更新 xxl-job 执行器开关功能 +* update 代码生成 改为生成抽象实体 +* update 代码生成 搜索框 更新文本域生成 用于模糊查询 +* update 通用数据注入改为适配通用实体类 +* update 使用路由懒加载提升页面响应速度 +* update 迁移所有脚本文件至 script 目录 +* update swagger 组顺序配置 +* update sql 文件更新 xxljob 控制台菜单 +* update 前端增加 任务调度中心页面与环境及 nginx 配置 +* update 合并 oss.sql 至主 sql +* update 补全国际化文件(英文) +* update 更新关于全局路径设置与文档链接 +* update 删除无用 setUsername 使用自动注入 +* update RedisUtils 更新删除 hash 数据方法 + +### 漏洞修复 +* fix 修复 多数据源 aop 语法错误 +* fix 修复 子菜单无 query 参数问题 +* fix 修复 oss 配置删除时删除缓存 bug +* fix 修复无权限获取请求头 download-filename 导致文件名为空问题 + +## v3.2.0 - 2021-9-28 + +### 重大更新 +* update [重大改动]接口文档 支持分组配置 +* update [重大改动]security 路径配置抽取到配置文件 +* update [重大改动] 将 framework 与 system 模块 解耦 调整依赖结构 解决依赖冲突 +* update [重大改动]重写 防重提交实现 使用分布式锁 解决并发问题 压测通过 + +### 依赖更新 +* update springboot 2.5.4 => 2.5.5 bugfix版本 +* update mybatis-plus 3.4.3.3 => 3.4.3.4 bugfix版本 +* update redisson 3.16.2 => 3.16.3 bugfix版本 +* update easyexcel 2.2.10 => 2.2.11 +* update hutool 5.7.11 => 5.7.13 +* update file-saver 2.0.4 => 2.0.5 +* update dart-sass 1.32.0 => 1.32.13 +* update sass-loader 10.1.0 => 10.1.1 + +### 功能更新 +* update 优化代码生成 根据MP生成特性 调整导入表结构默认值合理化 +* update 将所有 云存储字样 改为 对象存储 避免误解 +* update 更新 @Cacheable 错误用法 注意事项 +* update 优化 AddressUtils 空校验处理 +* update 菜单管理支持配置路由参数 +* update 优化aop语法 使用spring自动注入注解 +* update 使用 Redisson 限流工具 重写限流实现 +* update 使用 vue-data-dict 简化数据字典使用 +* update 增加日志注解新增是否保存响应参数开关 +* update 用户未登录日志改为 warn 级别 +* update OSS模块 关于下载403报错信息优化 +* update 更新 Actuator prod 默认暴漏端点 增加暴漏 logfile 日志端点 +* update 默认适配jdk11 测试 jdk17 无异常 +* update 封装通用下载方法简化下载使用 + +### 新功能 +* add 新增通用方法简化模态/缓存使用 +* add 增加 限流演示案例 +* add 增加 redis redisson 集群配置 + +### 漏洞修复 +* fix Cron表达式生成器关闭时销毁,避免再次打开时存在上一次修改的数据 +* fix 全局限流key会多出一个"-" 将其移动到IP后面 去除多余的空格 +* fix 修复多主键代码生成bug +* fix 修复 @Cacheable 与 @DataScope 冲突问题 +* fix 修复代码生成页面数据编辑保存之后总是跳转第一页的问题 + +### 功能移除 +* remove 移除过期工具 RedisCache +* remove 移除无用配置类 ServerConfig +* remove 移除 SysUser 无用字段 salt + +## v3.1.0 - 2021-9-7 + +### 重大更新 +* add [重大改动] 过期 RedisCache 新增 RedisUtils 工具类 新增 发布订阅功能 更灵巧便于使用 +* add [重大改动] 新增 saveOrUpdateAll 方法 可完美替代 saveOrUpdateBatch 高性能 +* update [重大改动] 重写 InsertAll 方法实现 可完美替代 saveBatch 秒级插入上万数据 +* update [重大改动] 更改OSS上传通用路径生成 按照年月日分三级目录 +* update [重大改动] MP字段验证策略更改为 NOT_NULL 个别特殊字段使用注解单独处理 +* update [重大改动] 所有业务适配 RedisUtils 新工具 + +### 依赖升级 +* update springboot 2.5.3 => 2.5.4 +* update spring-boot-admin 2.5.0 => 2.5.1 +* update mybatis-plus 3.4.3 => 3.4.3.3 适配升级 (包含不兼容升级) +* update aliyun.oss 3.13.0 => 3.13.1 +* update qcloud.cos 5.6.47 => 5.6.51 +* update hutool 5.7.9 => 5.7.11 +* update maven-jar-plugin 3.1.1 => 3.2.0 +* update feign-okhttp 11.2 => 11.6 +* update redisson 3.16.1 => 3.16.2 + +### 新功能 +* add 优化 docker 增加 redis 配置文件 +* add 新增暗色菜单风格主题 +* add 菜单&部门新增展开/折叠功能 +* add 页签右键按钮添加图标 页签新增关闭左侧 + +### 功能更新 +* update 优化 OSS 模块与上传组件 异常处理 +* update 更新 jackson 配置 支持 LocalDateTime 全局格式化 +* update 优化 使用权限工具 获取用户信息 +* update 自定义可拖动弹窗宽度指令 +* update 重构 将下载excel工具提取到全局 +* update 定时任务对检查异常进行事务回滚 +* update 优化spy配置文件为 UTF8编码 解决中文注释乱码问题 +* update 修改时检查用户数据权限范围 +* update 解决 logout 写死 无法扩展路径问题 +* update 优化代码生成 导入与同步 批处理效率 +* update 修改时检查用户数据权限范围 +* update 修改代码生成字典回显样式 +* update 修改数据字典回显 +* update 优化验证码配置 使用泛型 防止错误输入 +* update 优化全局线程池配置 使用泛型 防止错误输入 +* update 使用 MP 全局配置分页溢出 +* update 代码生成器 导入表时查询 新创建表的优先排序在前面 +* update 定时任务支持在线生成cron表达式 +* update 自定义弹层溢出滚动样式 +* update 优化分页工具排序处理 +* update 优化 oss配置 使用发布订阅工具 刷新配置 +* update 代码生成 查询数据库列表 按照时间倒序 +* update 使用MP自行判断数据库类型 + +### 漏洞修复 +* fix 修复保存配置主题颜色失效问题 +* fix 修复 导出雪花id excel失真问题 +* fix 修复 druid 监控 集群模式下 无法路由到同一台服务器问题 +* fix 解决搜索校验不通过问题 +* fix 修复定时器工具编写错误问题 +* fix 修复 minio 无 perfix 问题 +* fix 修复 富文本图片路径错误问题 +* fix 修复 OSS配置清空被过滤问题 +* fix 修复 excel 导入与 class 未对应问题 +* fix 修复字典组件值为整形不显示问题 + +## v3.0.0 - 2021-8-18 + +### 重大更新 +* add [重大更新]重写 OSS 模块相关实现 支持动态配置(页面配置) +* add [重大更新]增加 jackson 超出 JS 最大数值自动转字符串(雪花id序列化)处理 +* add [重大更新]重写 防重提交拦截器 支持全局与注解自定义 拦截时间配置配置 优化逻辑 +* add [重大更新]新增是否开启用户注册功能 +* add [重大更新]增加 easyexcel 工具类 +* add [重大更新]集成 性能分析插件 p6spy 更强劲的 SQL 分析 +* add [重大更新]增加 完整国际化解决方案 +* add [重大更新]支持自定义注解实现接口限流 + +### 依赖升级 +* update feign-okhttp 11.0 => 11.2 +* update okhttp 3.19.4 => 4.9.1 +* update minio 8.2.0 => 8.3.0 +* update hutool 5.7.6 => 5.7.7 +* update element-ui 2.15.2 => 2.15.5 +* update springboot admin 2.4.3 => 2.5.0 (新增 Quartz 专属监控页) + +### 新功能 +* add 增加 admin 监控客户端开关 +* add 增加 国际化演示demo + +### 依赖更新 +* update 更新软件架构图 +* update 优化XSS跨站脚本过滤 +* update 优化BLOB下载时清除URL对象引用 +* update 更新 防重提交拦截器 demo演示案例 +* update 日常字符串校验 统一重构到 StringUtils 便于维护扩展 +* update 修改 自动注入器 用户未登录异常拦截抛出警告 返回Null +* update 重构 统一使用 流工具下载 +* update 重写 所有业务导出 适配easyexcel工具 +* update 移动文件存储业务到 system 模块 +* update 代码生成模板 适配新excel导出 +* update 将 Actuator 配置 移动到全局配置 +* update 统一镜像时区配置 移除主机时间映射 +* update 更改多数据源框架更清晰的依赖名 +* update 更新 阿里云 maven源 新地址 +* update 补全基础实体 文档注解 +* update 代码生成文档注解 增加必填判断配置 +* update 注入器 insert 增加 update 字段处理 +* update 默认首页使用keep-alive缓存 + +### 漏洞修复 +* fix 生产minio回显问题 +* fix 修复角色分配用户页面接收参数与传递参数类型不一致导致的错误 +* fix 修复代码生成 删除按钮报错 loading 不取消问题 +* fix 解决登录后浏览器后台Breadcrumb组件报错 +* fix 修复DictUtils方法报错 +* fix 头像上传 未走OSS存储问题 +* fix oss列表 jpeg 不回显问题 +* fix 修复操作日志根据状态查询异常问题 + +### 功能移除 +* remove 移除原生excel工具 +* remove 移除通用上传下载接口与配置 + +## v2.6.0 - 2021-7-28 + +### 重大更新 +* add [重大新增] 增加 OSS 对象存储模块 +* remove [重大改动] 删除 自带通用上传 接口 使用OSS模块替换 +* update [重大改动] 重写VO转换 支持深拷贝 将VO类抽象到 ServicePlus 泛型处理 +* update [重大改动] 多BO合并 使用分组校验 生成BO代码 +* update [重大改动] 重构 IServicePlus 功能 增加 BeanCopyUtils 深拷贝工具 + +### 依赖升级 +* update springboot 2.4.9 => 2.5.3 +* update hutool 5.7.4 => 5.7.6 +* update minio 8.2.2 => 8.3.0 +* update docker plugin 1.2.0 => 1.2.2 +* update redisson 3.16.0 => 3.16.1 +* update datasource 3.4.0 => 3.4.1 +* update element-ui 2.15.2 => 2.15.3 + +### 新功能 +* add 演示Demo增加自定义分页接口案例 +* add 角色&菜单新增字段属性提示信息 + +### 功能更新 +* update 更新druid配置 独立配置更明显 +* update 顶部菜单排除隐藏的默认路由 +* update 富文本新增上传文件大小限制 +* update 导入用户样式调整 +* update 顶部菜单样式调整 +* update 密码框新增显示切换密码图标 +* update 内链设置meta信息 +* update 跳转路由高亮相对应的菜单栏 + +### 漏洞修复 +* fix 修复多数据源druid全局配置缩进错误 引起无效配置问题 +* fix 修复定时任务日志执行状态显示 +* fix 修复 授权角色空数据问题 +* fix 修复 DictData 删除逻辑问题 +* fix 修复任意账户越权漏洞 + +## v2.5.2 - 2021-7-19 + +### 功能更新 +* update 优化代码生成器注释格式 + +### 漏洞修复 +* fix 回滚代码生成 批处理优化 +* fix 代码生成 queryType 重复勾选数据库无默认值问题 +* fix 修复接口单参数校验无效问题 +* fix 代码生成 queryType >= <= 标识符错误问题 +* fix 修复代码生成字典问题 +* fix 修复 thread-pool: enabled 配置不生效问题 + +### 功能移除 +* remove 删除无用文档与脚本 + +## v2.5.1 - 2021-7-13 +* update 验证码开关 转移到表 参数管理 内 +* update 使用hutool重构 判断是否url + +### 漏洞修复 + +### 功能更新 +* fix 修复 docker业务集群部署与文件上传的问题 +* fix 修复代码生成同步表结构id冲突问题 +* fix 修复代码生成选择字典 无法取消问题 +* fix 修复代码生成字典为null问题 +* fix 图片上传 多图时无法删除相应图片修复 + +### 功能移除 +* remove 删除富文本video事件 + +## v2.5.0 - 2021-7-12 + +### 依赖升级 +* update springboot 2.4.7 => 2.4.8 +* update knife4j 3.0.2 => 3.0.3 +* update hutool 5.7.2 => 5.7.4 +* update spring-boot-admin 2.4.1 => 2.4.3 +* update redisson 3.15.2 => 3.16.0 + +### 新功能 +* add 增加 docker 编排 与 shell 脚本 +* add 增加 feign 熔断 自定义结构体解析方法 与 demo 注释 +* add 用户管理新增分配角色功能 +* add 角色管理新增分配用户功能 +* add 增加spring-cache演示案例 + +### 功能更新 +* update 独立 springboot-admin 监控到扩展模块项目 +* update springboot-admin 监控 增加用户登录权限管理 +* update 优化代码生成器 批量导入 +* update 优化 增加MP注入异常拦截 +* update 关闭默认二级缓存 推荐使用 spring-cache 注解手动缓存 +* update FileUpload ImageUpload组件 支持多图片上传 +* update 优化中英文语言配置 +* update 规范maven写法 + +### 漏洞修复 +* fix redis获取map属性bug修复。 +* fix 修复 按钮loading 后端500卡死问题 +* fix 相对路径下载问题 +* fix 修复 hutool 工具返回结果不一致问题 + +## v2.4.0 - 2021-6-24 + +### 依赖升级 +* update springboot 2.3.11 => 2.4.7 +* update springboot-admin 2.3.1 => 2.4.1 +* update feign 2.2.6 => 3.0.3 +* update hutool 5.6.7 => 5.7.2 + +### 功能更新 +* update 多数据源替换成dynamic-datasource +* update 适配 jdk11 +* update 集成 Lock4j 分布式锁 +* update 移除 fastjson 增加 jackson 工具类 重写相关业务 +* update 优化 异步工厂重写 使用 spring 异步处理 +* update 全局挂载字典标签组件 +* update 日志列表支持排序操作 +* update 更新 feign demo 更清晰的用法 +* update 更新多数据源演示案例 + +### 新功能 +* add 增加 ServicePlusImpl 自动以实现类 重写移除事务注解方法 防止多数据源失效 +* add 增加 自定义 批量insert方法 +* add 增加 Swagger3 用法示例 + +### 漏洞修复 +* fix 修复地址ip地址特殊回环问题 + +## v2.3.2 - 2021-6-11 + +### 新功能 +* add redis锁工具类编写 + +### 功能更新 +* update spring-cache 整合 redisson +* update MybatisPlus整合Redis二级缓存 +* update swagger 升级为 3.0.0 使用 OAS_30 协议 +* update 优化 代码生成器 增加表单防重注解 +* update 优化 锁切面代码 key到常量类 + +### 漏洞修复 +* fix 修复相对路径上传异常问题 + +## v2.3.1 - 2021-6-4 + +### 新功能 +* add 增加 redisson 分布式锁 注解与demo案例 +* add 增加 Oracle 分支 + +### 功能更新 +* update 优化 redis 空密码兼容性 +* update 优化前端代码生成按钮增加 loading + +### 漏洞修复 +* fix 修复 redisson 不能批量删除的bug +* fix 修复表单构建选择下拉选择控制台报错问题 +* fix 修复 vo 代码生成 主键列表显示 重复生成bug +* fix 修复上传路径 win 打包编译为 win 路径, linux 报错bug + +## v2.3.0 - 2021-6-1 + +### 新功能 +* add 升级 luttuce 为 redisson 性能更强 工具更全 +* add 增加测试数据sql文件 +* add 增加demo模块 单表演示案例(包含数据权限) + +### 功能更新 +* update 完美修复 数据权限功能(支持单表多表过滤) +* update 优化代码生成模板 +* update 优化 system 模块 批量操作性能 + +## v2.2.1 - 2021-5-29 + +### 新功能 +* add 增加 security 权限框架 `@Async` 异步注解配置 + +### 功能更新 +* update 优化dataScope参数防止注入 +* update 优化参数&字典缓存操作 +* update 增加修改包名文档 +* update 文档增加演示图例 + +### 漏洞修复 +* fix 修复部门类sql符号错误 + +## v2.2.0 - 2021-5-25 + +* 同步升级 RuoYi-Vue 3.5.0 + +### 新功能 +* add 增加验证码开关 +* add 新增IE浏览器版本过低提示页面 + +### 功能更新 +* update 升级druid到最新版本v1.2.6 +* update 升级fastjson到最新版1.2.76 +* update 修改bo加入判断是否设置必填再加载必填注解 +* update 生成vue模板导出按钮点击后添加遮罩 +* update Redis设置HashKey序列化 +* update 优化Redis序列化配置 + +### 漏洞修复 +* fix 修复代码生成器中表字段取消必填无法更新问题 + +## v2.1.2 - 2021-5-21 + +### 功能更新 +* update springboot 升级 2.3.11 +* update mybatis-plus 升级 3.4.3 分页Plus对象适配更新 +* update 验证码生成更新为无符号整数计算 +* update 请求响应对象 与 分页对象 结构修改 适配接口文档配置 +* update swagger增加请求前缀 + +## v2.1.1 - 2021-5-19 + +### 功能更新 +* update 配置统一提取为 properties 配置类 +* update 分页工具 删除过期方法 +* update admin 实时监控日志 改为保留一天 + +### 漏洞修复 +* fix 修复swagger开关无法控制关闭问题 +* fix maven install 异常 + +## v2.1.0 - 2021-5-17 + +### 功能更新 +* update knife4j升级3.0.2 +* update 增强分页工具兼容性 +* update 通用Service接口 增加自定义vo转换函数 + +### 移除功能 +* remove 移除ruoyi自带服务监控(Admin已全部包含) + +## v2.0.0 - 2021-5-15 + +### 依赖升级 +* springboot 升级 2.3.10 依赖全面升级适配 + +### 新功能 +* add 增加分页工具 +* add 增加 增强Mapper 与 增强Service 重写业务适配 +* add 代码生成器 增加校验注解 + +### 功能更新 +* update 代码生成器修改为MP分页 +* update 使用 MP 分页工具 重构业务 +* update 重写文档介绍 + +### 移除功能 +* remove 移除 pagehelper 分页工具 + +### 漏洞修复 +* fix 修复代码生成 数据权限问题 + +## v1.0.2 - 2021-5-13 + +### 功能更新 +* update 更新整合打包文档 重新排版 + +### 漏洞修复 +* fix vue与boot整合打包与admin页面路由冲突 + +## v1.0.1 - 2021-5-11 + +### 依赖更新 +* update 更新banner +* update 配置转移到 yml 文件 统一管理 +* update 上传媒体类型添加视频格式 +* update 树级结构更新子节点使用replaceFirst +* update 删除操作日志记录日志 + +### 漏洞修复 +* fix 修正导入表权限标识 +* fix 文件上传时报错 + +## v1.0.0 - 2021-5-10 +* 基于 ruoyi-vue 3.4.0 发布 v1.0.0 稳定版 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/architecture_diagram.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/architecture_diagram.md new file mode 100644 index 00000000..ef65fef3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/architecture_diagram.md @@ -0,0 +1,3 @@ +# 软件架构图 +- - - +![输入图片说明](https://foruda.gitee.com/images/1722569328704494018/e84442b6_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/doc.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/doc.md new file mode 100644 index 00000000..06e62dc5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/doc.md @@ -0,0 +1,89 @@ +# 接口文档 +- - - +## 版本 >= `4.3.0` +## 说明 + +由于 `springfox` 与 `knife4j` 均停止维护 bug众多
+故从 `4.3.0` 开始 迁移到 `springdoc` 框架
+基于 `javadoc` 无注解零入侵生成规范的 `openapi` 结构体
+由于框架自带文档UI功能单一扩展性差 故移除自带UI 建议使用外置文档工具 + +## 文档工具使用 +由于框架采用 `openapi` 行业规范 故市面上大部分的框架均支持 可自行选择
+例如: `apifox` `apipost` `postman` `torna` `knife4j` 等 根据对应工具的文档接入即可 + +## Swagger升级SpringDoc指南 + +常见功能如下 其他功能自行挖掘
+**注意: `javadoc` 只能替换基础功能 特殊功能还需要使用注解实现** + +| swagger | springdoc | javadoc | +|----------------------------------|---------------------------------|--------------------| +| @Api(name = "xxx") | @Tag(name = "xxx") | java类注释第一行 | +| @Api(description= "xxx") | @Tag(description= "xxx") | java类注释 | +| @ApiOperation | @Operation | java方法注释 | +| @ApiIgnore | @Hidden | 无 | +| @ApiParam | @Parameter | java方法@param参数注释 | +| @ApiImplicitParam | @Parameter | java方法@param参数注释 | +| @ApiImplicitParams | @Parameters | 多个@param参数注释 | +| @ApiModel | @Schema | java实体类注释 | +| @ApiModelProperty | @Schema | java属性注释 | +| @ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 无 | +| @ApiResponse | @ApiResponse | java方法@return返回值注释 | + +# 建议使用 `Apifox`(常见问题有其他对接方式) + +官网连接: [https://www.apifox.cn/](https://www.apifox.cn/)
+视频教程: [springdoc与apifox配合使用](https://www.bilibili.com/video/BV1mr4y1j75M?p=8&vd_source=8f52c77be3233dbdd1c5e332d4d45bfb) + +![输入图片说明](https://foruda.gitee.com/images/1678976476639902970/f1617b40_1766278.png "屏幕截图") + +支持 文档编写 接口调试 Mock 接口压测 自动化测试 等一系列功能 + +### 接入框架 + +> 1.下载或使用web在线版 创建一个自己的项目 + +![输入图片说明](https://foruda.gitee.com/images/1678976502850663851/7bbd8728_1766278.png "屏幕截图") + +> 2.进入项目 选择项目设置 找到自动同步 + +![输入图片说明](https://foruda.gitee.com/images/1678976508918240326/6a4a61a8_1766278.png "屏幕截图") + +> 3.根据项目内所有文档组完成所有数据源创建(拉取后端`openapi`结构体)
+数据源URL格式 `http://后端ip:端口/v3/api-docs/组名`
+项目内所需:
+`http://localhost:8080/v3/api-docs/1.演示模块`
+`http://localhost:8080/v3/api-docs/2.通用模块`
+`http://localhost:8080/v3/api-docs/3.系统模块`
+`http://localhost:8080/v3/api-docs/4.代码生成模块`
+也可不分组统一导入: `http://localhost:8080/v3/api-docs`
+ +![输入图片说明](https://foruda.gitee.com/images/1678976514385097727/05c7e0a6_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1686626073422245046/df4b6a54_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678976527495742967/79836e7f_1766278.png "屏幕截图") + +> 4.选择 接口管理 项目概览 点击立即导入 并等待导入完成
+后续会根据策略每3个小时自动导入一次
+每次重新进入apifox也会自动同步一次
+后端有改动也可以手动点击导入
+ +![输入图片说明](https://foruda.gitee.com/images/1678976534677430926/f32c64c5_1766278.png "屏幕截图") + +> 5.(注意版本号)设置鉴权 选择接口管理 项目概览 找到Auth 按照如下配置 + +**版本号: >= 5.X** + +![输入图片说明](https://foruda.gitee.com/images/1690966897370710566/6a688aea_1766278.png "屏幕截图") + +**版本号: 4.X** + +![输入图片说明](https://foruda.gitee.com/images/1678976539608390075/77246461_1766278.png "屏幕截图") + +> key对应项目配置 默认为 `Authorization` + +![输入图片说明](https://foruda.gitee.com/images/1678976544342001474/c2ff85d3_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678976549237304743/bcdfadda_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/i18n.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/i18n.md new file mode 100644 index 00000000..4052979a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/i18n.md @@ -0,0 +1,33 @@ +# 国际化方案 +- - - +* 前端国际化参考 [ruoyi前端国际化文档](http://doc.ruoyi.vip/ruoyi-vue/document/htsc.html#前端国际化流程)
+* 后端国际化(2.7.0 以上增加) +* 3.4.0 以上支持 `Validator` 校验框架 +* 参考 `demo` 模块 `TestI18nController` 国际化演示案例 + 在 `Header` 请求头 增加上下文语言参数 `content-language` 参数需与国际化配置文件后缀对应 + 如 `zh_CN` `en_US` 等
+ +![输入图片说明](https://foruda.gitee.com/images/1678976722892396585/60917594_1766278.png "屏幕截图") + +## 获取 `code` 对应国际化内容 + +![输入图片说明](https://foruda.gitee.com/images/1678976728533100954/0ab8e36a_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976733019209506/a16574d6_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976738409745057/a073b425_1766278.png "屏幕截图") + +## 使用 `Validator` 框架校验 `controller` 参数返回国际化 + +`controller` 校验接口参数 需要在类增加 `@Validated` 注解
+![输入图片说明](https://foruda.gitee.com/images/1678976741834729507/6c19b9cc_1766278.png "屏幕截图")
+参数对应校验注解 使用 `{code}` 形式标注使用国际化处理
+![输入图片说明](https://foruda.gitee.com/images/1678976746093285542/ad0989db_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976750822808564/56bd60d7_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976754755107198/b89bf173_1766278.png "屏幕截图") + +## 使用 `Validator` 框架校验 `Bean` 返回国际化 + +`Bean` 校验需要在接口校验 `Bean` 参数使用 `@Validated` 注解
+![输入图片说明](https://foruda.gitee.com/images/1678976761015767874/729da3bc_1766278.png "屏幕截图")
+`Bean` 内属性校验注解 使用 `{code}` 形式标注使用国际化处理
+![输入图片说明](https://foruda.gitee.com/images/1678976765122587920/0b1027af_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976769965314387/0c416ede_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/new_module.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/new_module.md new file mode 100644 index 00000000..87b992aa --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/new_module.md @@ -0,0 +1,15 @@ +# 关于如何创建新模块 +- - - +* 参考ruoyi-demo模块 +* 需要改动: 父pom 与 admin模块pom + +![输入图片说明](https://foruda.gitee.com/images/1719814203987728184/6940ecf8_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1719814254968294155/70fe6d77_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1719814280768521481/14fb11cd_1766278.png "屏幕截图") + +### 注意事项 +如果是两个不同包名的模块 需要修改如下配置 + +![输入图片说明](https://foruda.gitee.com/images/1719813861680271619/82435586_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1692006501957936219/059f8526_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_package_name.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_package_name.md new file mode 100644 index 00000000..2dcab848 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_package_name.md @@ -0,0 +1,33 @@ +# 关于修改包名 +- - - + +**注意: 老包名为 com.ruoyi** + +## 1.随便找个地方新建 org.dromara 包 +![输入图片说明](https://foruda.gitee.com/images/1708491220807198688/b95c0c34_1766278.png "屏幕截图") + +## 2.在包上右键选择 refactor -> rename 选择 All Directories +![输入图片说明](https://foruda.gitee.com/images/1683276891079076405/79808b22_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1708491697128844860/1e87ad39_1766278.png "屏幕截图") + +**因为dromara组织下有很多依赖导致idea无法识别完整包名** +
+![输入图片说明](https://foruda.gitee.com/images/1708490576909691001/692e5b37_1766278.png "屏幕截图") + +**需要先将dromara修改为 例如: ruoyi 然后重复上述步骤 这样就可以整包修改了** +
+![输入图片说明](https://foruda.gitee.com/images/1708490906933084793/ff104cd7_1766278.png "屏幕截图") + +## 3.使用IDEA全局替换 org.dromara 替换为 com.xxx + +![输入图片说明](https://foruda.gitee.com/images/1708491055347995519/dedda0d1_1766278.png "屏幕截图") + +**注意: 由于dromara组织下项目很多 非本框架的依赖模块 请勿修改 例如上图中的 org.dromara.sms4j** + +## 4.如有需要 将所有模块名逐一修改即可 + +## 5.修改完成后需查看所有common包下模块spi文件是否修改正确 + +**老版本idea或者未按照教程修改包名可能导致文件丢包问题** + +![输入图片说明](https://foruda.gitee.com/images/1708491365841192006/8bc337c2_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_url.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_url.md new file mode 100644 index 00000000..0f5f106b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/association/update_url.md @@ -0,0 +1,26 @@ +# 修改应用路径 +- - - +## 修改后端路径 + +更改 `application.yml` 的 `server.servlet.context-path` 即可更改后端容器路径 + +![输入图片说明](https://foruda.gitee.com/images/1724316662536650741/41d534b1_1766278.png "屏幕截图") + +与之对应前端 `dev`环境 需更改 `vite.config.ts` 的代理路径 + +![输入图片说明](https://foruda.gitee.com/images/1724316844091667249/9b0badc5_1766278.png "屏幕截图") + +`prod` 生产环境需修改 `nginx.conf` 后端代理路径 + +![输入图片说明](https://foruda.gitee.com/images/1661823876773225117/f1f912a9_1766278.png "屏幕截图") + +## 修改前端路径 +### 注意: 3.4.0 提供便捷更改方式 +直接修改对应环境的 `.env.环境` 文件内的 `VITE_APP_CONTEXT_PATH` 应用访问路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1661824572484410642/14265f05_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1724317049535973756/0a2cc43b_1766278.png "屏幕截图") + +生产环境 `nginx.conf` 与之对应修改即可
+**注意: 文件真实目录为 `/usr/share/nginx/html/admin/index.html` 此功能一般为多项目部署需要 故会增加一层目录 如不需要可以自行修改**
+![输入图片说明](https://foruda.gitee.com/images/1678976662194341301/2720b7e9_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/client.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/client.md new file mode 100644 index 00000000..bec103d8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/client.md @@ -0,0 +1,85 @@ +# 客户端管理功能 +- - - +## 版本 >= 5.X + +## 客户端管理页面 + +![输入图片说明](https://foruda.gitee.com/images/1690961819029076660/c44374ac_4959041.png "屏幕截图") + +### 客户端管理字段说明 +| 字段名称 | 取值说明 | 注意事项 | +|----------------|----------------------------|--------------------------------| +| 客户端id | 由后端生成,用于前端登录校验以及接口数据加密 | 无法修改,不要删除默认数据,否则会报错 | +| 客户端key | 前端自定义 | 无法修改,不要删除默认数据,否则会报错 | +| 客户端秘钥 | 前端自定义 | 无法修改,不要删除默认数据,否则会报错 | +| 授权类型 | 密码认证、短信认证、邮件认证、小程序认证、第三方认证 | 根据授权类型判断当前客户端是否支持该登录方式 | +| 设备类型 | PC端、APP端 | | +| Token活跃超时时间 | 自定义 | 指定时间无操作则过期(单位:秒),默认30分钟(1800秒) | +| Token固定超时时间 | 自定义 | 指定时间必定过期(单位:秒),默认七天(604800秒) | + +### 前后端使用新的客户端id + +步骤如下: +1. 前端管理页面生成新的客户端id。 +2. 将新的客户端id复制到前端配置文件。 + +![输入图片说明](https://foruda.gitee.com/images/1690962894318847386/133d2f90_4959041.png "屏幕截图") + +## 新增自定义客户端 + +### 步骤一:新增客户端数据(例如增加小程序端) + +![输入图片说明](https://foruda.gitee.com/images/1690965463070099188/baeb4441_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1690965508836621042/df06248f_4959041.png "屏幕截图") + +### 步骤二:配置前端请求头信息 + +需要在全局请求头 header 中增加 cientid
+确保客户端所有请求都携带此id 可参考项目 `request.ts` + +![输入图片说明](https://foruda.gitee.com/images/1690965768235114596/980b88d2_4959041.png "屏幕截图") + +`VITE_APP_CLIENT_ID` 即配置文件中的客户端id。 + +**重点:不同客户端登录获取到的token不同与其他端不互通(例如: app登录获取到的token无法用于pc端接口查询)** + +## 新增自定义登录方式授权类型 + +**重点说明: 不要单独增加登录接口 系统全局统一只有一个登录接口 只需增加不同的鉴权方式即可** + +如何调试使用登录看这里 -> [关于登录调试步骤](/questions/login_step.md) + +### 步骤一:新增字典数据 + +![输入图片说明](https://foruda.gitee.com/images/1690968849418013624/3b28417e_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1690968865819397010/64529fad_4959041.png "屏幕截图") + +### 步骤二:新增/修改客户端数据 + +### 步骤三:后端新增认证策略 + +新增策略实现类实现 `IAuthStrategy` 接口。
+ +![输入图片说明](https://foruda.gitee.com/images/1690972828588111954/7614a4c5_4959041.png "屏幕截图") + +参照已有策略实现类实现自定义参数校验登录方法逻辑。
+ +![输入图片说明](https://foruda.gitee.com/images/1718951146945578143/789c80e4_1766278.png "屏幕截图") + +**注意修改 `@Service` 名称保证规范性** + +![输入图片说明](https://foruda.gitee.com/images/1718951179571300385/8db730b9_1766278.png "屏幕截图") + +`LoginBody` 校验参数(可自定义)
+ +![输入图片说明](https://foruda.gitee.com/images/1718951237123374392/f7840db2_1766278.png "屏幕截图") + +例如 扩展小程序登录参数 只需要继承 `LoginBody
+ +![输入图片说明](https://foruda.gitee.com/images/1718951283931895761/e6348be5_1766278.png "屏幕截图")` + +校验分组(可自定义)
+ +![输入图片说明](https://foruda.gitee.com/images/1718951343601334215/8ef404b4_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/code_generate.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/code_generate.md new file mode 100644 index 00000000..e20e09ac --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/code_generate.md @@ -0,0 +1,85 @@ +# 代码生成 +- - - +## 功能介绍 + +### 数据源配置 + +![输入图片说明](https://foruda.gitee.com/images/1678976867341325193/a2be0608_1766278.png "屏幕截图") + +**>= 4.1.0版本 项目适配多种类型数据库 可以在代码生成页面切换**
+ +> 填写对应的数据源名称 点击搜索按钮 即可切换到对应的数据源
+ +![输入图片说明](https://foruda.gitee.com/images/1678976876081856486/4ef4841c_1766278.png "屏幕截图") + +**>= 5.2.2版本 项目支持100+种数据库适配 在代码生成模块增加对应的数据库依赖即可**
+ +![输入图片说明](https://foruda.gitee.com/images/1722396530340741054/3914eb72_1766278.png "屏幕截图") + +### 导入数据表 + +> 点击导入按钮 会加载系统数据库所有的表
+ +![输入图片说明](https://foruda.gitee.com/images/1678976880393939803/3ecf1dcc_1766278.png "屏幕截图") + +> 选择需要的表 点击确定即可
+ +![输入图片说明](https://foruda.gitee.com/images/1678976885370716109/4834faa5_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976891856866728/853420d9_1766278.png "屏幕截图") + +### 编辑表生成结构 + +> 点击表对应的编辑按钮
+ +![输入图片说明](https://foruda.gitee.com/images/1678976899111822310/aeaa33f9_1766278.png "屏幕截图") + +> 更改要生成表的数据
+ +![输入图片说明](https://foruda.gitee.com/images/1678976903345795925/4326f6ee_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678976908897387614/4cdf939b_1766278.png "屏幕截图") + +### 生成条件影响 + +![输入图片说明](https://foruda.gitee.com/images/1678976913809284051/24da09b0_1766278.png "屏幕截图") + + +* `插入` `编辑` 影响生成 BO 类 与 前端添加编辑页面 是否有该字段 +* `列表` 影响生成 VO 类 与 前端列表页面展示 是否有该字段 +* `查询` 影响 前端页面是否有该字段的搜索框 与 后端代码是否生成对应的查询条件 +* `查询方式` 影响生成查询条件的类型 +* `必填` 影响 BO 类 与 页面是否强制校验 +* `显示类型` 影响生成页面使用何种展示组件 +* `字典类型` 影响页面是否生成与字典的关联 + +### 树表配置 + +> 编辑表生成信息 生成模板为 `树表` 填写对应数据即可
+ +![输入图片说明](https://foruda.gitee.com/images/1678976917918548901/f5886c5c_1766278.png "屏幕截图") + +### 主子表说明 + +框架不支持也不推荐使用主子表
+原因一般业务场景 基本都是一对N表 多表关联场景
+还有一些 主 => 子 <= 主 场景 需求很复杂 很少有单纯主子表场景出现
+另外主子表关联 很容易出现 笛卡尔积 或者数据错乱等问题 需要自行sql调优场景
+所以建议大家都按照 单表生成 自行编写业务逻辑 + +### 预览功能 + +> 配置好生成信息后 可以点击预览按钮
+ +![输入图片说明](https://foruda.gitee.com/images/1678976924411765532/2e9747df_1766278.png "屏幕截图") + +> 系统会根据已经配置好的数据 生成对应的代码预览
+> 可以再此处观察代码的生成结构和数据是否正确等
+ +![输入图片说明](https://foruda.gitee.com/images/1678976945982406065/ca7383bb_1766278.png "屏幕截图") + + +### 代码结构同步 + +> 实际开发中 难免会有表结构更改的需求
+> 这时可以使用 同步功能 点击同步按钮 即可与实时数据库表进行字段同步
+ +![输入图片说明](https://foruda.gitee.com/images/1678976952919156537/3c47c078_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/export.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/export.md new file mode 100644 index 00000000..1dd01c17 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/export.md @@ -0,0 +1,249 @@ +# 导出功能 + +- - - + +在本框架中引入了 `Easy Excel` 依赖(对 `Apache POI`进行了封装以及扩展),可以对数据进行导出操作(即写 Excel)。 + +[EasyExcel 文档地址](https://easyexcel.opensource.alibaba.com/) + +## 导出功能使用流程说明 + +### 步骤一:定义导出实体对象 + +以框架中 `SysUserExportVo` 为例: + +```Java + /** + * 用户ID + */ + @ExcelProperty(value = "用户序号") + private Long userId; + + // ....................... + + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; +``` + +> 说明:
+> 1. 使用 `@ExcelProperty` 注解标注需要导出的属性。 +> 2. 注解 `@ExcelProperty` 中 `value` 属性代表表格头部标题字段,`converter` 代表使用的转换器,后面会详细说明。 +> 3. 注解 `@ExcelDictFormat` 为自定义注解,与自定义转换器结合使用,同样在后面进行详细说明。 + +### 步骤二:使用导出方法 + +以框架中 `SysUserController#export` 方法为例: + +```Java + /** + * 导出用户列表 + */ + @PostMapping("/export") + public void export(SysUserBo user, HttpServletResponse response) { + // 根据参数查询导出的用户列表数据 + List list = userService.selectUserList(user); + // 将列表转换为导出对象列表 + List listVo = MapstructUtils.convert(list, SysUserExportVo.class); + // 导出方法 + ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response); + } +``` + +> 说明:
+> 使用 `ExcelUtil.exportExcel` 方法完成导出功能,上述 Demo 传入参数分别是:导出对象集合,Excel sheet 表名称,导出对象类型,response。 + +## 框架工具使用说明 + +### 1:字典转换器 + +字典转换器 `ExcelDictConvert` 与自定义注解 `@ExcelDictFormat` 结合使用,标注在需要转换的属性上。 + +使用方式一: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; +``` + +使用方式二: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp="0=男,1=女,2=未知", separator=",") + private String sex; +``` + +`@ExcelDictFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|------------------|--------|-----|-----------------------------------| +| dictType | String | "" | 字典的type值 (如: sys_user_sex) | +| readConverterExp | String | "" | 读取内容转表达式 (如: 0=男,1=女,2=未知) | +| separator | String | "," | 与 readConverterExp 属性结合使用,表达式的分隔符 | + +### 2:枚举转换器 + +字典转换器 `ExcelEnumConvert` 与自定义注解 `@ExcelEnumFormat` 结合使用,标注在需要转换的属性上。 + +使用方式: + +```Java + /** + * 用户类型 + *

+ * 使用ExcelEnumFormat注解需要进行下拉选的部分 + */ + @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) + @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") + private String userStatus; +``` + +`@ExcelEnumFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|-----------|------------|------|------------------------------| +| enumClass | Enum Class | - | 字典枚举类型 | +| codeField | String | code | 字典枚举类中对应的 code 属性名称,默认为 code | +| textField | String | text | 字典枚举类中对应的 text 属性名称,默认为 text | + +### 3:合并单元格 + +`@CellMerge` 注解用于合并相同的列数据,需要结合 `CellMergeStrategy` 策略使用,标注在需要转换的属性上。 + +使用方式: + +步骤一:在属性标注 `@CellMerge` 注解: +```Java + /** + * 部门id + */ + @CellMerge + @ExcelProperty(value = "部门id") + private Long deptId; +``` + +`@CellMerge` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|---------|----------|-----|------------------------------| +| index | int | -1 | 合并列的下标,建议使用默认值 | +| mergeBy | String[] | {} | 合并需要依赖的其他字段名称(基于这个字段内容做合并条件) | + +步骤二:导出方法开启合并: +```Java + /** + * 导出测试单表列表 + */ + @PostMapping("/export") + public void export(@Validated TestDemoBo bo, HttpServletResponse response) { + List list = testDemoService.queryList(bo); + // 参数 true 表示开启合并单元格策略 + ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, true, response); + } +``` +![输入图片说明](https://foruda.gitee.com/images/1700128921644543994/e8d4704f_1766278.png "屏幕截图") + +### 4:复杂 Excel 导出示例 +`TestExcelController` 提供了几种导出示例,如果需要可以参照相应方法进行导出。 + +#### 4.1:单列表多数据导出(模板导出) + +模板内容: + +![输入图片说明](https://foruda.gitee.com/images/1700124852002972562/d9f57a8c_4959041.png "屏幕截图") + +模板位置:`ruoyi-modules/ruoyi-demo/src/main/resources/excel/` + +导出示例代码:参考 demo 模块 `TestExcelController` 模板写法请查看 `EasyExcel` 文档 + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700124885532359879/0d011d05_4959041.png "屏幕截图") + +#### 4.2:多列表多数据导出(模板导出) + +模板内容: + +![输入图片说明](https://foruda.gitee.com/images/1700125025931981176/105dbaaa_4959041.png "屏幕截图") + +模板位置:`ruoyi-modules/ruoyi-demo/src/main/resources/excel/` + +导出示例代码:参考 demo 模块 `TestExcelController` 模板写法请查看 `EasyExcel` 文档 + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700125054011300002/71869c1d_4959041.png "屏幕截图") + +#### 4.3:导出下拉框 + +`ExcelDictFormat` 注解指定的字典项默认都会转换成下拉框 + +自定义导出省市区下拉框示例代码:参考 demo 模块 `TestExcelController` + +导出结果: + +![输入图片说明](https://foruda.gitee.com/images/1700125265411678973/7f767719_4959041.png "屏幕截图") + +## Easy Excel 常用注解 + +`Easy Excel` 提供了丰富的注解可以对导出对象进行定制化操作,这里的注解说明针对的是原生注解,自定义注解会结合转换器一起进行说明。 + +| 类型 | 注解名称 | 使用举例 | 说明 | +|-------|-------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| 格式化注解 | @DateTimeFormat | @DateTimeFormat(value=格式化值) | 对字符串进行日期格式化 (参照 `java.text.SimpleDateFormat` 书写即可) | +| 格式化注解 | @NumberFormat | @NumberFormat(value=格式化值, roundingMode=舍入模式) | 对字符串进行数值格式化 (参照 `java.text.DecimalFormat` 书写即可, `roundingMode` 默认 `RoundingMode.HALF_UP`) | +| 样式注解 | @ColumnWidth | @ColumnWidth(value=值) | 设置列宽 | +| 样式注解 | @ContentFontStyle | @ContentFontStyle(color=颜色) | 可以设置字体类型,颜色,粗细,是否斜体,下划线等,具体可查看注解 `@ContentFontStyle` | +| 样式注解 | @ContentLoopMerge | @ContentLoopMerge(eachRow=行值, columnExtend=列值) | 设置循环合并的区域 | +| 样式注解 | @ContentRowHeight | @ContentRowHeight(value=值) | 设置内容行高 | +| 样式注解 | @ContentStyle | - | 设置单元格样式,具体可查看注解 `@ContentStyle` | +| 样式注解 | @HeadFontStyle | @HeadFontStyle(color=颜色) | 设置表头字体格式,类似 `@ContentFontStyle`,具体可查看注解 `@HeadFontStyle` | +| 样式注解 | @HeadRowHeight | @HeadRowHeight(value=值) | 设置表头行高 | +| 样式注解 | @HeadStyle | - | 设置表头样式,具体可查看注解 `@HeadStyle` | +| 样式注解 | @OnceAbsoluteMerge | @OnceAbsoluteMerge(firstRowIndex=开始行下标, lastRowIndex=结束行下标, firstColumnIndex=开始列下标, lastColumnIndex=结束列下标) | 根据设置值合并单元格 | +| 属性注解 | @ExcelIgnore | @ExcelIgnore | 导出忽略该字段 | +| 属性注解 | @ExcelIgnoreUnannotated | @ExcelIgnoreUnannotated | 默认不管加不加 `@ExcelProperty` 的注解的所有字段都会参与读写,加了 `@ExcelIgnoreUnannotated` 注解以后,不加 `@ExcelProperty` 注解的字段就不会参与 | +| 属性注解 | @ExcelProperty | @ExcelProperty(value=值, order=排序值, index=下标, converter=转换器) | 默认按照对象属性顺序导出,如果设置了 `order` 以及 `index`,优先级 `index` > `order` > 默认;converter 可以自定义 | + +## 扩展说明 + +### 自定义转换器实现 + +由于业务需要,原生注解不一定能够符合需要,因而衍生出了自定义转换器。能够实现定制化的内容转换需要。 +以下以框架中的字典转换器 `ExcelDictConvert` 为例进行说明。 + +字典转换器 `ExcelDictConvert`,字典转换器使用了自定义注解 `@ExcelDictFormat` 配合使用。 + +_**注:自定义转换器并非一定需要自定义注解,也可以针对已有的注解进行自定义转换实现。**_ + +#### 实现方式 + +自定义转换器需要实现 `com.alibaba.excel.converters.Converter` 接口,实现接口中的方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700104014304819918/33eb0c42_4959041.png "屏幕截图") + +转换方法 `ExcelDictConvert#convertToExcelData` : + +![输入图片说明](https://foruda.gitee.com/images/1700104426131801297/72931ef0_4959041.png "屏幕截图") + +## 更多功能 + +更多导出功能使用可以参照 `Easy Excel` [官方文档](https://easyexcel.opensource.alibaba.com/docs/current/api/write)。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/import.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/import.md new file mode 100644 index 00000000..f1bbca7d --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/import.md @@ -0,0 +1,202 @@ +# 导入功能 +- - - + +在本框架中引入了 `Easy Excel` 依赖(对 `Apache POI`进行了封装以及扩展),可以对数据进行导入操作(即读 Excel)。 + +## 导入功能使用流程说明 + +### 步骤一:定义导入实体对象 + +以框架中 `SysUserImportVo` 为例: + +```java + /** + * 用户ID + */ + @ExcelProperty(value = "用户序号") + private Long userId; + + // ....................... + + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; +``` + +> 说明:
+> 1. 使用 `@ExcelProperty` 注解标注需要导入的属性。 +> 2. 注解 `@ExcelProperty` 中 `value` 属性代表表格头部标题字段,`converter` 代表使用的转换器,后面会详细说明。 +> 3. 注解 `@ExcelDictFormat` 为自定义注解,与自定义转换器结合使用,同样在后面进行详细说明。 +> 4. 对象禁止使用链式注解 `@Accessors(chain = true)`,会找不到set方法。 + +### 步骤二:使用导入方法 + +以框架中 `SysUserController#importData` 方法为例: + +```Java + /** + * 导入数据 + * + * @param file 导入文件 + * @param updateSupport 是否更新已存在数据 + */ + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @SaCheckPermission("system:user:import") + @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception { + // 导入方法 + ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport)); + return R.ok(result.getAnalysis()); + } +``` +> 说明:
+> 使用 `ExcelUtil.importExcel` 方法完成导出功能,上述 Demo 传入参数分别是:导入文件流,导入对象类型,导入监听器 `SysUserImportListener`。 + +## 框架工具使用说明 + +### 1:字典转换器 + +字典转换器 `ExcelDictConvert` 与自定义注解 `@ExcelDictFormat` 结合使用,标注在需要转换的属性上。 + +使用方式一: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_user_sex") + private String sex; +``` + +使用方式二: + +```Java + /** + * 用户性别 + */ + @ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp="0=男,1=女,2=未知", separator=",") + private String sex; +``` + +`@ExcelDictFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|------------------|--------|-----|-----------------------------------| +| dictType | String | "" | 字典的type值 (如: sys_user_sex) | +| readConverterExp | String | "" | 读取内容转表达式 (如: 0=男,1=女,2=未知) | +| separator | String | "," | 与 readConverterExp 属性结合使用,表达式的分隔符 | + +### 2:枚举转换器 + +字典转换器 `ExcelEnumConvert` 与自定义注解 `@ExcelEnumFormat` 结合使用,标注在需要转换的属性上。 + +使用方式: + +```Java + /** + * 用户类型 + *

+ * 使用ExcelEnumFormat注解需要进行下拉选的部分 + */ + @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) + @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") + private String userStatus; +``` + +`@ExcelEnumFormat` 注解属性说明: + +| 属性名称 | 属性类型 | 默认值 | 说明 | +|-----------|------------|------|------------------------------| +| enumClass | Enum Class | - | 字典枚举类型 | +| codeField | String | code | 字典枚举类中对应的 code 属性名称,默认为 code | +| textField | String | text | 字典枚举类中对应的 text 属性名称,默认为 text | + + +### 3:导入监听器 + +#### 3.1:ExcelListener 监听器接口 + +`ExcelListener` 扩展了 `ReadListener` 接口,增加了获取结果方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700181723794469524/99bf83c9_4959041.png "屏幕截图") + +#### 3.2:DefaultExcelListener 默认监听器 + +`DefaultExcelListener` 默认监听器在读 Excel 时调用,主要对数据进行校验、解析、异常处理、返回结果等。导入操作时如果没有特别指定则使用该监听器。 + +#### 3.3:SysUserImportListener 用户导入监听器 + +`SysUserImportListener` 用户导入监听器是在用户导入时调用的监听器。 + +该监听器重写了 `invoke` 反射接口,对导入的用户数据进行了校验;重写了 `getExcelResult` 获取结果接口,返回结果数据。 + +#### 3.4:ExportDemoListener 带下拉框的导入监听器 + +`ExportDemoListener` 是对带有下拉框的 Excel 进行处理的导入监听器。 + +## Easy Excel 常用注解 + +`Easy Excel` 提供了丰富的注解可以对导出对象进行定制化操作,这里的注解说明针对的是原生注解。 + +| 类型 | 注解名称 | 使用举例 | 说明 | +|-------|-------------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| 格式化注解 | @DateTimeFormat | @DateTimeFormat(value=格式化值) | 对字符串进行日期格式化 (参照 `java.text.SimpleDateFormat` 书写即可) | +| 格式化注解 | @NumberFormat | @NumberFormat(value=格式化值, roundingMode=舍入模式) | 对字符串进行数值格式化 (参照 `java.text.DecimalFormat` 书写即可, `roundingMode` 默认 `RoundingMode.HALF_UP`) | +| 属性注解 | @ExcelIgnore | @ExcelIgnore | 导出忽略该字段 | +| 属性注解 | @ExcelIgnoreUnannotated | @ExcelIgnoreUnannotated | 默认不管加不加 `@ExcelProperty` 的注解的所有字段都会参与读写,加了 `@ExcelIgnoreUnannotated` 注解以后,不加 `@ExcelProperty` 注解的字段就不会参与 | +| 属性注解 | @ExcelProperty | @ExcelProperty(value=值, order=排序值, index=下标, converter=转换器) | 默认按照对象属性顺序导出,如果设置了 `order` 以及 `index`,优先级 `index` > `order` > 默认;converter 可以自定义 | + +## 扩展使用 + +### 扩展一:自定义转换器实现 + +由于业务需要,原生注解不一定能够符合需要,因而衍生出了自定义转换器。能够实现定制化的内容转换需要。 +以下以框架中的字典转换器 `ExcelDictConvert` 为例进行说明。 + +字典转换器 `ExcelDictConvert`,字典转换器使用了自定义注解 `@ExcelDictFormat` 配合使用。 + +_**注:自定义转换器并非一定需要自定义注解,也可以针对已有的注解进行自定义转换实现。**_ + +#### 实现方式 + +自定义转换器需要实现 `com.alibaba.excel.converters.Converter` 接口,实现接口中的方法。 + +![输入图片说明](https://foruda.gitee.com/images/1700104014304819918/33eb0c42_4959041.png "屏幕截图") + +转换方法 `ExcelDictConvert#convertToJavaData` : + +![输入图片说明](https://foruda.gitee.com/images/1700182975516396213/d3c020f9_4959041.png "屏幕截图") + +### 扩展二:自定义监听器实现 + +自定义监听器主要用于在读取解析 Excel 数据时进行自定义操作。 +以下以框架中的用户导入监听器 `SysUserImportListener` 为例进行说明。 + +#### 实现方式 +1. 继承分析事件监听器 `AnalysisEventListener` 以及实现 Excel 监听器 `ExcelListener`。 + +![输入图片说明](https://foruda.gitee.com/images/1700184652693497753/09333dac_4959041.png "屏幕截图") + +2. 显示使用构造函数,否则将导致空指针。 + +![输入图片说明](https://foruda.gitee.com/images/1700184759075616584/cf05b0ed_4959041.png "屏幕截图") + +3. 实现 `invoke` 方法,对数据进行解析操作,可以在此方法对数据进行合法性判断。 + +4. 实现 `getExcelResult` 方法,对结果进行操作,例如返回成功、失败的统计数据。 + +## 更多功能 + +更多导入功能使用可以参照 `Easy Excel` [官方文档](https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read)。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/interface_release.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/interface_release.md new file mode 100644 index 00000000..a3e6e6b6 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/interface_release.md @@ -0,0 +1,25 @@ +# 接口放行 +- - - +## 使用方式 + +在配置文件填写路径放行
+![输入图片说明](https://foruda.gitee.com/images/1678979039071447990/8b272aba_1766278.png "屏幕截图") + +### 注解放行 +版本 4.3.1 以上 建议使用 `@SaIgnore` 忽略注解
+ +支持在类或方法上放行
+**注意: 动态路径会解析成通配符 请设计好接口路径避免问题** + +**例如: `/get/{userId}` 会解析成 `/get/*`**
+![输入图片说明](https://foruda.gitee.com/images/1666595109409104199/5b7d75c7_1766278.png "屏幕截图") + +## 注意事项 + +接口放行后不需要token即可访问
+但是没有token也就无法获取用户信息与鉴权 + +### 解决方案 +删除接口上的鉴权注解
+删除接口内获取用户信息功能
+删除数据库实体类 自动注入 `createBy` `updateBy` 因为会获取用户数据 \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/oss.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/oss.md new file mode 100644 index 00000000..47484ab2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/oss.md @@ -0,0 +1,124 @@ +# 关于OSS模块使用 +- - - +## 重点注意事项 + +`桶/存储区域` 系统会根据配置自行创建分配权限
+~~如手动配置需要设置 `公有读` 权限 否则文件无法访问~~(`aliyun` 还需开通跨域配置)
+4.4.0 版本支持配置`公有/私有`权限(`aliyun` 还需开通跨域配置)
+访问站点 后严禁携带其他 `url` 例如: `/`, `/ruoyi` 等
+**阿里云与腾讯云SDK访问站点中不能包含桶名 系统会自动处理**
+**minio 站点不允许使用 localhost 请使用 127.0.0.1**
+**访问站点与自定义域名 都不要包含 `http` `https` 前缀 设置`https`请使用选项处理** + +## 代码使用 + +> 参考 `SysOssService.upload` 用法
+> 使用 `OssFactory.instance()` 获取当前启用的 `OssClient` 实例
+> 进行功能调用 获取返回值后 存储到对应的业务表 + +![输入图片说明](https://foruda.gitee.com/images/1678978345529639839/d350ec0b_1766278.png "屏幕截图") + + +## 功能配置 + +### 配置OSS + +> 进入 `系统管理 -> 文件管理 -> 配置管理` 填写对应的OSS服务相关配置
+ +![输入图片说明](https://foruda.gitee.com/images/1678978349820700551/1f91a237_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978354387669856/3a91a3a9_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978358019307086/0c2523e4_1766278.png "屏幕截图") + +**重点说明** + +> 云厂商只需修改 `访问站点`对应的域 切勿乱改(云厂商强烈建议绑定自定义域名使用 七牛云必须绑定[官方规定])
+ +![输入图片说明](https://foruda.gitee.com/images/1678978362358100362/5c2c4d20_1766278.png "屏幕截图") + +> 七牛云 访问站点
+ + +![输入图片说明](https://foruda.gitee.com/images/1678978366254745764/e93a65ff_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978369853348732/79e8950e_1766278.png "屏幕截图") + +> 阿里云 访问站点 + +![输入图片说明](https://foruda.gitee.com/images/1678978373981462025/56a70398_1766278.png "屏幕截图") + +> 腾讯云 访问站点 + +![输入图片说明](https://foruda.gitee.com/images/1678978378697093134/785517f3_1766278.png "屏幕截图") + +### MinIO 使用 https访问站点 + +**注意:S3 API 签名计算算法不支持托管 MinIO Server API 的代理方案** + +[ minio https 配置方式](https://blog.csdn.net/Michelle_Zhong/article/details/126484358) + +### 切换OSS + +> 再配置列表点击 `状态` 按钮开启即可(注意: 只能开启一个OSS默认配置)
+> 手动使用 `OssFactory.instance("configKey")`
+ +![输入图片说明](https://foruda.gitee.com/images/1678978383700118702/7f3fa0c5_1766278.png "屏幕截图") + +### 扩展分类 + +> 如有文件分类 建议创建多个 oss配置 进行切换存储
+ +例如: 创建一个 图片存储的 oss配置
+指定唯一的 `configKey` 与 `前缀目录` 或 直接使用独立的`桶`
+独立桶的特点 可以自定义访问权限
+例如: 创建一个私有文件存储桶 不对外开放
+ +![输入图片说明](https://foruda.gitee.com/images/1678978389139754119/140be1df_1766278.png "屏幕截图") + +> 指定需要使用的配置
+> 使用 `OssFactory.instance("image")` 获取的 `OssClient` 会加载上图的配置 从而达到上传不同的目录或桶 + + +![输入图片说明](https://foruda.gitee.com/images/1678978397550123641/1b536881_1766278.png "屏幕截图") + + +### 上传图片或文件 + +> 进入 `系统管理 -> 文件管理` 点击 `上传文件` 或 `上传图片` 根据选项选择即可 会对应上传到配置开启的OSS内
+ +![输入图片说明](https://foruda.gitee.com/images/1678978401028132972/445d058e_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978404388284503/5459da29_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978408761764835/c81651fc_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978412748494539/7bae621f_1766278.png "屏幕截图") + +### 列表展示 + +> 默认展示图片(可预览) 文件会展示路径
+ +![输入图片说明](https://foruda.gitee.com/images/1678978416327601385/af1ecb3b_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678978422249633007/19d68eaa_1766278.png "屏幕截图") + +> 可以点击 `预览禁用启用` 按钮对是否展示进行更改 + +![输入图片说明](https://foruda.gitee.com/images/1678978426017014926/4f7fa3f3_1766278.png "屏幕截图") + +> 点击禁用后 图片会变成路径展示 + +![输入图片说明](https://foruda.gitee.com/images/1678978429692592556/0231d778_1766278.png "屏幕截图") + +> 也可再 `参数设置` 更改预览状态 将 `OSS预览列表资源` 改为 `false` 即可关闭预览 + +![输入图片说明](https://foruda.gitee.com/images/1678978433769403801/7d480e76_1766278.png "屏幕截图") + +### 删除功能 + +> 点击列表上方或后方 `删除` 按钮 会根据OSS服务商类型 调用对应的删除(注意: 需确保对应的服务商配置正确)
+> 可勾选多服务商类型的文件进行删除 系统会自动判断 + +![输入图片说明](https://foruda.gitee.com/images/1678978438265941745/f32edc72_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678978441938542080/43ed7c3d_1766278.png "屏幕截图") + +### 下载功能 + +> 点击列表后方对应资源的 `下载` 按钮 根据需求填写文件名 点击确认即可完成下载 + +![输入图片说明](https://foruda.gitee.com/images/1678978448927336261/409af888_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678978452761792483/ed0a4a72_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/page.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/page.md new file mode 100644 index 00000000..97b6c525 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/page.md @@ -0,0 +1,32 @@ +# 分页功能 +- - - +## 对应版本 + +> 3.5.0 版本 + +## 重点说明 + +> 项目使用 `mybatis-plus` 分页插件 实现分页功能 大致用法与 MP 一致 [MP分页文档](https://baomidou.com/pages/97710a/)
+> 项目已配置分页合理化 页数溢出 例如: 一共5页 查了第6页 默认返回第一页
+ +![输入图片说明](https://foruda.gitee.com/images/1678977804058241635/b5cb362d_1766278.png "屏幕截图") + +## 代码用法 + +> `Controller` 使用 `PageQuery` 接收分页参数 具体参数参考 `PageQuery` + +![输入图片说明](https://foruda.gitee.com/images/1678977844048821356/1f994221_1766278.png "屏幕截图") + +> 构建 `Mybatis-Plus` 分页对象
+> 使用 `PageQuery#build()` 方法 可快速(基于当前对象数据)构建 `MP` 分页对象 + +![输入图片说明](https://foruda.gitee.com/images/1678977862816976499/b82c1638_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678977876194578744/eaa7b854_1766278.png "屏幕截图")
+ +具体用法与 `MP` 一致 + +> 自定义 `SQL` 方法分页
+> 只需在 `Mapper` 方法第一个参数和返回值 重点: 第一个参数 标注分页对象 + +![输入图片说明](https://foruda.gitee.com/images/1678977898181729571/6e102731_1766278.png "屏幕截图")
+![输入图片说明](https://foruda.gitee.com/images/1678977906788451483/70979292_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/param_check.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/param_check.md new file mode 100644 index 00000000..95ee19d8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/param_check.md @@ -0,0 +1,158 @@ +# 参数校验 +- - - + +参数校验在日常开发中十分常见,在本框架中引入了 `spring-boot-starter-validation` 依赖,底层基于 `hibernate-validator`,可以对参数进行校验。 + +## 参数校验使用 + +### 方法一:使用 `@Validated` 注解 + +#### 步骤一:标注 `@Validated` + +`@Validated` 可以标注在类上,或者是参数前。 + +```Java +/** 标注在类上 **/ +@Validated +@RestController +@RequestMapping("/auth") +public class AuthController { + + @PostMapping("/login") + public R login(@RequestBody LoginBody body) { + // ... + } + +} +``` + +```Java +/** 标注在参数前 **/ +@PostMapping +public R add(@Validated @RequestBody SysUserBo user) { + // ... +} +``` + +#### 步骤二:标注校验注解 + +在参数中加入校验注解。 + +```Java +public class SysUserBo { + + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") + private String userName; + + @NotBlank(message = "用户昵称不能为空") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符") + private String nickName; + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符") + private String email; + +} +``` + +常见校验注解见文末附表。 + +_注:message 支持 EL 表达式,{max} 直接读取前面的参数值。_ + +### 方法二:使用校验工具类 `ValidatorUtils` + +`org.dromara.common.core.utils.ValidatorUtils` + +![输入图片说明](https://foruda.gitee.com/images/1700050047426137432/206bd032_4959041.png "屏幕截图") + +使用方式 1:校验所有带有校验注解的属性 + +```Java +// 校验所有带有校验注解的属性 +ValidatorUtils.validate(object); +``` + +使用方式 2:按照分组校验属性(可以传多个分组) + +```Java +// 按照分组校验属性(可以传多个分组) +ValidatorUtils.validate(object, group); +``` + +## 扩展使用 + +### 扩展一:自定义校验注解 + +除了已有的校验注解以外,可以结合业务进行自定义。 + +以框架中的 `@Xss` 注解为例进行说明。 + +```Java +@Xss(message = "用户账号不能包含脚本字符") +@NotBlank(message = "用户账号不能为空") +@Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") +private String userName; +``` + +#### 1:新增 `@Xss` 注解 + +`org.dromara.common.core.xss.Xss` + +![输入图片说明](https://foruda.gitee.com/images/1700048074014527096/b4e230c2_4959041.png "屏幕截图") + +#### 2:自定义校验器 + +自定义校验器实现 `jakarta.validation.ConstraintValidator` 接口。 + +`org.dromara.common.core.xss.XssValidator` + +![输入图片说明](https://foruda.gitee.com/images/1700048474563719650/f9172bdc_4959041.png "屏幕截图") + +### 扩展二:自定义分组校验 + +同一个对象在不同的请求中需要校验的参数不同,则可以使用分组校验。 + +#### 1:自定义分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049439236073123/9e0d2e16_4959041.png "屏幕截图") + +#### 2:`@Validated` 注解指定分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049302803077030/c2a985aa_4959041.png "屏幕截图") + +#### 3:校验注解中指定分组 + +![输入图片说明](https://foruda.gitee.com/images/1700049205699437759/96babbd6_4959041.png "屏幕截图") + +## 附录:常用校验注解 + +| 注解 | 使用(只列举特殊参数值) | 参数类型 | 说明 | +|------------------|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------| +| @AssertFalse | @AssertFalse | boolean / Boolean | 元素值必须为 false | +| @AssertTrue | @AssertTrue | boolean / Boolean | 元素值必须为 true | +| @DecimalMax | @DecimalMax(value=值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须小于或等于指定的最大值 | +| @DecimalMin | @DecimalMin(value=值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须大于或等于指定的最小值 | +| @Digits | @Digits(integer=整数位值, fraction=小数位值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 元素必须符合整数位以及小数位范围值 | +| @Email | @Email(regexp=正则表达式, flags=标志) | CharSequence | 元素是否符合正则表达式(正则表达式非必传) | +| @Future | @Future | - java.util.Date
- java.util.Calendar
- java.time.Instant
- java.time.LocalDate
- java.time.LocalDateTime
- java.time.LocalTime
- java.time.MonthDay
- java.time.OffsetDateTime
- java.time.OffsetTime
- java.time.Year
- java.time.YearMonth
- java.time.ZonedDateTime
- java.time.chrono.HijrahDate
- java.time.chrono.JapaneseDate
- java.time.chrono.MinguoDate
- java.time.chrono.ThaiBuddhistDate | 元素必须是未来的时刻、日期或时间 | +| @FutureOrPresent | @FutureOrPresent | 同 @Future | 元素必须是当前或未来的时刻、日期或时间 | +| @Length | @Length(min=最小值, max=最大值) | - CharSequence | 验证字符串是否在包含的 min 和 max 之间 | +| @Max | @Max(value=值) | - BigDecimal
- BigInteger
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须小于或等于指定的最大值 | +| @Min | @Min(value=值) | - BigDecimal
- BigInteger
- byte, short, int, long 及其包装类 | 元素必须是一个数字,其值必须大于或等于指定的最小值 | +| @Negative | @Negative | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须是一个严格的负数(即 0 被视为无效值) | +| @NegativeOrZero | @NegativeOrZero | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须为负数或 0 | +| @NotBlank | @NotBlank | CharSequence | 元素不能为 null,并且必须至少包含一个非空白字符 | +| @NotEmpty | @NotEmpty | - CharSequence
- Collection
- Map
- Array | 元素不能为 null 或空集合 | +| @NotNull | @NotNull | 不限类型 | 元素不能为 null | +| @Null | @Null | 不限类型 | 元素必须为 null | +| @Past | @Past | 同 @Future | 元素必须是过去的瞬间、日期或时间 | +| @PastOrPresent | @PastOrPresent | 同 @Future | 元素必须是过去或现在的瞬间、日期或时间 | +| @Pattern | @Pattern(regexp=正则表达式, flags=标志) | CharSequence | 元素必须与指定的正则表达式匹配(正则表达式遵循 Java 正则表达式约定) | +| @Positive | @Positive | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须是一个严格的正数(即 0 被视为无效值) | +| @PositiveOrZero | @PositiveOrZero | - BigDecimal
- BigInteger
- byte,short,int,long,float,double 及其包装类 | 元素必须为正数或 0 | +| @Range | @Range(min=最小值, max=最大值) | - BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long 及其包装类 | 验证元素是否在包含的 min 和 max 之间 | +| @Size | @Size(min=最小值, max=最大值) | - CharSequence
- Collection
- Map
- Array | 验证元素是否在包含的 min 和 max 之间 | +| @Valid | @Valid | 对象 | 级联验证 | + +更多注解可参考包: `org.hibernate.validator` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions.md new file mode 100644 index 00000000..384b7749 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions.md @@ -0,0 +1,144 @@ +# 关于数据权限 +- - - +* 参考 demo 模块用法(需导入 test.sql 文件) + +### 新版数据权限功能: +1.支持自动注入 sql 数据过滤
+2.查询、更新、删除 限制
+3.支持自定义数据字段过滤
+4.模板支持 spel 语法实现动态 Bean 处理
+5.支持与菜单权限标识符联合使用(5.2.X新功能) + +### 数据权限相关代码 + +| 类 | 说明 | 功能 | +|-------------------------------|-----------------|----------------------------------------| +| DataScopeType | 数据权限模板定义 | 用于定义数据权限模板 | +| DataPermission | 数据权限组注解 | 用于标注开启数据权限 (默认过滤部门权限) | +| DataColumn | 具体的数据权限字段标注 | 用于替换数据权限模板内的 key 变量 | +| PlusDataPermissionInterceptor | 数据权限 sql 拦截器 | 用于拦截所有 sql 检查是否标注了 `DataPermission` 注解 | +| PlusDataPermissionHandler | 数据权限处理器 | 用于处理被拦截到的 sql 为其添加数据权限过滤条件 | +| DataPermissionHelper | 数据权限助手 | 操作数据权限上下文变量 | +| SysDataScopeService | 自定义 Bean 处理数据权限 | 用于自定义扩展 | + +## 忽略数据权限 + +1.如果需要指定单独 SQL 不开启过滤,可在对应的 Mapper 接口添加如下忽略注解: +``` +@InterceptorIgnore(dataPermission = "true") +``` + +2.如果需要在业务层忽略数据权限,可调用以下方法: +``` +# 无返回值 +DataPermissionHelper.ignore(() -> { 业务代码 }); +# 有返回值 +Class result = DataPermissionHelper.ignore(() -> { return 业务代码 }); +``` + +### 使用方式 `参考demo模块` +数据权限体系 `用户 -> 多角色 => 角色 -> 单数据权限` +> 例子: 用户A 拥有两个角色
+> 角色A 部门经理 可查看 本部门及以下部门的数据
+> 角色B 兼职开发 可查看 仅自己的数据 + +> 创建角色 test1 为 本部门及以下 + +![输入图片说明](https://foruda.gitee.com/images/1678978669666831574/b51ed0a3_1766278.png "屏幕截图") + +> 创建角色 test2 为 仅本人 + +![输入图片说明](https://foruda.gitee.com/images/1678978674159035056/69cf32ad_1766278.png "屏幕截图") + +> 将其分配给用户 test + +![输入图片说明](https://foruda.gitee.com/images/1678978680492570269/a47b6afc_1766278.png "屏幕截图") + +### 编写列表查询(注意: 数据权限注解只能在 Mapper 层使用) + +> 标注数据权限注解 `dept_id` 为过滤部门字段 `user_id` 为过滤创建用户 + +![输入图片说明](https://foruda.gitee.com/images/1678978687179608427/d6b83c30_1766278.png "屏幕截图") + +### 重点注意: 如下情况不生效 + +> 有自定义实现方法 最终执行的mapper不是这个方法 所以无法生效 +> +> 解决方案: 一直往下点 找到最终的执行mapper重写即可 + +![输入图片说明](https://foruda.gitee.com/images/1678978692558777291/78b0a3dd_1766278.png "屏幕截图") + +### 编写数据权限模板 + +![输入图片说明](https://foruda.gitee.com/images/1678978697141183499/cfc1cb6a_1766278.png "屏幕截图") + +1.`code` 为关联角色的数据权限 `code`
+2.`sqlTemplate` 为 sql 模板
+`#{#deptName}` 为模板变量 对应权限注解的 `key`
+`#{@sdss}` 为模板 Bean 调用 调用其 Bean 的处理方法
+3.`elseSql` 为兜底 sql 处理当前角色与标注的注解 无对应的情况
+例如 数据权限为仅本人 且 方法并未标注具体过滤注解 则 填充 `1 = 0` 使条件不满足 不允许查看
+更详细用法可以参考 `DataScopeType` 注释 + +### 测试代码 + +> 使用 `管理员` 用户优先测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978703250082481/e93a68a5_1766278.png "屏幕截图") + +> 使用 `test` 用户测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978710644676604/d7f80487_1766278.png "屏幕截图") + +> 使用 `test` 删除一条不属于自己的数据 +> sql执行为不满足条件 不允许删除 + +![输入图片说明](https://foruda.gitee.com/images/1678978715711122947/441d61f7_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678978720298532619/a35b1147_1766278.png "屏幕截图") + + +> 使用 `test` 修改与删除同理
+> 具体实现为 更新和删除方法 标注数据权限注解 + +![输入图片说明](https://foruda.gitee.com/images/1678978725329242504/a70491a1_1766278.png "屏幕截图") + +### 自定义SQL模板 + +> 1.首先在角色管理 数据权限下拉框 添加自定义模板
+> 为什么不放置到系统字典问题: 因数据权限与模板绑定 不应随意改动 最好事先定义好 + +![输入图片说明](https://foruda.gitee.com/images/1678978730563169865/3459ee17_1766278.png "屏幕截图") + +> 2.代码 `DataScopeType` 自定义一个SQL模板 + +![输入图片说明](https://foruda.gitee.com/images/1678978735588305505/3f030c67_1766278.png "屏幕截图") + +> 3.标注权限注解 + +![输入图片说明](https://foruda.gitee.com/images/1678978742259837391/eabe5caa_1766278.png "屏幕截图") + +> 4.设置数据权限变量 + +![输入图片说明](https://foruda.gitee.com/images/1678978746778429543/e211201f_1766278.png "屏幕截图") + +> 5.测试 + +![输入图片说明](https://foruda.gitee.com/images/1678978751875467640/7d210cf4_1766278.png "屏幕截图") + +### mybatis-plus 原生方法 增加数据权限过滤 + +> 首先查看需要重写的方法源码 重点`方法源码` `方法源码` `方法源码`
+> 例如重写 `selectPage` 方法
+ +![输入图片说明](https://foruda.gitee.com/images/1678978757955000897/8315695c_1766278.png "屏幕截图") + +> 复制源码到自己的 `Mapper` 并增加数据权限注解 注意左边出现重写图标 即为重写成功
+ +![输入图片说明](https://foruda.gitee.com/images/1678978763224011694/bbea25a1_1766278.png "屏幕截图") + +### 支持类标注 + +> 获取规则 `方法 > 类` 注意: 类标注后 所有方法(包括父类方法) 都会进行数据权限过滤 + +![输入图片说明](https://foruda.gitee.com/images/1678978767336534896/fb13ee99_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions_control.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions_control.md new file mode 100644 index 00000000..fa3a079c --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/permissions_control.md @@ -0,0 +1,180 @@ +# 权限控制 +- - - + +本文采用 `Sa-Token` 框架实现权限控制。[官方文档传送门](https://sa-token.cc/doc.html#/) + +## 权限校验 +权限校验指的是校验用户是否拥有访问某个 API 的能力。 + +通常情况下,一个 API 对应一个权限码,如果用户具备当前 API 的权限码,即代表有能力访问该 API。 + +### 1:权限标识 +在本系统中,每一个菜单功能都有对应的权限标识,可以在菜单管理中进行设置。 + +> 注: +> 1. 前后端的权限标识要保持一致。 +> 2. 权限标识可以使用通配符`*`。 + +![输入图片说明](https://foruda.gitee.com/images/1701086497939145368/133fb327_4959041.png "屏幕截图") + + +### 2:校验方法 +#### 2.1:使用 `@SaCheckPermission` 注解进行校验 +`@SaCheckPermission` 注解是由 `Sa-Token` 框架提供的角色校验注解,可以标注在方法上或类上。 + +- 单个权限校验: + +```Java +@SaCheckPermission("system:user:list") +``` + +- 多个权限校验(或模式,满足任意一个权限即可): + +```Java +@SaCheckPermission( + value = { + "system:user:list", + "system:user:query" + }, + mode = SaMode.OR +) +``` + +- 多个权限校验(与模式,必须满足所有权限): + +```Java +@SaCheckPermission( + value = { + "system:user:list", + "system:user:query" + }, + mode = SaMode.AND +) +``` + +#### 2.2:使用 `StpUtil` 工具类校验 +`StpUtil` 工具类是由 `Sa-Token` 框架提供的权限工具类,提供了常用的校验方法。 + +- 判断当前用户是否拥有某个权限(返回 `boolean`): + +```Java +StpUtil.hasPermission("system:user:list"); +``` + +- 单个权限校验: + +```Java +StpUtil.checkPermission("system:user:list"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +- 多个权限校验(或模式,满足任意一个权限即可): + +```Java +StpUtil.checkPermissionOr("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +- 多个权限校验(与模式,必须满足所有权限): + +```Java +StpUtil.checkPermissionAnd("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotPermissionException` + +## 角色校验 +角色校验指的是校验用户是否拥有某个指定角色。 + +### 1:权限标识 +在本系统中,每个角色都拥有唯一的权限字符。 + +除了超级管理员角色外,其他角色的权限字符可以通过角色管理进行设置。 + +![输入图片说明](https://foruda.gitee.com/images/1701085080527279823/3255961d_4959041.png "屏幕截图") + +### 2:校验方法 +#### 2.1:使用 `@SaCheckRole` 注解校验 +`@SaCheckRole` 注解是由 `Sa-Token` 框架提供的角色校验注解,可以标注在方法上或类上。 + +- 单个角色校验 + +```Java +@SaCheckRole("superadmin") +``` + +- 多个角色校验(或模式,满足任意一个角色即可): + +```Java +@SaCheckRole( + value = { + "superadmin", + "admin" + }, + mode = SaMode.OR +) +``` + +- 多个角色校验(与模式,必须满足所有角色): + +```Java +@SaCheckRole( + value = { + "superadmin", + "admin" + }, + mode = SaMode.AND +) +``` + +#### 2.2:使用 `StpUtil` 工具类校验 +`StpUtil` 工具类是由 `Sa-Token` 框架提供的权限工具类,提供了常用的校验方法。 + +- 判断当前用户是否拥有某个角色(返回 `boolean`): + +```Java +StpUtil.hasRole("superadmin") +``` + +- 单个权限校验: + +```Java +StpUtil.checkRole("system:user:list"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +- 多个权限校验(或模式,满足任意一个角色即可): + +```Java +StpUtil.checkRoleOr("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +- 多个权限校验(与模式,必须满足所有角色): + +```Java +StpUtil.checkRoleAnd("system:user:list", "system:user:query"); +``` +如果验证未通过,则抛出异常: `NotRoleException` + +## 角色权限双重 `OR` 校验 +除了分开校验以外,权限和角色也可以进行组合,表示备选校验。 + +简单举个例子: + +假设某个 API 的权限码为 `system:user:list`,角色 `admin` 可以调用,则可以这样写: + +```Java +@SaCheckPermission(value = "system:user:list", orRole = "admin") +``` + +以上权限只需要满足任意一项即可。更多写法可以参考 `Sa-Token` [官方文档](https://sa-token.cc/doc.html#/use/at-check?id=_4%e3%80%81%e8%a7%92%e8%89%b2%e6%9d%83%e9%99%90%e5%8f%8c%e9%87%8d-or%e6%a0%a1%e9%aa%8c)。 + +## 当前用户的所有权限 +本系统中实现了 `StpInterface` 接口,可以对用户的权限以及角色进行管理,并且可以根据不同的用户类型进行设置。 + +具体参考类:`org.dromara.common.satoken.core.service.SaPermissionImpl` + +## 忽略权限校验 +请参考文档:[接口放行](/ruoyi-vue-plus/framework/basic/interface_release?id=接口放行) + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/social.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/social.md new file mode 100644 index 00000000..a004434b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/social.md @@ -0,0 +1,68 @@ +# 第三方授权功能 +- - - +## 版本 >= 5.X + +## 前置说明 +1. 该功能基于 `JustAuth` 实现,支持多家平台实现第三方授权登录。 +2. 以 `Gitee` 授权登录为例进行本功能的使用说明。 +3. 其他第三方授权配置信息获取方式可参考 `JustAuth` [官方文档](https://www.justauth.cn/guide/)。
+ + ![输入图片说明](https://foruda.gitee.com/images/1690937097426867003/91d80587_4959041.png "屏幕截图") + +## 第三方授权配置 + +### 申请三方应用(以gitee为例) + +![输入图片说明](https://foruda.gitee.com/images/1700641775779304627/1cf1b56f_1766278.png "屏幕截图") + +### 更改后端配置 `application-dev.yml` + +![输入图片说明](https://foruda.gitee.com/images/1690936741844431943/580f8998_4959041.png "屏幕截图") + +**注:内网地址无法回调,请使用外网可以访问的地址。** + +![输入图片说明](https://foruda.gitee.com/images/1690940457570856867/ce22df18_4959041.png "屏幕截图") + +### 更改前端配置 `login.vue` + +![输入图片说明](https://foruda.gitee.com/images/1690937306197173754/5c1ece29_4959041.png "屏幕截图") + +## 授权登录(未绑定第三方平台) + +### 步骤一:个人中心授权第三方应用 + +![输入图片说明](https://foruda.gitee.com/images/1690938449386201097/ea375106_4959041.png "屏幕截图") + +### 步骤二:同意授权 + +![输入图片说明](https://foruda.gitee.com/images/1690938522418523183/81b327bf_4959041.png "屏幕截图") + +顶部出现授权成功,并跳转到系统首页。
+ +![输入图片说明](https://foruda.gitee.com/images/1690938559178527841/563168e4_4959041.png "屏幕截图")
+ +![输入图片说明](https://foruda.gitee.com/images/1690938636375977741/8ceb77cf_4959041.png "屏幕截图") + +查看第三方应用可看到授权成功的个人信息。
+ +![输入图片说明](https://foruda.gitee.com/images/1690938725512311321/5532a2a9_4959041.png "屏幕截图") + +## 授权登录(已绑定第三方平台) + +### 步骤一:点击登录页面图标 + +![输入图片说明](https://foruda.gitee.com/images/1690938908352243992/fd044381_4959041.png "屏幕截图") + +### 步骤二:同意授权 + +![输入图片说明](https://foruda.gitee.com/images/1690938522418523183/81b327bf_4959041.png "屏幕截图") + +## 解除授权绑定 + +### 步骤一:个人中心点击解绑第三方应用 + +![输入图片说明](https://foruda.gitee.com/images/1690939087877969002/4ef324e7_4959041.png "屏幕截图") + +### 步骤二:点击确定完成解绑 + +![输入图片说明](https://foruda.gitee.com/images/1690939108017661775/7236088d_4959041.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/tenant.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/tenant.md new file mode 100644 index 00000000..33953086 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/tenant.md @@ -0,0 +1,121 @@ +# 多租户功能 +- - - +## 版本 >= 5.X + +## 前置说明(重要) +1. 本框架多租户功能的实现是基于 [MyBatis-Plus 多租户插件](https://baomidou.com/pages/aef2f2/#tenantlineinnerinterceptor) 的,只支持最简单的隔离。 +2. 本系统默认开启多租户功能。 +3. 多租户业务表建表需要加上租户id `tenant_id`,可参考其他系统表。 +4. 非多租户表可在配置文件进行配置排除。 +5. 只有超级管理员支持切换租户。 + +## 多租户使用流程(先说结论再展开!) +0. 开启多租户配置(系统默认已经开启) +1. 登录界面(可以选择不同租户) +> 注:如果为租户设置了绑定域名,则只能选择当前域名相关的租户列表。 +2. 设置多租户套餐 +3. 新增/修改租户(需要选择套餐) +4. 切换租户(仅超级管理员可操作) + +## 多租户配置 +`application.yml`
+ +> 开关 `enable` 节点不用废话。
+> 如果不需要过滤租户的表可在 `excludes` 节点下添加。 + +**注意: 如果已经基于租户模式启动了程序 关闭租户必须删除mysql与redis内的相关数据重新导入sql** + +![输入图片说明](https://foruda.gitee.com/images/1680168468127690787/2cd3279e_4959041.png "屏幕截图") + +## 忽略租户 + +1.如果需要指定单独 SQL 不开启过滤,可在对应的 Mapper 接口添加如下忽略注解: +``` +@InterceptorIgnore(tenantLine = "true", dataPermission = "false") +``` +**此处注意事项 使用此注解如果需要开启数据权限 dataPermission = "false" 必须添加 mp的注解默认是忽略数据权限的 会导致数据权限失效** + +2.如果需要在业务层忽略多租户,可调用以下方法(推荐使用): +``` +# 无返回值 +TenantHelper.ignore(() -> { 业务代码 }); +# 有返回值 +Class result = TenantHelper.ignore(() -> { return 业务代码 }); +``` + +## 动态切换租户 + +**仅适用于特殊需求业务(例如: 创建租户时, 对该租户操作一些数据, 或者需要去其他租户查一些数据等) 禁止乱用后果自负** + +``` +# 无返回值 +TenantHelper.dynamic(租户id, () -> { 业务代码 }); +# 有返回值 +Class result = TenantHelper.dynamic(租户id, () -> { return 业务代码 }); +``` + +## 登录界面 + +![输入图片说明](https://foruda.gitee.com/images/1680173982933030545/bca146d7_4959041.png "屏幕截图") + +> 注:如果为租户设置了绑定域名,则只能选择当前域名相关的租户列表。 + +## 租户套餐管理 +### 租户套餐新增 +![输入图片说明](https://foruda.gitee.com/images/1680174317475230288/352957a1_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680174602877523112/fc194f17_4959041.png "屏幕截图") + +> 注: +> 1、先新增套餐再新增租户,因为租户新增之后无法修改所选套餐。 +> 2、租户所关联的套餐如果后续有修改可以进行同步。 + + +## 租户管理 +### 默认租户 +> 注:默认租户无法修改 + +![输入图片说明](https://foruda.gitee.com/images/1680174738913576400/b6aca11a_4959041.png "屏幕截图") + +### 新增租户 +#### 填写表单 +![输入图片说明](https://foruda.gitee.com/images/1680174945220618443/f7181b51_4959041.png "屏幕截图") + +#### 选择新增的租户套餐 +![输入图片说明](https://foruda.gitee.com/images/1680174991869792688/0dbaadd6_4959041.png "屏幕截图") + +#### 新增完成 +![输入图片说明](https://foruda.gitee.com/images/1680175033853525725/42e64b4d_4959041.png "屏幕截图") + +#### 登录租户 +![输入图片说明](https://foruda.gitee.com/images/1680176145378931134/e05f347e_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176208161104366/44a935f1_4959041.png "屏幕截图") + +### 修改租户 +#### 配置域名 +![输入图片说明](https://foruda.gitee.com/images/1680175251192690133/141fa6a6_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680175431036971650/db522d39_4959041.png "屏幕截图") + +#### 没有配置域名 +![输入图片说明](https://foruda.gitee.com/images/1680175541165540240/95e211f7_4959041.png "屏幕截图") + +#### 强调一下:这不是bug! +> 注:域名的配置就是为了绑定特定租户! + +### 同步套餐 +应用场景:租户套餐进行了修改,配置的菜单需要同步到特定租户。 +(不是所有租户都有更新套餐的权利, 这是跟钱挂钩的) + +> 点一下按钮的事,图略。 + +## 切换租户(仅超级管理员) +> 注:管理员切换租户不是切换用户,切换的只是数据,管理员拥有所有权限。 + +![输入图片说明](https://foruda.gitee.com/images/1680176324802967804/5c5d6fc3_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176431031189788/0c3f924c_4959041.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1680176496555243569/624ec677_4959041.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/user.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/user.md new file mode 100644 index 00000000..b665ac5a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/basic/user.md @@ -0,0 +1,85 @@ +# 系统用户相关 +- - - + +> 框架采用sa-token控制权限 并对sa-token的api做了一定的业务封装
+ +## 用户登录 + +> 参考自带多种登录实现 不限制用户数据来源 只需要构建 LoginUser 即可完成登录
+> 例如: `同表不同类型` `不同表` `同表+扩展表`
+ +![输入图片说明](https://foruda.gitee.com/images/1699590555824776931/63d493fc_1766278.png "屏幕截图") + +## 获取用户信息 + +> 完成登录后会生成登录token返回给前端 前端需要再请求头携带token 后端方可获取到对应的用户信息 + +请求头传递格式: `Authorization: Bearer token` + +后端获取用户信息: +```java +LoginUser user = LoginHelper.getLoginUser(); +``` + +## 获取用户信息(基于token) +```java +LoginUser user = LoginHelper.getLoginUser(token); +``` + +## 获取登录用户id +```java +Long userId = LoginHelper.getUserId(); +``` + +## 获取登录用户账户名 +```java +String username = LoginHelper.getUsername(); +``` + +## 获取登录用户所属租户id +```java +String tenantId = LoginHelper.getTenantId(); +``` + +## 获取登录用户所属部门id +```java +Long deptId = LoginHelper.getDeptId(); +``` + +## 获取登录用户类型 +```java +UserType userType = LoginHelper.getUserType(); +``` + +## 获取登录用户其他扩展属性 +```java +Object obj = LoginHelper.getExtra(key); +``` + +## 设置登录用户其他扩展属性 + +参考登录设置 `clientId` 属性 + +![输入图片说明](https://foruda.gitee.com/images/1699591164562734430/42730add_1766278.png "屏幕截图") + +## 判断用户是否为超级管理员 + +```java +// 判断当前登录用户 +boolean b = LoginHelper.isSuperAdmin(); +// 判断用户基于id +boolean b = LoginHelper.isSuperAdmin(userId); +``` + +## 判断用户是否为租户管理员 + +```java +// 判断当前登录用户 +boolean b = LoginHelper.isTenantAdmin(); +// 判断用户基于角色组 +boolean b = LoginHelper.isSuperAdmin(rolePermission); +``` + +## 其他更多操作 +[Sa-Token 官方文档 - 登录认证](https://sa-token.cc/doc.html#/use/login-auth) + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/about_join.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/about_join.md new file mode 100644 index 00000000..593129e8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/about_join.md @@ -0,0 +1,14 @@ +# 关于多表查询 +- - - +## 建议单表查询 + +文章连接: [大连接查询分解好处](https://java.isture.com/db/mysql/mysql-x-optimize-decompose-connection.html) +文章连接: [如何用mp多表查询性能测试](https://developer.aliyun.com/article/858927) + +![输入图片说明](https://foruda.gitee.com/images/1678979482724037085/1e74f3e1_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1666336728402711844/52788205_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1666336945935088277/f60e3288_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1666336954686520161/c6c83adc_1766278.png "屏幕截图") + +**(上图出自 <高性能MySql>)** \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/key.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/key.md new file mode 100644 index 00000000..3ec55fec --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/key.md @@ -0,0 +1,19 @@ +# 主键使用说明 +- - - +## 关于如何使用分布式id或雪花id + +参考 `MybatisPlusConfig` 如需自定义 修改 `Bean` 实现即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979401707903546/e25f6c06_1766278.png "屏幕截图") + +框架默认集成 雪花ID 只需全局更改 主键类型即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979411517764918/1470df04_1766278.png "屏幕截图") + +如单表使用 可单独配置注解 + +![输入图片说明](https://foruda.gitee.com/images/1678979416033986923/2a4c3736_1766278.png "屏幕截图") + +### 重点说明 +* 由于雪花id位数过长 `Long` 类型在前端会失真 +* 框架已配置序列化方案 超越 `JS` 最大值自动转字符串 参考 `BigNumberSerializer` 类 (3.0.0 及以上新增) \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/test.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/test.md new file mode 100644 index 00000000..c6dbfe99 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/test.md @@ -0,0 +1,6 @@ +# 单元测试 +- - - +## 参考文章 +[SpringBoot 2.X 整合 JUnit5 及全方位使用手册](https://lionli.blog.csdn.net/article/details/127576604) +## 参考代码(4.4.0新增) +![输入图片说明](https://foruda.gitee.com/images/1666973091281055549/6e8f58c3_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/transaction.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/transaction.md new file mode 100644 index 00000000..dfad76f8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/explain/transaction.md @@ -0,0 +1,45 @@ +# 事务相关 +- - - +若依文档对事务注解的描述 [关于事务](https://doc.ruoyi.vip/ruoyi/document/htsc.html#%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86) 以下对多数据源事务做补充: + +## 多后端多数据源事务 + +框架支持对接 `seata` 保证分布式多数据源事务
+详情参考多数据源框架文档连接: https://www.kancloud.cn/tracy5546/dynamic-datasource/2268607 + +## 本地多数据源事务 +请使用 `@DSTransactional` 注解 会代理 `@DS` 注解切换后的数据源事务做回滚处理
+只要 `@DSTransactional` 注解下任一环节发生异常,则全局多数据源事务回滚。
+如果BC上也有 `@DSTransactional` 会有影响吗?答:没有影响的。 + +```java +//如AService调用BService和CService的方法,A,B,C分别对应不同数据源。 + +public class AService { + + @DS("a")//如果a是默认数据源则不需要DS注解。 + @DSTransactional + public void dosomething(){ + BService.dosomething(); + CService.dosomething(); + } +} + +public class BService { + + @DS("b") + public void dosomething(){ + //dosomething + } +} + +public class CService { + + @DS("c") + public void dosomething(){ + //dosomething + } +} +``` + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/api_encrypt.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/api_encrypt.md new file mode 100644 index 00000000..fc56df31 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/api_encrypt.md @@ -0,0 +1,38 @@ +# 数据加解密 +- - - + +## 1:API 加密注解 `@ApiEncrypt` +1. 对于标注了 `@ApiEncrypt` 注解的接口,请求参数都必须进行加密。 +2. 注解的参数 `response` 为响应加密标识,默认 `false` 不加密,为 `true` 表示响应加密。 +3. 加密解密逻辑由过滤器实现,详情可参考 `org.dromara.common.encrypt.filter.CryptoFilter`。 + +## 2:API 加密配置 +`application.yml` + +![输入图片说明](https://foruda.gitee.com/images/1701131796468961065/83c464cd_4959041.png "屏幕截图") + +`.env.development` / `.env.production` + +![输入图片说明](https://foruda.gitee.com/images/1709533252413969800/1d0dff25_1766278.png "屏幕截图") + +> 注: +> 1. 公私钥与前端配置文件互为配对,如果需要更换请一同更换。 +> 2. 后端公钥对应前端私钥;后端私钥对应前端公钥。 + +## 3:前端开启加密 +如果需要开启 API 加密,则需要修改 `request` 的 `headers` 内容: +```Javascript +headers: { + isEncrypt: true +} +``` + +![输入图片说明](https://foruda.gitee.com/images/1701137141916998346/5e839bbe_4959041.png "屏幕截图") + +## 4.关于请求响应参数加解密说明 + +如何加解密请求响应参数看这里 -> [关于请求响应参数解密](/questions/api_encrypt.md) + +## 密钥生成说明 + +![输入图片说明](https://foruda.gitee.com/images/1675577852271308699/9b30258e_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/dynamic_datasource.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/dynamic_datasource.md new file mode 100644 index 00000000..1e81b5e0 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/dynamic_datasource.md @@ -0,0 +1,45 @@ +# 多数据源 +- - - + +### 框架默认 mysql 其他数据库使用说明 + +找到 `ruoyi-admin` 模块在 pom 文件内增加对应的jdbc依赖 + +![输入图片说明](https://foruda.gitee.com/images/1721098535176969987/d42870ca_1766278.png "屏幕截图") + + +### 关于多数据源事务 具体参考 `事务相关` 文档说明 + +### 多数据源框架功能介绍 +多数据源框架官方文档: [dynamic-datasource文档](https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611) + +* 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。 +* 支持数据库敏感配置信息 加密 ENC()。 +* 支持每个数据库独立初始化表结构schema和数据库database。 +* 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。 +* 支持 自定义注解 ,需继承DS(3.2.0+)。 +* 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。 +* 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。 +* 提供 自定义数据源来源 方案(如全从数据库加载)。 +* 提供项目启动后 动态增加移除数据源 方案。 +* 提供Mybatis环境下的 纯读写分离 方案。 +* 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。 +* 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。 +* 提供 基于seata的分布式事务方案。 +* 提供 本地多数据源事务方案。 附:不能和原生spring事务混用。 + +### 用法说明 + +> 加载顺序 `方法 => 类 => 默认`
+ +![输入图片说明](https://foruda.gitee.com/images/1678979069737596299/abe8ae7f_1766278.png "屏幕截图") + +### 配置方式 + +![输入图片说明](https://foruda.gitee.com/images/1678979074000345758/b9238f0b_1766278.png "屏幕截图") + +### 数据库异构 + +例如: `mysql + oracle` 参考对应多数据源框架文档 [dynamic-ds文档](https://www.kancloud.cn/tracy5546/dynamic-datasource) + +![输入图片说明](https://foruda.gitee.com/images/1678979078387192317/2de94a78_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/encrypt.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/encrypt.md new file mode 100644 index 00000000..19d726cd --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/encrypt.md @@ -0,0 +1,28 @@ +# 数据加解密 +- - - +## 框架版本 >= 4.6.0 +## 功能说明 + +数据库 数据存储加密 查询解密功能
+支持加密算法: `BASE64` `AES` `RSA` `SM2` `SM4` + +## 注解 `@EncryptField` + +![输入图片说明](https://foruda.gitee.com/images/1675577493013639395/cd920f15_1766278.png "屏幕截图") + +## 用法说明 + +**详细用法可参考案例 TestEncryptController 测试数据库加解密功能** + +全局默认加密配置(如果注解不配置则使用全局配置) + +![输入图片说明](https://foruda.gitee.com/images/1675577674063566357/dee94786_1766278.png "屏幕截图") + +注解可自定义算法与配置 + +![输入图片说明](https://foruda.gitee.com/images/1675577725117970708/7ee7a833_1766278.png "屏幕截图") + +## 密钥生成说明 + +![输入图片说明](https://foruda.gitee.com/images/1675577852271308699/9b30258e_1766278.png "屏幕截图") + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/idempotent.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/idempotent.md new file mode 100644 index 00000000..46c7f423 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/idempotent.md @@ -0,0 +1,29 @@ +# 防重幂等 +- - - +### 功能介绍 + +防重功能为防止两条相同的数据重复提交导致脏数据或业务错乱
+**注意: 重复提交属于小概率事件 请不要拿并发压测与之相提并论**
+框架防重功能参考 `美团GTIS防重系统` 使用 请求参数与用户Token或URL 生成全局业务ID
+有效防止 `同一个用户` 在 `限制时间` 内对 `同一个业务` 提交 `相同的数据` + +框架防重处理 `支持业务失败或异常` 快速释放限制
+业务处理成功后 会在设置时间内 限制同一条数据的提交
+**注意: 只对同一个用户的同一个接口提交相同的数据有效** + + + + +### 美团GTIS系统流程图 + +[美团 分布式系统互斥性与幂等性问题的分析与解决](https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html) + +![输入图片说明](https://foruda.gitee.com/images/1678979231862359032/34f030c5_1766278.png "屏幕截图") + +### 使用方法 + +在Controller标注 `@RepeatSubmit` 注解即可 + +![输入图片说明](https://foruda.gitee.com/images/1678979236772683145/9fa27e5b_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678979240831458322/8e1fac4b_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/mail.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/mail.md new file mode 100644 index 00000000..6e7413ed --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/mail.md @@ -0,0 +1,17 @@ +# 邮件功能 +- - - +## 配置功能 + +版本: v4.2.0 提供邮件功能 + +修改配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1663555260932007318/fabb2bfa_1766278.png "屏幕截图") + +* `enabled` 为邮件功能开关 + +## 功能使用 + +参考 `demo` 模块 `MailController` 邮件演示案例 + +![输入图片说明](https://foruda.gitee.com/images/1663555374113593089/885b4db2_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/maxkey.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/maxkey.md new file mode 100644 index 00000000..a0b51d24 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/maxkey.md @@ -0,0 +1,20 @@ +# 对接 MaxKey 单点登录 +- - - + +# 安装 MaxKey 应用服务 + +参考 MaxKey 官方文档安装 [MaxKey安装部署](http://www.maxkey.top/doc/docs/intro/) + +# 配置应用 OAuth2.0 认证注册 + +![输入图片说明](https://foruda.gitee.com/images/1693377802128677240/0927270a_1766278.png "屏幕截图") + +# 配置后端服务 + +找到框架 `application-环境.yml` 配置文件 + +修改 `maxkey` 对应的 `client-id` 与 `client-secret` + +![输入图片说明](https://foruda.gitee.com/images/1693378118762354596/2f02c8a3_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1693378168538263792/24476d2a_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sensitive.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sensitive.md new file mode 100644 index 00000000..3e2d92ba --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sensitive.md @@ -0,0 +1,33 @@ +# 数据脱敏 +- - - +## 功能说明 + +系统使用 `Jackson` 序列化策略 对标注了 `Sensitive` 注解的属性进行脱敏处理 + +## 使用教程 + +> 使用注解标注需要脱敏的字段 选择对应的策略 + +![输入图片说明](https://foruda.gitee.com/images/1699523591703893602/ffd6dba2_1766278.png "屏幕截图") + +* strategy 脱敏策略 +* roleKey 角色code(判断用户是否拥有角色权限) +* perms 权限code(判断用户是否拥有标识符权限) + +![输入图片说明](https://foruda.gitee.com/images/1678979315796014155/614adf91_1766278.png "屏幕截图") + +> 可再 `SensitiveStrategy` 内自定义策略 + +![输入图片说明](https://foruda.gitee.com/images/1678979319996224858/3b3e3c8b_1766278.png "屏幕截图") + +## 脱敏逻辑修改 + +> 系统使用通用接口处理是否需要脱敏 多个系统可以自定义不同的脱敏逻辑实现 + +![输入图片说明](https://foruda.gitee.com/images/1678979325448998856/b262e425_1766278.png "屏幕截图") + +> 系统默认处理逻辑为 根据角色与标识符或非管理员脱敏 可自行修改默认实现 + +![输入图片说明](https://foruda.gitee.com/images/1699523752627488891/f82f2f50_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/skywalking.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/skywalking.md new file mode 100644 index 00000000..283eb221 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/skywalking.md @@ -0,0 +1,20 @@ +# Skywalking链路监控 +- - - +## skywalking服务搭建 + +参考文章: https://lionli.blog.csdn.net/article/details/127656534
+多种搭建方式 也可以参考百度 + +## 代码改动 + +https://gitee.com/dromara/RuoYi-Vue-Plus/commit/4d02466fed4f3ea012a80c3359cde9af0737141f
+根据上方commit提交记录 开启注释掉的代码 + +## 本地使用 + +参考文章: https://lionli.blog.csdn.net/article/details/127656534 + +## docker部署使用 + +完成上方代码改动 将下载好的 `agent` 探针 放入服务器 `/docker/skywalking/agent/` 目录下 赋予所有权限即可
+![输入图片说明](https://foruda.gitee.com/images/1669032573170837535/d9901f53_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sms.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sms.md new file mode 100644 index 00000000..a2308ecd --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sms.md @@ -0,0 +1,51 @@ +# 短信模块 +- - - + +# 配置功能 + +### 版本: >= v5.1.0 + +已完成 sms4j 项目整合 文档地址: https://sms4j.com/doc3 + +配置方式 具体厂商配置扩展 可以查看sms4j文档 + +![输入图片说明](https://foruda.gitee.com/images/1705573035997239848/2ca8512d_1766278.png "屏幕截图") + +使用方式 参考文档各种写法 下方为 demo 模块提供示例 + +![输入图片说明](https://foruda.gitee.com/images/1705573001447394180/2bd726d0_1766278.png "屏幕截图") + +### 版本: v4.2.0 提供短信模块 + +短信模块采用SPI加载
+使用哪家的短信 引入哪家的依赖 即可动态加载
+目前支持: `阿里云` `腾讯云` 欢迎扩展PR其他 + +> 参考 `ruoyi-demo` pom文件写法 + +![输入图片说明](https://foruda.gitee.com/images/1678979157797419426/cc9b7444_1766278.png "屏幕截图") + +> 修改配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1678979163029635375/e5fd6e20_1766278.png "屏幕截图") + +* `enabled` 为短信功能开关 +* `endpoint` 为域名 各厂家域名固定 按照文档配置即可 +* `accessKeyId` 密钥id +* `accessKeySecret` 密钥密匙 +* `signName` 签名 +* `sdkAppId` 应用id 腾讯专用 + +## 功能使用 + +参考 `demo` 模块 `SmsController` 短信演示案例
+功能采用 `模板模式` 动态加载对应厂家的工具模板
+引入 `SmsTemplate` 即可使用 + +![输入图片说明](https://foruda.gitee.com/images/1678979168699323982/e9301e84_1766278.png "屏幕截图") + +## 重点须知 + +由于各厂家参数解析不一致 请遵守以下规则 + +![输入图片说明](https://foruda.gitee.com/images/1678979172581090456/ac1f10e8_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sse.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sse.md new file mode 100644 index 00000000..8a738329 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/sse.md @@ -0,0 +1,22 @@ +# SSE功能 +- - - + +## 框架版本 >= 5.2.2 + +## 配置说明 + +![输入图片说明](https://foruda.gitee.com/images/1721986820599622433/1abe5d60_1766278.png "屏幕截图") + +* enabled 是否开启此功能 +* path 应用路径 + +## 使用方法 + +前端连接方式: `http://后端ip:端口/resource/sse?clientid=import.meta.env.VITE_APP_CLIENT_ID&Authorization=Bearer eyJ0eXAiO......` + +其中 `Authorization` 为请求token需要登录后获取 连接成功之后 与框架内其他获取登录用户方式一致 + +`SseMessageUtils.sendMessage` 推送单机消息(特殊需求使用)
+`SseMessageUtils.subscribeMessage` 订阅分布式消息(框架初始化已订阅)
+`SseMessageUtils.publishMessage` 发布分布式消息(推荐使用 所有集群内寻找到接收人)
+`SseMessageUtils.publishAll` 群发消息给所有连接人
\ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/topiam.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/topiam.md new file mode 100644 index 00000000..4778d0f9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/topiam.md @@ -0,0 +1,30 @@ +# 对接 TOPIAM 单点登录 +- - - + +# 安装 TOPIAM 应用服务 + +参考 TOPIAM 官方文档安装 [TOPIAM安装部署](https://eiam.topiam.cn/docs/deployment/) + +# 配置 OIDC 应用 + +在 `登录 Redirect URI` 中填写 `http://localhost:80/oauth/callback?source=topiam` + +# 配置后端服务 + +找到框架 `application-环境.yml` 配置文件 + +修改 `topiam` 对应的 `client-id` 与 `client-secret` + +```yaml +justauth: + # 前端外网访问地址 + address: http://localhost:80 + type: + topiam: + # topiam 服务器地址,可在【应用配置信息】中找到 + server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol + client-id: 449c4*********937************759 + client-secret: ac7***********1e0************28d + redirect-uri: ${justauth.address}/social-callback?source=topiam + scopes: [openid] +``` \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/translation.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/translation.md new file mode 100644 index 00000000..547b15ff --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/translation.md @@ -0,0 +1,34 @@ +# 翻译功能 +- - - +## 框架版本 >= 4.6.0 +## 注解 + +![输入图片说明](https://foruda.gitee.com/images/1675575648043199227/d04b3e21_1766278.png "屏幕截图") + +`@Translation` 翻译注解 用于实体类字段上
+`@TranslationType` 翻译类别注解 用于实现类上标注与 `@Translation` 注解相同的 `type` 类型 实现翻译功能 + + +## 用法说明 + +默认提供功能 `用户id转账号(用户名)` `部门id转名称` `字典type转label` `ossId转url` + +![输入图片说明](https://foruda.gitee.com/images/1675575977860232549/143b74f8_1766278.png "屏幕截图") + +用户名翻译(映射翻译) 根据另一个映射字段 翻译保存到此字段 + +![输入图片说明](https://foruda.gitee.com/images/1675576044011477847/13eb9f57_1766278.png "屏幕截图") + +ossUrl翻译(直接翻译) 直接根据此字段值翻译后替换此字段值 + +![输入图片说明](https://foruda.gitee.com/images/1675576265894720924/70792f66_1766278.png "屏幕截图") + +字典翻译(其他扩展条件翻译) 根据`other`条件 自行定义如何使用 例如字典翻译`other`条件就是字典的唯一值 + +![输入图片说明](https://foruda.gitee.com/images/1675576391012282823/f95c5d78_1766278.png "屏幕截图") + +## 自定义扩展 + +实现接口 `TranslationInterface` 标注注解 `@TranslationType` 可参考框架默认实现 + +![输入图片说明](https://foruda.gitee.com/images/1676735436673932715/c3caa8d7_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/websocket.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/websocket.md new file mode 100644 index 00000000..9e74e1e3 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/extend/websocket.md @@ -0,0 +1,37 @@ +# WebSocket功能 +- - - + +## 框架版本 >= 5.1.0 + +## 配置说明(默认关闭 推送建议使用SSE) + +![输入图片说明](https://foruda.gitee.com/images/1688356273985385949/5e4d1de8_1766278.png "屏幕截图") + +* enabled 是否开启此功能 +* path 应用路径 +* allowedOrigins 设置访问源地址 + +**重点: 如关闭ws功能需连同前端ws开关一同关闭 不然前端启动会报错** + +![输入图片说明](https://foruda.gitee.com/images/1700644877512019497/052d2f46_1766278.png "屏幕截图") + +## 使用方法 + +前端连接方式: `ws://后端ip:端口/resource/websocket?clientid=import.meta.env.VITE_APP_CLIENT_ID&Authorization=Bearer eyJ0eXAiO......` + +**由于js不支持请求头传输故而采用参数传输 如支持请求头传输建议使用请求头传输** + +传输方式: +```js +headers: { + Authorization: "Bearer " + getToken(), + clientid: import.meta.env.VITE_APP_CLIENT_ID +} +``` + +其中 `Authorization` 为请求token需要登录后获取 连接成功之后 与框架内其他获取登录用户方式一致 + +`WebSocketUtils.sendMessage` 推送单机消息(特殊需求使用)
+`WebSocketUtils.subscribeMessage` 订阅分布式消息(框架初始化已订阅)
+`WebSocketUtils.publishMessage` 发布分布式消息(推荐使用 所有集群内寻找到接收人)
+`WebSocketUtils.publishAll` 群发消息给所有连接人
\ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/tree.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/tree.md new file mode 100644 index 00000000..329cce21 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/framework/tree.md @@ -0,0 +1,61 @@ +# 目录结构 +- - - +v5.2.2 +~~~ +RuoYi-Vue-Plus +├─ ruoyi-admin // 管理模块 [8080,28080] +│ └─ RuoYiApplication // 启动类 +│ └─ RuoYiServletInitializer // 容器部署初始化类 +│ └─ resources // 资源文件 +│ └─ i18n/messages.properties // 国际化配置文件 +│ └─ application.yml // 框架总配置文件 +│ └─ application-dev.yml // 开发环境配置文件 +│ └─ application-prod.yml // 生产环境配置文件 +│ └─ banner.txt // 框架启动图标 +│ └─ logback-plus.xml // 日志配置文件 +│ └─ ip2region.xdb // IP区域地址库 +├─ ruoyi-extend // 扩展模块 +│ └─ ruoyi-monitor-admin // admin监控模块 [9090] +│ └─ ruoyi-snailjob-server // 任务调度中心模块 [8800,17888] +├─ ruoyi-common // 通用模块 +│ └─ ruoyi-common-bom // common依赖包管理 +│ └─ ruoyi-common-core // 核心模块 +│ └─ ruoyi-common-doc // 系统接口模块 +│ └─ ruoyi-common-encrypt // 数据加解密模块 +│ └─ ruoyi-common-excel // excel模块 +│ └─ ruoyi-common-idempotent // 幂等功能模块 +│ └─ ruoyi-common-job // 定时任务模块 +│ └─ ruoyi-common-json // 序列化模块 +│ └─ ruoyi-common-log // 日志模块 +│ └─ ruoyi-common-mail // 邮件模块 +│ └─ ruoyi-common-mybatis // 数据库模块 +│ └─ ruoyi-common-oss // oss服务模块 +│ └─ ruoyi-common-ratelimiter // 限流功能模块 +│ └─ ruoyi-common-redis // 缓存服务模块 +│ └─ ruoyi-common-satoken // satoken模块 +│ └─ ruoyi-common-security // 安全模块 +│ └─ ruoyi-common-sensitive // 脱敏模块 +│ └─ ruoyi-common-sms // 短信模块 +│ └─ ruoyi-common-social // 社交三方模块 +│ └─ ruoyi-common-sse // sse流推送模块 +│ └─ ruoyi-common-tenant // 租户模块 +│ └─ ruoyi-common-translation // 通用翻译模块 +│ └─ ruoyi-common-web // web模块 +│ └─ ruoyi-common-websocket // websocket服务集成模块 +├─ ruoyi-modules // 模块组 +│ └─ ruoyi-demo // 演示模块 +│ └─ ruoyi-generator // 代码生成模块 +│ └─ ruoyi-job // 任务调度服务 +│ └─ ruoyi-system // 业务模块 +│ └─ ruoyi-workflow // 工作流模块 +├─ plus-ui // 前端框架 [80] +├─ script // 系统脚本包 +│ └─ bin // 运行脚本包 +│ └─ docker // docker相关脚本 +│ └─ sql // sql脚本 +├─ .run // 执行脚本文件 +├─ .editorconfig // 编辑器编码格式配置 +├─ LICENSE // 开源协议 +├─ pom.xml // 公共依赖 +├─ README.md // 框架说明文件 +~~~ \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/home.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/home.md new file mode 100644 index 00000000..ff4de9ef --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/home.md @@ -0,0 +1,127 @@ + +
+ +- - - +# 平台简介 +
+ +[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus) +[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE) +[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) +
+[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]() +[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() +[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]() + +> RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架) + +> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可
+活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源 + +# 本框架与RuoYi的功能差异 + +| 功能 | 本框架 | RuoYi | +|-------------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| 前端项目 | 采用 Vue3 + TS + ElementPlus 重写 | 基于Vue2/Vue3 + JS | +| 后端项目结构 | 采用插件化 + 扩展包形式 结构解耦 易于扩展 | 模块相互注入耦合严重难以扩展 | +| 后端代码风格 | 严格遵守Alibaba规范与项目统一配置的代码格式化 | 代码书写与常规结构不同阅读障碍大 | +| Web容器 | 采用 Undertow 基于 XNIO 的高性能容器 | 采用 Tomcat | +| 权限认证 | 采用 Sa-Token、Jwt 静态使用功能齐全 低耦合 高扩展 | Spring Security 配置繁琐扩展性极差 | +| 权限注解 | 采用 Sa-Token 支持注解 登录校验、角色校验、权限校验、二级认证校验、HttpBasic校验、忽略校验
角色与权限校验支持多种条件 如 `AND` `OR` 或 `权限 OR 角色` 等复杂表达式 | 只支持是否存在匹配 | +| 三方鉴权 | 采用 JustAuth 第三方登录组件 支持微信、钉钉等数十种三方认证 | 无 | +| 关系数据库支持 | 原生支持 MySQL、Oracle、PostgreSQL、SQLServer
可同时使用异构切换(支持其他 mybatis-plus 支持的所有数据库 只需要增加jdbc依赖即可使用 达梦金仓等均有成功案例) | 支持 Mysql、Oracle 不支持同时使用、不支持异构切换 | +| 缓存数据库 | 支持 Redis 5-7 支持大部分新功能特性 如 分布式限流、分布式队列 | Redis 简单 get set 支持 | +| Redis客户端 | 采用 Redisson Redis官方推荐 基于Netty的客户端工具
支持Redis 90%以上的命令 底层优化规避很多不正确的用法 例如: keys被转换为scan
支持单机、哨兵、单主集群、多主集群等模式 | Lettuce + RedisTemplate 支持模式少 工具使用繁琐
连接池采用 common-pool Bug多经常性出问题 | +| 缓存注解 | 采用 Spring-Cache 注解 对其扩展了实现支持了更多功能
例如 过期时间 最大空闲时间 组最大长度等 只需一个注解即可完成数据自动缓存 | 需手动编写Redis代码逻辑 | +| ORM框架 | 采用 Mybatis-Plus 基于对象几乎不用写SQL全java操作 功能强大插件众多
例如多租户插件 分页插件 乐观锁插件等等 | 采用 Mybatis 基于XML需要手写SQL | +| SQL监控 | 采用 p6spy 可输出完整SQL与执行时间监控 | log输出 需手动拼接sql与参数无法快速查看调试问题 | +| 数据分页 | 采用 Mybatis-Plus 分页插件
框架对其进行了扩展 对象化分页对象 支持多种方式传参 支持前端多排序 复杂排序 | 采用 PageHelper 仅支持单查询分页 参数只能从param传 只能单排序 功能扩展性差 体验不好 | +| 数据权限 | 采用 Mybatis-Plus 插件 自行分析拼接SQL 无感式过滤
只需为Mapper设置好注解条件 支持多种自定义 不限于部门角色 | 采用 注解+aop 实现 基于部门角色 生成的sql兼容性差 不支持其他业务扩展
生成sql后需手动拼接到具体业务sql上 对于多个Mapper查询不起作用 | +| 数据脱敏 | 采用 注解 + jackson 序列化期间脱敏 支持不同模块不同的脱敏条件
支持多种策略 如身份证、手机号、地址、邮箱、银行卡等 可自行扩展 | 无 | +| 数据加解密 | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密
支持多种策略 如BASE64、AES、RSA、SM2、SM4等 | 无 | +| 接口传输加密 | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性 | 无 | +| 数据翻译 | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译
支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现 | 无 | +| 多数据源框架 | 采用 dynamic-datasource 支持市面大部分数据库
通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源
支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 | +| 多数据源事务 | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚 | 不支持 | +| 数据库连接池 | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下 | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般 | +| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 | +| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 | +| SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 | +| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 | +| 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 | +| 分布式锁 | 采用 Lock4j 底层基于 Redisson | 无 | +| 分布式任务调度 | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 | +| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储
支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 | +| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 | +| 短信 | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用 | 不支持 | +| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 | +| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释
只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 | +| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 | +| Excel框架 | 采用 Alibaba EasyExcel 基于插件化
框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 | +| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 | +| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 | +| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制
实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 | +| 链路追踪 | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗
用了它即可实时查看请求经过的每一处每一个节点 | 无 | +| 代码生成器 | 只需设计好表结构 一键生成所有crud代码与页面
降低80%的开发量 把精力都投入到业务设计上
框架为其适配MP、SpringDoc规范化代码 同时支持动态多数据源代码生成 | 代码生成原生结构 只支持单数据源生成 | +| 部署方式 | 支持 Docker 编排 一键搭建所有环境 让开发人员从此不再为搭建环境而烦恼 | 原生jar部署 其他环境需手动下载安装 自行搭建 | +| 项目路径修改 | 提供详细的修改方案文档 并为其做了一些改动 非常简单即可修改成自己想要的 | 需要做很多改造 文档说明有限 | +| 国际化 | 基于请求头动态返回不同语种的文本内容 开发难度低 有对应的工具类 支持大部分注解内容国际化 | 只提供基础功能 其他需自行编写扩展 | +| 代码单例测试 | 提供单例测试 使用方式编写方法与maven多环境单测插件 | 只提供基础功能 其他需自行编写扩展 | +| Demo案例 | 提供框架功能的实际使用案例 单独一个模块提供了很多很全 | 无 | + + +## 本框架与RuoYi的业务差异 + +| 业务 | 功能说明 | 本框架 | RuoYi | +|--------|----------------------------------------------------------------------|-----|------------------| +| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 | +| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 | +| 客户端管理 | 系统内对接的所有客户端管理 如: pc端、小程序端等
支持动态授权登录方式 如: 短信登录、密码登录等 支持动态控制token时效 | 支持 | 无 | +| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 | +| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 | +| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 | +| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 | +| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 | +| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 | +| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 | +| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 | +| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 | +| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 | +| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 | +| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 | +| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 | +| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 | +| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 | +| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 | +| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 | +| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 | +| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 | +| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 | + + +## 演示图例 + +| | | +|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") | + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/4.Xinit.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/4.Xinit.md new file mode 100644 index 00000000..73a6dbc4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/4.Xinit.md @@ -0,0 +1,67 @@ +# 4.X项目初始化 +- - - +### 项目分支说明 +`4.X` 主分支 4.X版本 稳定发布分支
+`fast` 单体分支 功能与主分支相同 结构为单模块
+`dev` 开发分支 代码随时更新 不推荐使用 经测试后会发布到主分支
+`future/*` 新功能预览分支
+ +### 项目必备环境 +> 推荐使用 `docker` 安装 项目内置 `docker` 编排文件 +* oracle jdk 8 11 (暂时不支持 17 不支持大于 jdk8_202 因为202是最后一个免费版本) +* mysql 5.7 8.0 (5.6未适配可能会有问题) +* oracle 11g 12c +* postgres 13 14 +* sqlserver 2017 2019 +* redis 5.X 6.X 由于框架大量使用了redis特性 版本必须 >= 5.X ([win redis 下载地址](https://github.com/tporadowski/redis)) +* minio 本地文件存储 或 阿里云 腾讯云 七牛云等一切支持S3协议的云存储 +* maven 3.6.3 3.8.X +* nodejs >= 12 < 18 +* npm 6.X 8.X (7.X确认有问题) + +### 3.2.0及以上 只需勾选对应环境即可 +![输入图片说明](https://foruda.gitee.com/images/1678976284045210056/a2f28d33_1766278.png "屏幕截图") + +### 默认 `JDK1.8` 如有变动 需更改以下配置 + +![输入图片说明](https://foruda.gitee.com/images/1681017282888708602/09da902f_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1681017287160367631/3a033268_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1681017292933832275/0bdf875e_1766278.png "屏幕截图") + +### sql导入 + +请按照以下顺序依次导入 + +![输入图片说明](https://foruda.gitee.com/images/1681017239000759855/ad43a5b2_1766278.png "屏幕截图") + +默认为 `mysql` 其他数据库需导入对应的sql文件 + +![输入图片说明](https://foruda.gitee.com/images/1681017245687923510/1b444bc4_1766278.png "屏幕截图") + +**多数据库仅支持主应用 扩展应用需自行适配(例如: xxl-job仅支持mysql)** + +### 服务启动顺序说明 + +1. 必须启动基础建设: mysql redis admin
+2. 可选启动基础建设: minio(影响文件上传) monitor(影响监控) xxljob(影响定时任务)
+ +![输入图片说明](https://foruda.gitee.com/images/1678976302776168895/7333341c_1766278.png "屏幕截图") + +* `MonitorAdminApplication` 为 Admin监控服务(非必要 可参考对应文档关闭) +* `XxlJobAdminApplication` 为 任务调度中心服务(非必要 可参考对应文档关闭) +* `RuoYiApplication` 为 主应用服务 +> 需优先启动 `MonitorAdminApplication` 与 `XxlJobAdminApplication` 具体配置方式参考对应文档 +> 最后启动 主服务 `RuoYiApplication` + +### 主服务配置方式 + +在勾选对应环境的配置文件内 填写 mysql 与 redis 配置信息 + +![输入图片说明](https://foruda.gitee.com/images/1678941357316005626/70559736_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1681017185596156350/d4607b5b_1766278.png "屏幕截图") + +其他数据库配置 按照系统自带的配置更改即可 + +![输入图片说明](https://foruda.gitee.com/images/1678941444707120259/b274592a_1766278.png "屏幕截图") + + diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/5.Xnew.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/5.Xnew.md new file mode 100644 index 00000000..a966adad --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/5.Xnew.md @@ -0,0 +1,5 @@ +### 视频讲解 + +[RuoYi-Vue-Plus 5.0.0 新功能与变更介绍](https://www.bilibili.com/video/BV1Us4y1m7ky/) + +[RuoYi-Vue-Plus 5.1.0 新功能与变更介绍](https://www.bilibili.com/video/BV1fj411y71X/) diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/admin_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/admin_init.md new file mode 100644 index 00000000..0a317a83 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/admin_init.md @@ -0,0 +1,32 @@ +# 搭建Admin监控 +- - - +### 配置监控客户端 + +> 修改主服务配置文件 + +![输入图片说明](https://foruda.gitee.com/images/1678941504260707700/68ab99e5_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `url` 为监控中心地址 +* `username 与 password` 为监控中心的账号密码 + +### 启用监控中心 +在 `扩展项目 -> 监控模块` 启动 + +![输入图片说明](https://foruda.gitee.com/images/1678976327174539378/df97e36e_1766278.png "屏幕截图") + +在监控模块对应的 `yml` 配置文件 可设置登录的账号密码与访问路径 + +![输入图片说明](https://foruda.gitee.com/images/1678941572583282843/28117457_1766278.png "屏幕截图") + +### 前端修改admin监控访问路径 +`dev`环境 默认使用 `.env.development` 配置文件内地址 + +![输入图片说明](https://foruda.gitee.com/images/1678941607472644388/460e8eea_1766278.png "屏幕截图") + +`prod`环境 使用 `.env.production` 本机路由 + +![输入图片说明](https://foruda.gitee.com/images/1678941644784144830/6293ab1c_1766278.png "屏幕截图") +故而 `prod` 环境只需更改 `nginx` 反向代理路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1678981483900657668/31fd1aad_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/deploy.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/deploy.md new file mode 100644 index 00000000..37e184c4 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/deploy.md @@ -0,0 +1,126 @@ +# 应用部署 +- - - +## 版本 >= 4.3.0 + +### 请优先阅读 [idea环境配置](/ruoyi-vue-plus/quickstart/idea_environment.md) + +## 手动部署 + +在服务器安装 `mysql` `redis` `nginx` `minio` + +将项目内 `script/docker/nginx/nginx.conf` 配置文件 复制到 `nginx` 配置内
+将项目内 `script/docker/redis/redis.conf` 配置文件 复制到 `redis` 配置内 + +并修改相关参数如 `前端页面存放位置` `后端Ip地址` 等使其生效 + +jar包部署后端服务 打包命令如下 + +3.2.0及以上 +```mvn +mvn clean package -D maven.test.skip=true -P prod +``` +服务器需创建临时文件存储目录与配置文件对应(无此目录上传文件会报错) + +![输入图片说明](https://foruda.gitee.com/images/1659951373949149804/屏幕截图.png "屏幕截图.png") + +前端参考下方前端部署章节 + +## 部署视频 + +[RuoYi-Vue-Plus 5.0 生产环境搭建部署](https://www.bilibili.com/video/BV1mL411e7ha/) + +## docker 后端部署 + +### 请优先阅读 [idea环境配置](/ruoyi-vue-plus/quickstart/idea_environment.md) + +**重点: 一知半解的必看** +> [docker安装](https://lionli.blog.csdn.net/article/details/83153029)
+> [docker-compose安装](https://lionli.blog.csdn.net/article/details/111220320)
+> [docker网络模式讲解](https://lionli.blog.csdn.net/article/details/109603785)
+> [docker 开启端口 2375 供外部程序访问](https://lionli.blog.csdn.net/article/details/92627962) + +### 将配置使用FTP上传到根目录 +idea拖拽文件到远程目录即可上传 + +![输入图片说明](https://foruda.gitee.com/images/1662109450908169859/eaac9299_1766278.png "屏幕截图") + +### 给docker分配文件夹权限 +**重点注意: 一定要确保目录 `/docker` 及其所有子目录 具有写权限 如果后续出现权限异常问题 重新执行一遍分配权限** + +![输入图片说明](https://foruda.gitee.com/images/1662109847279259882/3a2202c1_1766278.png "屏幕截图") +```shell +chmod -R 777 /docker +``` +### 构建应用镜像 + +**1.需要先使用maven打包成jar包** + +![输入图片说明](https://foruda.gitee.com/images/1662110477410977621/c6931c42_1766278.png "屏幕截图") + +**2.执行构建** +> 项目初始化后会自动生成构建镜像的运行配置
+> 配置好docker连接之后 运行如下即可构建对应的应用镜像 + +**重点注意: idea2024及以上版本要求必须在本地安装docker才可以执行如下操作** + +![输入图片说明](https://foruda.gitee.com/images/1662110192257483752/0f754b47_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1662120004773449909/9fdef59c_1766278.png "屏幕截图") + +**3.结构讲解** +右键编辑 即可看到内部配置 + +![输入图片说明](https://foruda.gitee.com/images/1662458355500139498/eaa26036_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1662458446794722159/32c086a7_1766278.png "屏幕截图") + + +### 创建基础服务 + +```shell +docker-compose up -d mysql nginx-web redis minio +``` + +### 创建业务服务(需要先构建服务镜像) + +4.X +```shell +docker-compose up -d ruoyi-monitor-admin ruoyi-xxl-job-admin ruoyi-server1 ruoyi-server2 +``` + +5.X +```shell +docker-compose up -d ruoyi-monitor-admin ruoyi-snailjob-server ruoyi-server1 ruoyi-server2 +``` + +### docker其他操作(idea的docker插件 推荐使用) +![输入图片说明](https://foruda.gitee.com/images/1662458271941863770/cd180a04_1766278.png "屏幕截图") + +## 前端部署 + +执行打包命令 +```shell +# 打包正式环境 +npm run build:prod +``` +打包后生成打包文件在 `ruoyi-ui/dist` 目录 +将 `dist` 目录下文件(不包含 `dist` 目录) 上传到部署服务器 `docker/nginx/html` 目录下(手动部署放入自己配置的路径即可) + +![输入图片说明](https://foruda.gitee.com/images/1662110914769648699/07f344c4_1766278.png "屏幕截图") + +重启 `nginx` 服务即可 + + +### 如需更改后端代理路径或者后端ip地址的话往下看 + +更改`nginx.conf`配置文件代理路径(注意: /开头/结尾) + +![输入图片说明](https://foruda.gitee.com/images/1660185698211067202/屏幕截图.png "屏幕截图.png") + +更改前端`.env.环境` 文件内的 `VITE_APP_BASE_API` + +![输入图片说明](https://foruda.gitee.com/images/1724318035232137124/5d035a09_1766278.png "屏幕截图") + +更改`nginx.conf`配置文件后端ip地址 + +![输入图片说明](https://foruda.gitee.com/images/1660185711265558730/屏幕截图.png "屏幕截图.png") diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/extend_project.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/extend_project.md new file mode 100644 index 00000000..0c6f33ac --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/extend_project.md @@ -0,0 +1,49 @@ +# 基于 RuoYi-Vue-Plus 的扩展项目列表 +- - - +### 精品PR 欢迎投稿 +| 功能介绍 | PR地址 | +|-------------------------------------|------------------------------------------------------| +| 拖拽图片调整显示顺序 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/173 | +| 增加Jasypt加密库对配置文件加密 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/177 | +| 使用富文本wangeditor5替换Quill | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/213 | +| 集成screw数据库文档功能模块 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/42 | +| Excel导入模板增加批注支持 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/222 | +| 压缩包处理工具 支持本地文件/目录+oss文件/网络文件混合 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/44 | +| 添加websocket模块 支持satoken鉴权 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/65 | +| 数据库字段加解密(支持 base64 aes rsa sm2 sm4) | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/274 | +| 增加liquibase迁移数据库 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/299 | +| 增加OSS模块支持本地环境 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/353 | +| 扩展模块独立集成flyway | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/439 | +| 扩展模块独立集成go-view大屏看板 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/445 | +| 基于AmazonS3协议的分片上传 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/130 | +| 扩展forest http客户端 声明式http请求 二次封装像工具类 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/547 | +| 增加短链接生成工具 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/515 | +| 新增oss预签名上传工具组合使用异步客户端分片 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/522 | +| 新增规则引擎LiteFlow,SQL持久化接入,支持可视化页面 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/552 | +| 一键部署到私有Nexus仓库 | https://gitee.com/dromara/RuoYi-Cloud-Plus/pulls/181 | +| 服务状态监控发送邮件钉钉等 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/568 | +| 登录验证支持2FA验证 | https://gitee.com/dromara/RuoYi-Vue-Plus/pulls/578 | + +### 项目介绍+项目地址 欢迎投稿 + + +| 项目介绍 | 项目地址 | +|--------------------------------|---------------------------------------------------------------------------| +| 微服务扩展 | https://gitee.com/dromara/RuoYi-Cloud-Plus | +| Plus学习笔记 | https://zhonglingyuxiu1028.github.io/zlyx-space/#/ruoyi-vue-plus/home | +| 基于uniapp+TmUI从0开发 支持H5/小程序/安卓 | https://gitee.com/dapppp/ruoyi-plus-miniapp | +| 基于RuoYi-App框架二次修改使用Uniapp+Vue3 | https://gitee.com/wangying110166/ruo-yi-uni-app-plus | +| 基于RuoYi-App框架对接Plus后端 | https://gitee.com/FnTop/RuoYi-App-Plus | +| 基于vben(ant-design-vue)前端项目 | https://gitee.com/dapppp/ruoyi-plus-vben | +| 基于vue-next-admin的vue3+ts前端 | https://gitee.com/thiszhc/RuoYi-Vue3-UI | +| 集成GoView版本 | https://gitee.com/kdwqjwgqxx/RuoYi-Vue-Plus-GoView | +| mybatis-flex版本 | https://gitee.com/dataprince/ruoyi-flex | +| blog博客系统 | https://gitee.com/kalashok-pan/zhi-blog-plus | +| tdengine时序数据库扩展 | https://gitee.com/zhangbg/ruoyi-plus-tdengine | +| 重构项目结构(参考springboot源码) | https://gitee.com/denghuafeng/ruoyi-boot-plus | +| Activiti扩展 | https://gitee.com/sgs98/RuoYi-Vue-Plus-Activiti | +| flowable扩展 | https://gitee.com/sgs98/RuoYi-Vue-Plus-Flowable | +| flowable扩展 | https://gitee.com/KonBAI-Q/ruoyi-flowable-plus | +| mybatis-flex版本 | https://gitee.com/yhan219/ruoyi-vue-flex | +| weblog博客系统 | https://gitee.com/fu-zhanshuai/fzshuai-weblog | +| Annlcc博客 | https://gitee.com/ahcode/ann-blog-plus | \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/idea_environment.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/idea_environment.md new file mode 100644 index 00000000..5995e0ff --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/idea_environment.md @@ -0,0 +1,50 @@ +# idea环境配置 +- - - +## 配置项目编码 +![输入图片说明](https://foruda.gitee.com/images/1662107706295343419/e27065a9_1766278.png "屏幕截图") + +## 配置运行看板 +![输入图片说明](https://foruda.gitee.com/images/1662108673306567278/8af97b47_1766278.png "屏幕截图") +### 配置spring与docker看板 +![输入图片说明](https://foruda.gitee.com/images/1662111392476935892/6b6760fb_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1662108865191892425/3c045999_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1662108877322329668/ddb6d93d_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1662108894122798039/6a53a38c_1766278.png "屏幕截图") + +## 配置服务器SSH连接 +进入 `Settings -> Tools -> SSH Configurations` 点击加号创建SSH连接配置
+填写 服务器IP 用户名 密码 端口号 点击 Test Connection 测试连接 + +![输入图片说明](https://foruda.gitee.com/images/1662107776533098115/bd78467b_1766278.png "屏幕截图") + +使用Terminal 工具 点击箭头找到上方创建的SSH连接配置
+选择即可进入SSH连接界面 在这里可以对服务器进行命令操作 + +![输入图片说明](https://foruda.gitee.com/images/1662108010120640495/c70f9f9a_1766278.png "屏幕截图") + +## 配置服务器FTP连接 +进入 `Settings -> Build-> Deployment` 点击加号 选择SFTP 创建 FTP 连接配置
+选择之前创建好的SSH配置 点击 Test Connection 测试连接 + +![输入图片说明](https://foruda.gitee.com/images/1662107899553257979/e2eeb7fd_1766278.png "屏幕截图") + +在IDEA上方工具栏 找到 `Tools -> Deployment -> Browse Remote Host` 打开远程界面
+点击箭头找到我们上方配置的SFTP连接配置 即可连接到服务器的文件目录 + +![输入图片说明](https://foruda.gitee.com/images/1662107974682787233/b8a601fd_1766278.png "屏幕截图") + +## 配置Docker连接 +### 可操作远程docker与构建上传docker镜像(代替原来maven docker插件) +tcp连接需要开放服务器2375端口
+ssh需要使用上方的SSH连接配置
+建议使用SSH连接 + +![输入图片说明](https://foruda.gitee.com/images/1662108188005932060/75872bf8_1766278.png "屏幕截图") + +配置好之后 在运行窗口会多出一个Docker图标 双击即可连接远程docker
+可以查看容器实时日志 启动 重启 停止 等操作 + +![输入图片说明](https://foruda.gitee.com/images/1662108250902891875/b82d022b_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/init.md new file mode 100644 index 00000000..dba09058 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/init.md @@ -0,0 +1,73 @@ +# 5.X项目初始化 +- - - +### 项目分支说明 + +`5.X` 主分支 5.X版本 稳定发布分支
+`dev` 开发分支 代码随时更新 不推荐使用 经测试后会发布到主分支
+`future/*` 新功能预览分支
+ +### 项目必备环境 +> 推荐使用 `docker` 安装 项目内置 `docker` 编排文件 + +**注意: 禁止使用 `oraclejdk`(由于spring的bug导致打包运行会报错)** + +**Spring官方推荐使用JDK https://bell-sw.com/pages/downloads/** + +![输入图片说明](https://foruda.gitee.com/images/1720080025744223375/0213a652_1766278.png "屏幕截图") + +* openjdk-17/21 或 graalvm-community-jdk-17/21 [下载地址](https://github.com/graalvm/graalvm-ce-builds/releases) 版本 +* mysql 5.7 8.0 (其他版本未测试 如其他版本没问题 可以告知咱们) +* oracle >= 12c (其他版本未测试 如其他版本没问题 可以告知咱们) +* postgres 13 14 (其他版本未测试 如其他版本没问题 可以告知咱们) +* sqlserver 2017 2019 (其他版本未测试 如其他版本没问题 可以告知咱们) +* redis 5.X 6.X 7.X 由于框架大量使用了redis特性 版本必须 >= 5.X ([win redis 下载地址](https://github.com/zkteco-home/redis-windows)) +* minio 本地文件存储 或 阿里云 腾讯云 七牛云等一切支持S3协议的云存储 +* maven >= 3.8.X +* nodejs >= 18.18 (其他版本未测试 如其他版本没问题 可以告知咱们) +* npm >= 8.X (7.X确认有问题) +* idea 2022 2024 (一定不要使用2023后果自负 bug太多影响项目开发) + +### 搭建视频 + +[RuoYi-Vue-Plus 5.0 搭建与运行](https://www.bilibili.com/video/BV1Fg4y137JK/) + +### 勾选maven对应环境 +![输入图片说明](https://foruda.gitee.com/images/1678976284045210056/a2f28d33_1766278.png "屏幕截图") + +### 默认 `JDK17` 如有变动 需更改以下配置 + +![输入图片说明](https://foruda.gitee.com/images/1678941027820943505/c688e01e_1766278.png "屏幕截图") +![输入图片说明](https://foruda.gitee.com/images/1678941120518807034/4d56fcc9_1766278.png "屏幕截图") + +### sql导入 + +请按照以下顺序依次导入 默认为 `mysql` 其他数据库需导入对应的sql文件
+如需使用其他数据库 看这里 => [多数据库数据源](../framework/extend/dynamic_datasource.md)
+ +![输入图片说明](https://foruda.gitee.com/images/1725853192789853346/a0d3f0b7_1766278.png "屏幕截图") + +### 服务启动顺序说明 + +1. 必须启动基础建设: mysql redis admin
+2. 可选启动基础建设: minio(影响文件上传) monitor(影响监控) snailjob(影响定时任务)
+ +![输入图片说明](https://foruda.gitee.com/images/1716175484919688429/8b9a79b7_1766278.png "屏幕截图") + +* `MonitorAdminApplication` 为 Admin监控服务(非必要 可参考对应文档关闭 [搭建Admin监控](/ruoyi-vue-plus/quickstart/admin_init.md)) +* `SnailJobServerApplication` 为 任务调度中心服务(非必要 可参考对应文档关闭 [搭建调度中心](/ruoyi-vue-plus/quickstart/snail_job_init.md)) +* `DromaraApplication` 为 主应用服务 +> 需优先启动 `MonitorAdminApplication` 与 `SnailJobServerApplication` 具体配置方式参考对应文档
+> 最后启动 主服务 `DromaraApplication`
+> 工作流相关初始化使用 [工作流初始化](/ruoyi-vue-plus/quickstart/worker_init.md) + +### 主服务配置方式 + +在勾选对应环境的配置文件内 填写 mysql 与 redis 配置信息 + +![输入图片说明](https://foruda.gitee.com/images/1678941357316005626/70559736_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1678941405169571070/0d06a955_1766278.png "屏幕截图") + +其他数据库配置 按照系统自带的配置更改即可 + +![输入图片说明](https://foruda.gitee.com/images/1678941444707120259/b274592a_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/power_job_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/power_job_init.md new file mode 100644 index 00000000..863971b1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/power_job_init.md @@ -0,0 +1,48 @@ +# 搭建PowerJob任务调度中心(5.X分支已废弃) +- - - +### 废弃原因 + +接到大量投诉 使用困难 用法诡异 各种问题等 + +### 配置调度中心客户端 +> 修改主服务配置文件 +> + +![输入图片说明](https://foruda.gitee.com/images/1687656939847353725/951c1af7_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1687335574708412835/41d6c9d7_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `server-address` 为调度中心地址 +* `app-name` 为执行器组账户名(需在调度中心注册方可登录查看) + +### 启用调度中心 +**需执行 powerjob.sql 默认账号密码 `ruoyi-worker` `123456` 账号在数据库里 可以在页面修改密码** +
+ +![输入图片说明](https://foruda.gitee.com/images/1688634469876143273/c89455c0_1766278.png "屏幕截图") + +> 在 `扩展项目 -> powerjob-server模块` 启动 +> +![输入图片说明](https://foruda.gitee.com/images/1687335752250147336/17abe410_1766278.png "屏幕截图") + +> 需修改配置文件数据库连接地址(**注意: 此处为ruoyi-powerjob-server服务的配置文件**) +> +![输入图片说明](https://foruda.gitee.com/images/1687335802095066722/569d92be_1766278.png "屏幕截图") + +> 也可配置邮件发送 钉钉推送 和 mongodb存储 +> +![输入图片说明](https://foruda.gitee.com/images/1687335842722317559/f875c07a_1766278.png "屏幕截图") + +### 前端修改任务调度中心访问路径 +`dev`环境 默认使用 `.env.development` 配置文件内地址 + +![输入图片说明](https://foruda.gitee.com/images/1687335909698376722/7efa7539_1766278.png "屏幕截图") + +`prod`环境 使用 `.env.production` 本机路由 + +![输入图片说明](https://foruda.gitee.com/images/1687335937599399056/dd769ef5_1766278.png "屏幕截图") + +故而 `prod` 环境只需更改 `nginx` 反向代理路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1687335979933648639/6a43b749_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/snail_job_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/snail_job_init.md new file mode 100644 index 00000000..4c6d353b --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/snail_job_init.md @@ -0,0 +1,52 @@ +# 搭建SnailJob任务调度中心(5.2.0新功能) +- - - + +### 视频介绍 + +[Snail job任务调度中心:轻松掌握任务管理、重试机制和任务编排](https://www.bilibili.com/video/BV19i421m7GL/) + +### 配置调度中心客户端 +> 修改主服务配置文件 +> + +![输入图片说明](https://foruda.gitee.com/images/1687656939847353725/951c1af7_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1716174758437043952/de28db71_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `server.address` 为调度中心地址 +* `server.port` 为调度中心通信端口 +* `token` 为组通信校验token(可在调度中心组配置更换) +* `group-name` 为执行器组 +* `namespace` 作用域(不同作用域相互隔离请勿填错) + +### 启用调度中心 +**需执行 snail_job.sql 默认账号密码 `admin` `admin` 账号在数据库里 可以在页面修改密码** +
+ +![输入图片说明](https://foruda.gitee.com/images/1714355875395308961/adc21668_1766278.png "屏幕截图") + +> 在 `ruoyi-extend -> ruoyi-snailjob-server` 模块启动 +> +![输入图片说明](https://foruda.gitee.com/images/1716174842485474283/78cec86d_1766278.png "屏幕截图") + +> 需修改配置文件数据库连接地址(**注意: 此处为ruoyi-snailjob-server服务的配置文件 支持多种不同数据库**) +> +![输入图片说明](https://foruda.gitee.com/images/1714356048711590477/13289085_1766278.png "屏幕截图") + +### 快速入门 + +[Snailjob快速入门 基本使用介绍](https://juejin.cn/post/7412955032092442675) + +### 前端修改任务调度中心访问路径 +`dev`环境 默认使用 `.env.development` 配置文件内地址 + +![输入图片说明](https://foruda.gitee.com/images/1716174933143893408/58d47bbc_1766278.png "屏幕截图") + +`prod`环境 使用 `.env.production` 本机路由 + +![输入图片说明](https://foruda.gitee.com/images/1716174973454805690/0d6f20fb_1766278.png "屏幕截图") + +故而 `prod` 环境只需更改 `nginx` 反向代理路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1716174998979181179/2f9e4e4a_1766278.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/worker_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/worker_init.md new file mode 100644 index 00000000..da09117a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/worker_init.md @@ -0,0 +1,43 @@ +# 工作流初始化 +- - - + +### 工作流使用及配置方式 + +1.找到项目中script下bpmn文件夹 + +![输入图片说明](https://foruda.gitee.com/images/1714211764058540441/5c8b97af_5363069.png "屏幕截图") + +2.启动项目找到流程定义通过**部署流程文件**将bpmn文件夹下**模型.zip**上传 + +![输入图片说明](https://foruda.gitee.com/images/1714211950485333575/1e2b3ff4_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212113004821592/96586e69_5363069.png "屏幕截图") + +3.导入**模型.zip**后将会出现以下列表,默认使用**leave1**,test_leave为请假申请表名称 + +![输入图片说明](https://foruda.gitee.com/images/1714212222766335759/1227bbd6_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212493602552742/9e0258b1_5363069.png "屏幕截图") + +**此处表名由来与表单源码内编写的表名保持一致方可互相绑定** + +![输入图片说明](https://foruda.gitee.com/images/1716447357161482917/2c9b1639_1766278.png "屏幕截图") + + +4.新增一条请假申请,提交后将会得到如下信息 + +![输入图片说明](https://foruda.gitee.com/images/1714212617432902105/3609f6ef_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212630860787365/2922d38e_5363069.png "屏幕截图") + +5.关于如何切换一个新的流程使用,当前默认使用得KEY为leave1 ,我们切换到leave2使用,我们只需点击绑定业务将表名绑定,重新发起一个新的请假申请就可以得到一个新的流程信息 + +![输入图片说明](https://foruda.gitee.com/images/1714212876442323110/4554ea95_5363069.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714213037864274694/613149f5_5363069.png "屏幕截图") + +**此处表名由来与表单源码内编写的表名保持一致方可互相绑定** + +![输入图片说明](https://foruda.gitee.com/images/1716447357161482917/2c9b1639_1766278.png "屏幕截图") + +![输入图片说明](https://foruda.gitee.com/images/1714212963457174382/add768db_5363069.png "屏幕截图") \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/xxl_job_init.md b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/xxl_job_init.md new file mode 100644 index 00000000..f2751e9f --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ruoyi-vue-plus/quickstart/xxl_job_init.md @@ -0,0 +1,43 @@ +# 搭建Xxl-Job任务调度中心(5.X分支已废弃) +- - - +### 废弃原因 + +长时间不维护 社区冰点 不支持jdk17 不支持boot3 不支持其他数据库等 + +### 配置调度中心客户端 +> 修改主服务配置文件 +> +![输入图片说明](https://foruda.gitee.com/images/1678941760168414366/b81e023b_1766278.png "屏幕截图") + +* `enabled` 可启用或关闭客户端注册 +* `admin-addresses` 为调度中心地址 +* `access-token` 为调度中心交互鉴权token +* `executor` 为执行器配置 一个客户端为一个执行器 可配置执行器集群 使用分片任务处理 + +### 启用调度中心 +**默认账号密码 `admin` `123456` 账号在数据库里 可以在页面修改密码** + +> 在 `扩展项目 -> xxl-job-admin模块` 启动 +> +![输入图片说明](https://foruda.gitee.com/images/1678976353500205883/058fef13_1766278.png "屏幕截图") + +> 需修改配置文件数据库连接地址(**注意: 此处为xxl-job-admin服务的配置文件**) +> +![输入图片说明](https://foruda.gitee.com/images/1678941813423551656/04c32a5b_1766278.png "屏幕截图") + +> 也可配置邮件发送 +> +![输入图片说明](https://foruda.gitee.com/images/1678941825447455298/1baa5e43_1766278.png "屏幕截图") + +### 前端修改任务调度中心访问路径 +`dev`环境 默认使用 `.env.development` 配置文件内地址 + +![输入图片说明](https://foruda.gitee.com/images/1678976378255854583/8cdbf4e3_1766278.png "屏幕截图") + +`prod`环境 使用 `.env.production` 本机路由 + +![输入图片说明](https://foruda.gitee.com/images/1678976382819019066/96288331_1766278.png "屏幕截图") + +故而 `prod` 环境只需更改 `nginx` 反向代理路径即可 + +![输入图片说明](https://foruda.gitee.com/images/1678976386764602366/55894f85_1766278.png "屏幕截图") diff --git a/ruoyi-admin/src/main/resources/static/static/css/vue.css b/ruoyi-admin/src/main/resources/static/static/css/vue.css new file mode 100644 index 00000000..847f385a --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/css/vue.css @@ -0,0 +1 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}img.emoji{height:1.2em}img.emoji,span.emoji{vertical-align:middle}span.emoji{font-family:Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:1.2em}.progress{background-color:#42b983;background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:999999}.search .search-keyword,.search a:hover{color:#42b983;color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}li input[type=checkbox]{margin:0 .2em .25em 0;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:10}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:#42b983;color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative;cursor:pointer}.app-nav li ul{background-color:#fff;border:1px solid;border-color:#ddd #ddd #ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}.github-corner svg{color:#fff;fill:#42b983;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:20}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0 0 0 15px;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53.3%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53.3%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:30;cursor:pointer}.sidebar-toggle:hover .sidebar-toggle-button{opacity:.4}.sidebar-toggle span{background-color:#42b983;background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:80%;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee;width:1px;min-width:100%}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}.markdown-section ul.task-list>li{list-style-type:none}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;min-height:100vh;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{position:relative;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;min-height:100vh;width:100%;display:none}section.cover.show{display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;bottom:0;width:100%}section.cover .cover-main{flex:1;margin:0 16px;text-align:center;position:relative}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border-radius:2rem;border:1px solid #42b983;border-color:var(--theme-color,#42b983);box-sizing:border-box;color:#42b983;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:#42b983;background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid #42b983;border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:#42b983;color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:#42b983;color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid #42b983;border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code,.markdown-section output:after,.markdown-section pre{font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section code,.markdown-section pre{background-color:#f8f8f8}.markdown-section output,.markdown-section pre{margin:1.2em 0;position:relative}.markdown-section output,.markdown-section pre>code{border-radius:2px;display:block}.markdown-section output:after,.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.markdown-section code{border-radius:2px;color:#e96900;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) code{font-size:.8rem}.markdown-section pre{padding:0 1.4rem;line-height:1.5rem;overflow:auto;word-wrap:normal}.markdown-section pre>code{color:#525252;font-size:.8rem;padding:2.2em 5px;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;white-space:inherit}.markdown-section output{padding:1.7rem 1.4rem;border:1px dotted #ccc}.markdown-section output>:first-child{margin-top:0}.markdown-section output>:last-child{margin-bottom:0}.markdown-section code:after,.markdown-section code:before,.markdown-section output:after,.markdown-section output:before{letter-spacing:.05rem}.markdown-section output:after,.markdown-section pre:after{color:#ccc;font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0;content:attr(data-lang)}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:#42b983;color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:#42b983;color:var(--theme-color,#42b983)}.token.function,.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem;position:relative;left:auto} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/image/favicon.ico b/ruoyi-admin/src/main/resources/static/static/image/favicon.ico new file mode 100644 index 00000000..3f919d85 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/static/image/favicon.ico differ diff --git a/ruoyi-admin/src/main/resources/static/static/image/logo.png b/ruoyi-admin/src/main/resources/static/static/image/logo.png new file mode 100644 index 00000000..306851c1 Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/static/image/logo.png differ diff --git a/ruoyi-admin/src/main/resources/static/static/image/ruoyicloudplus.png b/ruoyi-admin/src/main/resources/static/static/image/ruoyicloudplus.png new file mode 100644 index 00000000..cc697e6d Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/static/image/ruoyicloudplus.png differ diff --git a/ruoyi-admin/src/main/resources/static/static/image/ruoyivueplus.png b/ruoyi-admin/src/main/resources/static/static/image/ruoyivueplus.png new file mode 100644 index 00000000..d0eba10d Binary files /dev/null and b/ruoyi-admin/src/main/resources/static/static/image/ruoyivueplus.png differ diff --git a/ruoyi-admin/src/main/resources/static/static/js/docsify-copy-code.min.js b/ruoyi-admin/src/main/resources/static/static/js/docsify-copy-code.min.js new file mode 100644 index 00000000..02a83a31 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/docsify-copy-code.min.js @@ -0,0 +1,9 @@ +/*! + * docsify-copy-code + * v3.0.0 + * https://github.com/jperasmus/docsify-copy-code + * (c) 2017-2023 JP Erasmus + * MIT license + */ +!function(){"use strict";function e(o){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(o)}!function(e,o){void 0===o&&(o={});var t=o.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=e:c.appendChild(document.createTextNode(e))}}(".docsify-copy-code-button,.docsify-copy-code-button>span{cursor:pointer;transition:all .25s ease}.docsify-copy-code-button{background:grey;background:var(--theme-color,grey);border:0;border-radius:0;color:#fff;font-size:1em;opacity:0;outline:0;overflow:visible;padding:.65em .8em;position:absolute;right:0;top:0;z-index:1}.docsify-copy-code-button>span{background:inherit;border-radius:3px;pointer-events:none}.docsify-copy-code-button>.error,.docsify-copy-code-button>.success{font-size:.825em;opacity:0;padding:.5em .65em;position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:-100}.docsify-copy-code-button.error>.error,.docsify-copy-code-button.success>.success{opacity:1;right:100%;transform:translate(-25%,-50%)}.docsify-copy-code-button:focus,pre:hover .docsify-copy-code-button{opacity:1}.docsify-copy-code-button>[aria-live]{height:1px;left:-10000px;overflow:hidden;position:absolute;top:auto;width:1px}"),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(e,o){e.ready((function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")}))}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(o,t){var n={buttonText:"Copy to clipboard",errorText:"Error",successText:"Copied"};o.doneEach((function(){var o=Array.from(document.querySelectorAll("pre[data-lang]"));t.config.copyCode&&Object.keys(n).forEach((function(o){var c=t.config.copyCode[o];"string"==typeof c?n[o]=c:"object"===e(c)&&Object.keys(c).some((function(e){var t=location.href.indexOf(e)>-1;return n[o]=t?c[e]:n[o],t}))}));var c=['"].join("");o.forEach((function(e){e.insertAdjacentHTML("beforeend",c)}))})),o.mounted((function(){var e=document.querySelector(".content");e&&e.addEventListener("click",(function(e){if(e.target.classList.contains("docsify-copy-code-button")){var o="BUTTON"===e.target.tagName?e.target:e.target.parentNode,t=document.createRange(),c=o.parentNode.querySelector("code"),i=o.querySelector("[aria-live]"),r=window.getSelection();t.selectNode(c),r&&(r.removeAllRanges(),r.addRange(t));try{document.execCommand("copy")&&(o.classList.add("success"),i.innerText=n.successText,setTimeout((function(){o.classList.remove("success"),i.innerText=""}),1e3))}catch(e){console.error("docsify-copy-code: ".concat(e)),o.classList.add("error"),i.innerText=n.errorText,setTimeout((function(){o.classList.remove("error"),i.innerText=""}),1e3)}(r=window.getSelection())&&("function"==typeof r.removeRange?r.removeRange(t):"function"==typeof r.removeAllRanges&&r.removeAllRanges())}}))}))}].concat(window.$docsify.plugins||[])}(); +//# sourceMappingURL=docsify-copy-code.min.js.map \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/docsify-footer.min.js b/ruoyi-admin/src/main/resources/static/static/js/docsify-footer.min.js new file mode 100644 index 00000000..b4262116 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/docsify-footer.min.js @@ -0,0 +1,4 @@ +parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c ul > li"),c("p",e)),this.hyperlink=m(t))}var b=function(){return'
'},k=function(t,e){a=e,r=t.route.path,o={},["previousText","nextText"].forEach(function(n){var i=a[n];"string"==typeof i?o[n]=i:Object.keys(i).some(function(t){var e=r&&-1\n \n
\n \n \n \n '+i+'\n
\n
'+t.prev.name+"
\n ",t.prev&&e.crossChapterText&&'
'+t.prev.chapterName+"
",t.prev&&"
\n \n ",t.next&&'\n \n "].filter(Boolean).join("")};window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(t,e){var n=d({},(e.config,{previousText:"PREVIOUS",nextText:"NEXT",crossChapter:!1,crossChapterText:!1}),e.config.pagination||{});function i(){var t=c("."+h);t&&(t.innerHTML=k(function(t,e){e=e.crossChapter;try{var n=t.router.toURL(t.route.path),i=g(c.all(".sidebar-nav li a")).filter(function(t){return!s(t,".section-link")}),a=i.find(x(n)),r=g((p(a,"ul")||{}).children).filter(function(t){return"LI"===t.tagName.toUpperCase()}),o=e?i.findIndex(x(n)):r.findIndex(function(t){t=m(t);return t&&x(n,t)}),l=e?i:r;return{route:t.route,prev:new y(l[o-1]).toJSON(),next:new y(l[o+1]).toJSON()}}catch(t){return{route:{}}}}(e,n),n))}t.afterEach(function(t){return t+b()}),t.doneEach(i)}].concat(window.$docsify.plugins||[])}); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/docsify-scroll-to-top.min.js b/ruoyi-admin/src/main/resources/static/static/js/docsify-scroll-to-top.min.js new file mode 100644 index 00000000..c5ed3ca2 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/docsify-scroll-to-top.min.js @@ -0,0 +1 @@ +var CONFIG={auto:true,text:"Top",right:15,bottom:15,offset:500};var install=function(hook,vm){var opts=vm.config.scrollToTop||CONFIG;CONFIG.auto=opts.auto&&typeof opts.auto==="boolean"?opts.auto:CONFIG.auto;CONFIG.text=opts.text&&typeof opts.text==="string"?opts.text:CONFIG.text;CONFIG.right=opts.right&&typeof opts.right==="number"?opts.right:CONFIG.right;CONFIG.bottom=opts.bottom&&typeof opts.bottom==="number"?opts.bottom:CONFIG.bottom;CONFIG.offset=opts.offset&&typeof opts.offset==="number"?opts.offset:CONFIG.offset;var onScroll=function(e){if(!CONFIG.auto){return}var offset=window.document.documentElement.scrollTop;var $scrollBtn=Docsify.dom.find("span.scroll-to-top");$scrollBtn.style.display=offset>=CONFIG.offset?"block":"none"};hook.mounted(function(){var scrollBtn=document.createElement("span");scrollBtn.className="scroll-to-top";scrollBtn.style.display=CONFIG.auto?"none":"block";scrollBtn.style.overflow="hidden";scrollBtn.style.position="fixed";scrollBtn.style.right=CONFIG.right+"px";scrollBtn.style.bottom=CONFIG.bottom+"px";scrollBtn.style.width="50px";scrollBtn.style.height="50px";scrollBtn.style.background="white";scrollBtn.style.color="#666";scrollBtn.style.border="1px solid #ddd";scrollBtn.style.borderRadius="4px";scrollBtn.style.lineHeight="42px";scrollBtn.style.fontSize="16px";scrollBtn.style.textAlign="center";scrollBtn.style.boxShadow="0px 0px 6px #eee";scrollBtn.style.cursor="pointer";var textNode=document.createTextNode(CONFIG.text);scrollBtn.appendChild(textNode);document.body.appendChild(scrollBtn);window.addEventListener("scroll",onScroll);scrollBtn.onclick=function(e){e.stopPropagation();var step=window.scrollY/15;var scroll=function(){window.scrollTo(0,window.scrollY-step);if(window.scrollY>0){setTimeout(scroll,15)}};scroll()}})};$docsify.plugins=[].concat(install,$docsify.plugins); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/docsify.min.js b/ruoyi-admin/src/main/resources/static/static/js/docsify.min.js new file mode 100644 index 00000000..18e85aa9 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/docsify.min.js @@ -0,0 +1 @@ +!function(){function c(i){var o=Object.create(null);return function(e){var n=f(e)?e:JSON.stringify(e);return o[n]||(o[n]=i(e))}}var a=c(function(e){return e.replace(/([A-Z])/g,function(e){return"-"+e.toLowerCase()})}),u=Object.prototype.hasOwnProperty,m=Object.assign||function(e){for(var n=arguments,i=1;i=e||n.classList.contains("hidden")?S(h,"add","sticky"):S(h,"remove","sticky"))}function ee(e,n,o,i){var t=[];null!=(n=l(n))&&(t=k(n,"a"));var a,r=decodeURI(e.toURL(e.getCurrentPath()));return t.sort(function(e,n){return n.href.length-e.href.length}).forEach(function(e){var n=decodeURI(e.getAttribute("href")),i=o?e.parentNode:e;e.title=e.title||e.innerText,0!==r.indexOf(n)||a?S(i,"remove","active"):(a=e,S(i,"add","active"))}),i&&(v.title=a?a.title||a.innerText+" - "+J:J),a}function ne(e,n){for(var i=0;ithis.end&&e>=this.next}[this.direction]}},{key:"_defaultEase",value:function(e,n,i,o){return(e/=o/2)<1?i/2*e*e+n:-i/2*(--e*(e-2)-1)+n}}]),re);function re(){var e=0c){n=n||p;break}n=p}!n||(r=fe[ve(e,n.getAttribute("data-id"))])&&r!==a&&(a&&a.classList.remove("active"),r.classList.add("active"),a=r,!pe&&h.classList.contains("sticky")&&(e=i.clientHeight,r=a.offsetTop+a.clientHeight+40,a=a.offsetTop>=t.scrollTop&&r<=t.scrollTop+e,i.scrollTop=a?t.scrollTop:+r"']/),xe=/[&<>"']/g,Se=/[<>"']|&(?!#?\w+;)/,Ae=/[<>"']|&(?!#?\w+;)/g,$e={"&":"&","<":"<",">":">",'"':""","'":"'"};var ze=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function Fe(e){return e.replace(ze,function(e,n){return"colon"===(n=n.toLowerCase())?":":"#"===n.charAt(0)?"x"===n.charAt(1)?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1)):""})}var Ee=/(^|[^\[])\^/g;var Re=/[^\w:]/g,Te=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var Ce={},je=/^[^:]+:\/*[^/]*$/,Le=/^([^:]+:)[\s\S]*$/,Oe=/^([^:]+:\/*[^/]*)[\s\S]*$/;function qe(e,n){Ce[" "+e]||(je.test(e)?Ce[" "+e]=e+"/":Ce[" "+e]=Pe(e,"/",!0));var i=-1===(e=Ce[" "+e]).indexOf(":");return"//"===n.substring(0,2)?i?n:e.replace(Le,"$1")+n:"/"===n.charAt(0)?i?n:e.replace(Oe,"$1")+n:e+n}function Pe(e,n,i){var o=e.length;if(0===o)return"";for(var t=0;tn)i.splice(n);else for(;i.length>=1,e+=e;return i+e},We=we.defaults,Xe=Be,Qe=Ze,Je=Me,Ke=Ve;function en(e,n,i){var o=n.href,t=n.title?Je(n.title):null,n=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:i,href:o,title:t,text:n}:{type:"image",raw:i,href:o,title:t,text:Je(n)}}var nn=function(){function e(e){this.options=e||We}return e.prototype.space=function(e){e=this.rules.block.newline.exec(e);if(e)return 1=i.length?e.slice(i.length):e}).join("\n")}(i,n[3]||"");return{type:"code",raw:i,lang:n[2]&&n[2].trim(),text:e}}},e.prototype.heading=function(e){var n=this.rules.block.heading.exec(e);if(n){var i=n[2].trim();return/#$/.test(i)&&(e=Xe(i,"#"),!this.options.pedantic&&e&&!/ $/.test(e)||(i=e.trim())),{type:"heading",raw:n[0],depth:n[1].length,text:i}}},e.prototype.nptable=function(e){e=this.rules.block.nptable.exec(e);if(e){var n={type:"table",header:Qe(e[1].replace(/^ *| *\| *$/g,"")),align:e[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:e[3]?e[3].replace(/\n$/,"").split("\n"):[],raw:e[0]};if(n.header.length===n.align.length){for(var i=n.align.length,o=0;o ?/gm,"");return{type:"blockquote",raw:n[0],text:e}}},e.prototype.list=function(e){e=this.rules.block.list.exec(e);if(e){for(var n,i,o,t,a,r=e[0],c=e[2],u=1s[1].length:o[1].length>s[0].length||3/i.test(e[0])&&(n=!1),!i&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?i=!0:i&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(i=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:n,inRawBlock:i,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Je(e[0]):e[0]}},e.prototype.link=function(e){var n=this.rules.inline.link.exec(e);if(n){e=n[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;var i=Xe(e.slice(0,-1),"\\");if((e.length-i.length)%2==0)return}else{var o=Ke(n[2],"()");-1$/.test(e)?i.slice(1):i.slice(1,-1):i)&&i.replace(this.rules.inline._escapes,"$1"),title:o&&o.replace(this.rules.inline._escapes,"$1")},n[0])}},e.prototype.reflink=function(e,n){if((i=this.rules.inline.reflink.exec(e))||(i=this.rules.inline.nolink.exec(e))){var e=(i[2]||i[1]).replace(/\s+/g," ");if((e=n[e.toLowerCase()])&&e.href)return en(i,e,i[0]);var i=i[0].charAt(0);return{type:"text",raw:i,text:i}}},e.prototype.strong=function(e,n,i){void 0===i&&(i="");var o=this.rules.inline.strong.start.exec(e);if(o&&(!o[1]||o[1]&&(""===i||this.rules.inline.punctuation.exec(i)))){n=n.slice(-1*e.length);var t,a="**"===o[0]?this.rules.inline.strong.endAst:this.rules.inline.strong.endUnd;for(a.lastIndex=0;null!=(o=a.exec(n));)if(t=this.rules.inline.strong.middle.exec(n.slice(0,o.index+3)))return{type:"strong",raw:e.slice(0,t[0].length),text:e.slice(2,t[0].length-2)}}},e.prototype.em=function(e,n,i){void 0===i&&(i="");var o=this.rules.inline.em.start.exec(e);if(o&&(!o[1]||o[1]&&(""===i||this.rules.inline.punctuation.exec(i)))){n=n.slice(-1*e.length);var t,a="*"===o[0]?this.rules.inline.em.endAst:this.rules.inline.em.endUnd;for(a.lastIndex=0;null!=(o=a.exec(n));)if(t=this.rules.inline.em.middle.exec(n.slice(0,o.index+2)))return{type:"em",raw:e.slice(0,t[0].length),text:e.slice(1,t[0].length-1)}}},e.prototype.codespan=function(e){var n=this.rules.inline.code.exec(e);if(n){var i=n[2].replace(/\n/g," "),o=/[^ ]/.test(i),e=/^ /.test(i)&&/ $/.test(i);return o&&e&&(i=i.substring(1,i.length-1)),i=Je(i,!0),{type:"codespan",raw:n[0],text:i}}},e.prototype.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},e.prototype.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2]}},e.prototype.autolink=function(e,n){e=this.rules.inline.autolink.exec(e);if(e){var i,n="@"===e[2]?"mailto:"+(i=Je(this.options.mangle?n(e[1]):e[1])):i=Je(e[1]);return{type:"link",raw:e[0],text:i,href:n,tokens:[{type:"text",raw:i,text:i}]}}},e.prototype.url=function(e,n){var i,o,t,a;if(i=this.rules.inline.url.exec(e)){if("@"===i[2])t="mailto:"+(o=Je(this.options.mangle?n(i[0]):i[0]));else{for(;a=i[0],i[0]=this.rules.inline._backpedal.exec(i[0])[0],a!==i[0];);o=Je(i[0]),t="www."===i[1]?"http://"+o:o}return{type:"link",raw:i[0],text:o,href:t,tokens:[{type:"text",raw:o,text:o}]}}},e.prototype.inlineText=function(e,n,i){e=this.rules.inline.text.exec(e);if(e){i=n?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Je(e[0]):e[0]:Je(this.options.smartypants?i(e[0]):e[0]);return{type:"text",raw:e[0],text:i}}},e}(),Ze=De,Ve=Ne,De=Ue,Ne={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:Ze,table:Ze,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};Ne.def=Ve(Ne.def).replace("label",Ne._label).replace("title",Ne._title).getRegex(),Ne.bullet=/(?:[*+-]|\d{1,9}[.)])/,Ne.item=/^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/,Ne.item=Ve(Ne.item,"gm").replace(/bull/g,Ne.bullet).getRegex(),Ne.listItemStart=Ve(/^( *)(bull)/).replace("bull",Ne.bullet).getRegex(),Ne.list=Ve(Ne.list).replace(/bull/g,Ne.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+Ne.def.source+")").getRegex(),Ne._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Ne._comment=/|$)/,Ne.html=Ve(Ne.html,"i").replace("comment",Ne._comment).replace("tag",Ne._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Ne.paragraph=Ve(Ne._paragraph).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.blockquote=Ve(Ne.blockquote).replace("paragraph",Ne.paragraph).getRegex(),Ne.normal=De({},Ne),Ne.gfm=De({},Ne.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n {0,3}([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n {0,3}\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),Ne.gfm.nptable=Ve(Ne.gfm.nptable).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.gfm.table=Ve(Ne.gfm.table).replace("hr",Ne.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",Ne._tag).getRegex(),Ne.pedantic=De({},Ne.normal,{html:Ve("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",Ne._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:Ze,paragraph:Ve(Ne.normal._paragraph).replace("hr",Ne.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",Ne.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});Ze={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:Ze,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",strong:{start:/^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,middle:/^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,endAst:/[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/},em:{start:/^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,middle:/^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,endAst:/[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:Ze,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};Ze.punctuation=Ve(Ze.punctuation).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze._blockSkip="\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>",Ze._overlapSkip="__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*",Ze._comment=Ve(Ne._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),Ze.em.start=Ve(Ze.em.start).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.em.middle=Ve(Ze.em.middle).replace(/punctuation/g,Ze._punctuation).replace(/overlapSkip/g,Ze._overlapSkip).getRegex(),Ze.em.endAst=Ve(Ze.em.endAst,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.em.endUnd=Ve(Ze.em.endUnd,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.start=Ve(Ze.strong.start).replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.middle=Ve(Ze.strong.middle).replace(/punctuation/g,Ze._punctuation).replace(/overlapSkip/g,Ze._overlapSkip).getRegex(),Ze.strong.endAst=Ve(Ze.strong.endAst,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.strong.endUnd=Ve(Ze.strong.endUnd,"g").replace(/punctuation/g,Ze._punctuation).getRegex(),Ze.blockSkip=Ve(Ze._blockSkip,"g").getRegex(),Ze.overlapSkip=Ve(Ze._overlapSkip,"g").getRegex(),Ze._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,Ze._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,Ze._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,Ze.autolink=Ve(Ze.autolink).replace("scheme",Ze._scheme).replace("email",Ze._email).getRegex(),Ze._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,Ze.tag=Ve(Ze.tag).replace("comment",Ze._comment).replace("attribute",Ze._attribute).getRegex(),Ze._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ze._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,Ze._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,Ze.link=Ve(Ze.link).replace("label",Ze._label).replace("href",Ze._href).replace("title",Ze._title).getRegex(),Ze.reflink=Ve(Ze.reflink).replace("label",Ze._label).getRegex(),Ze.reflinkSearch=Ve(Ze.reflinkSearch,"g").replace("reflink",Ze.reflink).replace("nolink",Ze.nolink).getRegex(),Ze.normal=De({},Ze),Ze.pedantic=De({},Ze.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:Ve(/^!?\[(label)\]\((.*?)\)/).replace("label",Ze._label).getRegex(),reflink:Ve(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Ze._label).getRegex()}),Ze.gfm=De({},Ze.normal,{escape:Ve(Ze.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\'+(i?e:gn(e,!0))+"\n":"
"+(i?e:gn(e,!0))+"
\n"},e.prototype.blockquote=function(e){return"
\n"+e+"
\n"},e.prototype.html=function(e){return e},e.prototype.heading=function(e,n,i,o){return this.options.headerIds?"'+e+"\n":""+e+"\n"},e.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},e.prototype.list=function(e,n,i){var o=n?"ol":"ul";return"<"+o+(n&&1!==i?' start="'+i+'"':"")+">\n"+e+"\n"},e.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},e.prototype.checkbox=function(e){return" "},e.prototype.paragraph=function(e){return"

    "+e+"

    \n"},e.prototype.table=function(e,n){return"\n\n"+e+"\n"+(n=n&&""+n+"")+"
    \n"},e.prototype.tablerow=function(e){return"\n"+e+"\n"},e.prototype.tablecell=function(e,n){var i=n.header?"th":"td";return(n.align?"<"+i+' align="'+n.align+'">':"<"+i+">")+e+"\n"},e.prototype.strong=function(e){return""+e+""},e.prototype.em=function(e){return""+e+""},e.prototype.codespan=function(e){return""+e+""},e.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},e.prototype.del=function(e){return""+e+""},e.prototype.link=function(e,n,i){if(null===(e=dn(this.options.sanitize,this.options.baseUrl,e)))return i;e='"},e.prototype.image=function(e,n,i){if(null===(e=dn(this.options.sanitize,this.options.baseUrl,e)))return i;i=''+i+'":">"},e.prototype.text=function(e){return e},e}(),ln=function(){function e(){}return e.prototype.strong=function(e){return e},e.prototype.em=function(e){return e},e.prototype.codespan=function(e){return e},e.prototype.del=function(e){return e},e.prototype.html=function(e){return e},e.prototype.text=function(e){return e},e.prototype.link=function(e,n,i){return""+i},e.prototype.image=function(e,n,i){return""+i},e.prototype.br=function(){return""},e}(),vn=function(){function e(){this.seen={}}return e.prototype.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},e.prototype.getNextSafeSlug=function(e,n){var i=e,o=0;if(this.seen.hasOwnProperty(i))for(o=this.seen[e];i=e+"-"+ ++o,this.seen.hasOwnProperty(i););return n||(this.seen[e]=o,this.seen[i]=0),i},e.prototype.slug=function(e,n){void 0===n&&(n={});e=this.serialize(e);return this.getNextSafeSlug(e,n.dryrun)},e}(),hn=we.defaults,_n=Ie,mn=function(){function i(e){this.options=e||hn,this.options.renderer=this.options.renderer||new sn,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new ln,this.slugger=new vn}return i.parse=function(e,n){return new i(n).parse(e)},i.parseInline=function(e,n){return new i(n).parseInline(e)},i.prototype.parse=function(e,n){void 0===n&&(n=!0);for(var i,o,t,a,r,c,u,f,p,d,g,s,l,v,h,_="",m=e.length,b=0;bAn error occurred:

    "+wn(e.message+"",!0)+"
    ";throw e}}xn.options=xn.setOptions=function(e){return bn(xn.defaults,e),yn(xn.defaults),xn},xn.getDefaults=Me,xn.defaults=we,xn.use=function(a){var n,e=bn({},a);if(a.renderer){var i,r=xn.defaults.renderer||new sn;for(i in a.renderer)!function(o){var t=r[o];r[o]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var i=a.renderer[o].apply(r,e);return i=!1===i?t.apply(r,e):i}}(i);e.renderer=r}if(a.tokenizer){var t,c=xn.defaults.tokenizer||new nn;for(t in a.tokenizer)!function(){var o=c[t];c[t]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var i=a.tokenizer[t].apply(c,e);return i=!1===i?o.apply(c,e):i}}();e.tokenizer=c}a.walkTokens&&(n=xn.defaults.walkTokens,e.walkTokens=function(e){a.walkTokens(e),n&&n(e)}),xn.setOptions(e)},xn.walkTokens=function(e,n){for(var i=0,o=e;iAn error occurred:

    "+wn(e.message+"",!0)+"
    ";throw e}},xn.Parser=mn,xn.parser=mn.parse,xn.Renderer=sn,xn.TextRenderer=ln,xn.Lexer=fn,xn.lexer=fn.lex,xn.Tokenizer=nn,xn.Slugger=vn;var Sn=xn.parse=xn;function An(e,i){if(void 0===i&&(i='
      {inner}
    '),!e||!e.length)return"";var o="";return e.forEach(function(e){var n=e.title.replace(/(<([^>]+)>)/g,"");o+='
  • '+e.title+"
  • ",e.children&&(o+=An(e.children,i))}),i.replace("{inner}",o)}function $n(e,n){return'

    '+n.slice(5).trim()+"

    "}function zn(e,o){var t=[],a={};return e.forEach(function(e){var n=e.level||1,i=n-1;o?@[\]^`{|}~]/g;function Rn(e){return e.toLowerCase()}function Tn(e){if("string"!=typeof e)return"";var n=e.trim().replace(/[A-Z]+/g,Rn).replace(/<[^>]+>/g,"").replace(En,"").replace(/\s/g,"-").replace(/-+/g,"-").replace(/^(\d)/,"_$1"),e=Fn[n],e=u.call(Fn,n)?e+1:0;return n=(Fn[n]=e)?n+"-"+e:n}Tn.clear=function(){Fn={}};var Cn={baseURL:"https://github.githubassets.com/images/icons/emoji/",data:{100:"unicode/1f4af.png?v8",1234:"unicode/1f522.png?v8","+1":"unicode/1f44d.png?v8","-1":"unicode/1f44e.png?v8","1st_place_medal":"unicode/1f947.png?v8","2nd_place_medal":"unicode/1f948.png?v8","3rd_place_medal":"unicode/1f949.png?v8","8ball":"unicode/1f3b1.png?v8",a:"unicode/1f170.png?v8",ab:"unicode/1f18e.png?v8",abacus:"unicode/1f9ee.png?v8",abc:"unicode/1f524.png?v8",abcd:"unicode/1f521.png?v8",accept:"unicode/1f251.png?v8",accessibility:"accessibility.png?v8",accordion:"unicode/1fa97.png?v8",adhesive_bandage:"unicode/1fa79.png?v8",adult:"unicode/1f9d1.png?v8",aerial_tramway:"unicode/1f6a1.png?v8",afghanistan:"unicode/1f1e6-1f1eb.png?v8",airplane:"unicode/2708.png?v8",aland_islands:"unicode/1f1e6-1f1fd.png?v8",alarm_clock:"unicode/23f0.png?v8",albania:"unicode/1f1e6-1f1f1.png?v8",alembic:"unicode/2697.png?v8",algeria:"unicode/1f1e9-1f1ff.png?v8",alien:"unicode/1f47d.png?v8",ambulance:"unicode/1f691.png?v8",american_samoa:"unicode/1f1e6-1f1f8.png?v8",amphora:"unicode/1f3fa.png?v8",anatomical_heart:"unicode/1fac0.png?v8",anchor:"unicode/2693.png?v8",andorra:"unicode/1f1e6-1f1e9.png?v8",angel:"unicode/1f47c.png?v8",anger:"unicode/1f4a2.png?v8",angola:"unicode/1f1e6-1f1f4.png?v8",angry:"unicode/1f620.png?v8",anguilla:"unicode/1f1e6-1f1ee.png?v8",anguished:"unicode/1f627.png?v8",ant:"unicode/1f41c.png?v8",antarctica:"unicode/1f1e6-1f1f6.png?v8",antigua_barbuda:"unicode/1f1e6-1f1ec.png?v8",apple:"unicode/1f34e.png?v8",aquarius:"unicode/2652.png?v8",argentina:"unicode/1f1e6-1f1f7.png?v8",aries:"unicode/2648.png?v8",armenia:"unicode/1f1e6-1f1f2.png?v8",arrow_backward:"unicode/25c0.png?v8",arrow_double_down:"unicode/23ec.png?v8",arrow_double_up:"unicode/23eb.png?v8",arrow_down:"unicode/2b07.png?v8",arrow_down_small:"unicode/1f53d.png?v8",arrow_forward:"unicode/25b6.png?v8",arrow_heading_down:"unicode/2935.png?v8",arrow_heading_up:"unicode/2934.png?v8",arrow_left:"unicode/2b05.png?v8",arrow_lower_left:"unicode/2199.png?v8",arrow_lower_right:"unicode/2198.png?v8",arrow_right:"unicode/27a1.png?v8",arrow_right_hook:"unicode/21aa.png?v8",arrow_up:"unicode/2b06.png?v8",arrow_up_down:"unicode/2195.png?v8",arrow_up_small:"unicode/1f53c.png?v8",arrow_upper_left:"unicode/2196.png?v8",arrow_upper_right:"unicode/2197.png?v8",arrows_clockwise:"unicode/1f503.png?v8",arrows_counterclockwise:"unicode/1f504.png?v8",art:"unicode/1f3a8.png?v8",articulated_lorry:"unicode/1f69b.png?v8",artificial_satellite:"unicode/1f6f0.png?v8",artist:"unicode/1f9d1-1f3a8.png?v8",aruba:"unicode/1f1e6-1f1fc.png?v8",ascension_island:"unicode/1f1e6-1f1e8.png?v8",asterisk:"unicode/002a-20e3.png?v8",astonished:"unicode/1f632.png?v8",astronaut:"unicode/1f9d1-1f680.png?v8",athletic_shoe:"unicode/1f45f.png?v8",atm:"unicode/1f3e7.png?v8",atom:"atom.png?v8",atom_symbol:"unicode/269b.png?v8",australia:"unicode/1f1e6-1f1fa.png?v8",austria:"unicode/1f1e6-1f1f9.png?v8",auto_rickshaw:"unicode/1f6fa.png?v8",avocado:"unicode/1f951.png?v8",axe:"unicode/1fa93.png?v8",azerbaijan:"unicode/1f1e6-1f1ff.png?v8",b:"unicode/1f171.png?v8",baby:"unicode/1f476.png?v8",baby_bottle:"unicode/1f37c.png?v8",baby_chick:"unicode/1f424.png?v8",baby_symbol:"unicode/1f6bc.png?v8",back:"unicode/1f519.png?v8",bacon:"unicode/1f953.png?v8",badger:"unicode/1f9a1.png?v8",badminton:"unicode/1f3f8.png?v8",bagel:"unicode/1f96f.png?v8",baggage_claim:"unicode/1f6c4.png?v8",baguette_bread:"unicode/1f956.png?v8",bahamas:"unicode/1f1e7-1f1f8.png?v8",bahrain:"unicode/1f1e7-1f1ed.png?v8",balance_scale:"unicode/2696.png?v8",bald_man:"unicode/1f468-1f9b2.png?v8",bald_woman:"unicode/1f469-1f9b2.png?v8",ballet_shoes:"unicode/1fa70.png?v8",balloon:"unicode/1f388.png?v8",ballot_box:"unicode/1f5f3.png?v8",ballot_box_with_check:"unicode/2611.png?v8",bamboo:"unicode/1f38d.png?v8",banana:"unicode/1f34c.png?v8",bangbang:"unicode/203c.png?v8",bangladesh:"unicode/1f1e7-1f1e9.png?v8",banjo:"unicode/1fa95.png?v8",bank:"unicode/1f3e6.png?v8",bar_chart:"unicode/1f4ca.png?v8",barbados:"unicode/1f1e7-1f1e7.png?v8",barber:"unicode/1f488.png?v8",baseball:"unicode/26be.png?v8",basecamp:"basecamp.png?v8",basecampy:"basecampy.png?v8",basket:"unicode/1f9fa.png?v8",basketball:"unicode/1f3c0.png?v8",basketball_man:"unicode/26f9-2642.png?v8",basketball_woman:"unicode/26f9-2640.png?v8",bat:"unicode/1f987.png?v8",bath:"unicode/1f6c0.png?v8",bathtub:"unicode/1f6c1.png?v8",battery:"unicode/1f50b.png?v8",beach_umbrella:"unicode/1f3d6.png?v8",bear:"unicode/1f43b.png?v8",bearded_person:"unicode/1f9d4.png?v8",beaver:"unicode/1f9ab.png?v8",bed:"unicode/1f6cf.png?v8",bee:"unicode/1f41d.png?v8",beer:"unicode/1f37a.png?v8",beers:"unicode/1f37b.png?v8",beetle:"unicode/1fab2.png?v8",beginner:"unicode/1f530.png?v8",belarus:"unicode/1f1e7-1f1fe.png?v8",belgium:"unicode/1f1e7-1f1ea.png?v8",belize:"unicode/1f1e7-1f1ff.png?v8",bell:"unicode/1f514.png?v8",bell_pepper:"unicode/1fad1.png?v8",bellhop_bell:"unicode/1f6ce.png?v8",benin:"unicode/1f1e7-1f1ef.png?v8",bento:"unicode/1f371.png?v8",bermuda:"unicode/1f1e7-1f1f2.png?v8",beverage_box:"unicode/1f9c3.png?v8",bhutan:"unicode/1f1e7-1f1f9.png?v8",bicyclist:"unicode/1f6b4.png?v8",bike:"unicode/1f6b2.png?v8",biking_man:"unicode/1f6b4-2642.png?v8",biking_woman:"unicode/1f6b4-2640.png?v8",bikini:"unicode/1f459.png?v8",billed_cap:"unicode/1f9e2.png?v8",biohazard:"unicode/2623.png?v8",bird:"unicode/1f426.png?v8",birthday:"unicode/1f382.png?v8",bison:"unicode/1f9ac.png?v8",black_cat:"unicode/1f408-2b1b.png?v8",black_circle:"unicode/26ab.png?v8",black_flag:"unicode/1f3f4.png?v8",black_heart:"unicode/1f5a4.png?v8",black_joker:"unicode/1f0cf.png?v8",black_large_square:"unicode/2b1b.png?v8",black_medium_small_square:"unicode/25fe.png?v8",black_medium_square:"unicode/25fc.png?v8",black_nib:"unicode/2712.png?v8",black_small_square:"unicode/25aa.png?v8",black_square_button:"unicode/1f532.png?v8",blond_haired_man:"unicode/1f471-2642.png?v8",blond_haired_person:"unicode/1f471.png?v8",blond_haired_woman:"unicode/1f471-2640.png?v8",blonde_woman:"unicode/1f471-2640.png?v8",blossom:"unicode/1f33c.png?v8",blowfish:"unicode/1f421.png?v8",blue_book:"unicode/1f4d8.png?v8",blue_car:"unicode/1f699.png?v8",blue_heart:"unicode/1f499.png?v8",blue_square:"unicode/1f7e6.png?v8",blueberries:"unicode/1fad0.png?v8",blush:"unicode/1f60a.png?v8",boar:"unicode/1f417.png?v8",boat:"unicode/26f5.png?v8",bolivia:"unicode/1f1e7-1f1f4.png?v8",bomb:"unicode/1f4a3.png?v8",bone:"unicode/1f9b4.png?v8",book:"unicode/1f4d6.png?v8",bookmark:"unicode/1f516.png?v8",bookmark_tabs:"unicode/1f4d1.png?v8",books:"unicode/1f4da.png?v8",boom:"unicode/1f4a5.png?v8",boomerang:"unicode/1fa83.png?v8",boot:"unicode/1f462.png?v8",bosnia_herzegovina:"unicode/1f1e7-1f1e6.png?v8",botswana:"unicode/1f1e7-1f1fc.png?v8",bouncing_ball_man:"unicode/26f9-2642.png?v8",bouncing_ball_person:"unicode/26f9.png?v8",bouncing_ball_woman:"unicode/26f9-2640.png?v8",bouquet:"unicode/1f490.png?v8",bouvet_island:"unicode/1f1e7-1f1fb.png?v8",bow:"unicode/1f647.png?v8",bow_and_arrow:"unicode/1f3f9.png?v8",bowing_man:"unicode/1f647-2642.png?v8",bowing_woman:"unicode/1f647-2640.png?v8",bowl_with_spoon:"unicode/1f963.png?v8",bowling:"unicode/1f3b3.png?v8",bowtie:"bowtie.png?v8",boxing_glove:"unicode/1f94a.png?v8",boy:"unicode/1f466.png?v8",brain:"unicode/1f9e0.png?v8",brazil:"unicode/1f1e7-1f1f7.png?v8",bread:"unicode/1f35e.png?v8",breast_feeding:"unicode/1f931.png?v8",bricks:"unicode/1f9f1.png?v8",bride_with_veil:"unicode/1f470-2640.png?v8",bridge_at_night:"unicode/1f309.png?v8",briefcase:"unicode/1f4bc.png?v8",british_indian_ocean_territory:"unicode/1f1ee-1f1f4.png?v8",british_virgin_islands:"unicode/1f1fb-1f1ec.png?v8",broccoli:"unicode/1f966.png?v8",broken_heart:"unicode/1f494.png?v8",broom:"unicode/1f9f9.png?v8",brown_circle:"unicode/1f7e4.png?v8",brown_heart:"unicode/1f90e.png?v8",brown_square:"unicode/1f7eb.png?v8",brunei:"unicode/1f1e7-1f1f3.png?v8",bubble_tea:"unicode/1f9cb.png?v8",bucket:"unicode/1faa3.png?v8",bug:"unicode/1f41b.png?v8",building_construction:"unicode/1f3d7.png?v8",bulb:"unicode/1f4a1.png?v8",bulgaria:"unicode/1f1e7-1f1ec.png?v8",bullettrain_front:"unicode/1f685.png?v8",bullettrain_side:"unicode/1f684.png?v8",burkina_faso:"unicode/1f1e7-1f1eb.png?v8",burrito:"unicode/1f32f.png?v8",burundi:"unicode/1f1e7-1f1ee.png?v8",bus:"unicode/1f68c.png?v8",business_suit_levitating:"unicode/1f574.png?v8",busstop:"unicode/1f68f.png?v8",bust_in_silhouette:"unicode/1f464.png?v8",busts_in_silhouette:"unicode/1f465.png?v8",butter:"unicode/1f9c8.png?v8",butterfly:"unicode/1f98b.png?v8",cactus:"unicode/1f335.png?v8",cake:"unicode/1f370.png?v8",calendar:"unicode/1f4c6.png?v8",call_me_hand:"unicode/1f919.png?v8",calling:"unicode/1f4f2.png?v8",cambodia:"unicode/1f1f0-1f1ed.png?v8",camel:"unicode/1f42b.png?v8",camera:"unicode/1f4f7.png?v8",camera_flash:"unicode/1f4f8.png?v8",cameroon:"unicode/1f1e8-1f1f2.png?v8",camping:"unicode/1f3d5.png?v8",canada:"unicode/1f1e8-1f1e6.png?v8",canary_islands:"unicode/1f1ee-1f1e8.png?v8",cancer:"unicode/264b.png?v8",candle:"unicode/1f56f.png?v8",candy:"unicode/1f36c.png?v8",canned_food:"unicode/1f96b.png?v8",canoe:"unicode/1f6f6.png?v8",cape_verde:"unicode/1f1e8-1f1fb.png?v8",capital_abcd:"unicode/1f520.png?v8",capricorn:"unicode/2651.png?v8",car:"unicode/1f697.png?v8",card_file_box:"unicode/1f5c3.png?v8",card_index:"unicode/1f4c7.png?v8",card_index_dividers:"unicode/1f5c2.png?v8",caribbean_netherlands:"unicode/1f1e7-1f1f6.png?v8",carousel_horse:"unicode/1f3a0.png?v8",carpentry_saw:"unicode/1fa9a.png?v8",carrot:"unicode/1f955.png?v8",cartwheeling:"unicode/1f938.png?v8",cat:"unicode/1f431.png?v8",cat2:"unicode/1f408.png?v8",cayman_islands:"unicode/1f1f0-1f1fe.png?v8",cd:"unicode/1f4bf.png?v8",central_african_republic:"unicode/1f1e8-1f1eb.png?v8",ceuta_melilla:"unicode/1f1ea-1f1e6.png?v8",chad:"unicode/1f1f9-1f1e9.png?v8",chains:"unicode/26d3.png?v8",chair:"unicode/1fa91.png?v8",champagne:"unicode/1f37e.png?v8",chart:"unicode/1f4b9.png?v8",chart_with_downwards_trend:"unicode/1f4c9.png?v8",chart_with_upwards_trend:"unicode/1f4c8.png?v8",checkered_flag:"unicode/1f3c1.png?v8",cheese:"unicode/1f9c0.png?v8",cherries:"unicode/1f352.png?v8",cherry_blossom:"unicode/1f338.png?v8",chess_pawn:"unicode/265f.png?v8",chestnut:"unicode/1f330.png?v8",chicken:"unicode/1f414.png?v8",child:"unicode/1f9d2.png?v8",children_crossing:"unicode/1f6b8.png?v8",chile:"unicode/1f1e8-1f1f1.png?v8",chipmunk:"unicode/1f43f.png?v8",chocolate_bar:"unicode/1f36b.png?v8",chopsticks:"unicode/1f962.png?v8",christmas_island:"unicode/1f1e8-1f1fd.png?v8",christmas_tree:"unicode/1f384.png?v8",church:"unicode/26ea.png?v8",cinema:"unicode/1f3a6.png?v8",circus_tent:"unicode/1f3aa.png?v8",city_sunrise:"unicode/1f307.png?v8",city_sunset:"unicode/1f306.png?v8",cityscape:"unicode/1f3d9.png?v8",cl:"unicode/1f191.png?v8",clamp:"unicode/1f5dc.png?v8",clap:"unicode/1f44f.png?v8",clapper:"unicode/1f3ac.png?v8",classical_building:"unicode/1f3db.png?v8",climbing:"unicode/1f9d7.png?v8",climbing_man:"unicode/1f9d7-2642.png?v8",climbing_woman:"unicode/1f9d7-2640.png?v8",clinking_glasses:"unicode/1f942.png?v8",clipboard:"unicode/1f4cb.png?v8",clipperton_island:"unicode/1f1e8-1f1f5.png?v8",clock1:"unicode/1f550.png?v8",clock10:"unicode/1f559.png?v8",clock1030:"unicode/1f565.png?v8",clock11:"unicode/1f55a.png?v8",clock1130:"unicode/1f566.png?v8",clock12:"unicode/1f55b.png?v8",clock1230:"unicode/1f567.png?v8",clock130:"unicode/1f55c.png?v8",clock2:"unicode/1f551.png?v8",clock230:"unicode/1f55d.png?v8",clock3:"unicode/1f552.png?v8",clock330:"unicode/1f55e.png?v8",clock4:"unicode/1f553.png?v8",clock430:"unicode/1f55f.png?v8",clock5:"unicode/1f554.png?v8",clock530:"unicode/1f560.png?v8",clock6:"unicode/1f555.png?v8",clock630:"unicode/1f561.png?v8",clock7:"unicode/1f556.png?v8",clock730:"unicode/1f562.png?v8",clock8:"unicode/1f557.png?v8",clock830:"unicode/1f563.png?v8",clock9:"unicode/1f558.png?v8",clock930:"unicode/1f564.png?v8",closed_book:"unicode/1f4d5.png?v8",closed_lock_with_key:"unicode/1f510.png?v8",closed_umbrella:"unicode/1f302.png?v8",cloud:"unicode/2601.png?v8",cloud_with_lightning:"unicode/1f329.png?v8",cloud_with_lightning_and_rain:"unicode/26c8.png?v8",cloud_with_rain:"unicode/1f327.png?v8",cloud_with_snow:"unicode/1f328.png?v8",clown_face:"unicode/1f921.png?v8",clubs:"unicode/2663.png?v8",cn:"unicode/1f1e8-1f1f3.png?v8",coat:"unicode/1f9e5.png?v8",cockroach:"unicode/1fab3.png?v8",cocktail:"unicode/1f378.png?v8",coconut:"unicode/1f965.png?v8",cocos_islands:"unicode/1f1e8-1f1e8.png?v8",coffee:"unicode/2615.png?v8",coffin:"unicode/26b0.png?v8",coin:"unicode/1fa99.png?v8",cold_face:"unicode/1f976.png?v8",cold_sweat:"unicode/1f630.png?v8",collision:"unicode/1f4a5.png?v8",colombia:"unicode/1f1e8-1f1f4.png?v8",comet:"unicode/2604.png?v8",comoros:"unicode/1f1f0-1f1f2.png?v8",compass:"unicode/1f9ed.png?v8",computer:"unicode/1f4bb.png?v8",computer_mouse:"unicode/1f5b1.png?v8",confetti_ball:"unicode/1f38a.png?v8",confounded:"unicode/1f616.png?v8",confused:"unicode/1f615.png?v8",congo_brazzaville:"unicode/1f1e8-1f1ec.png?v8",congo_kinshasa:"unicode/1f1e8-1f1e9.png?v8",congratulations:"unicode/3297.png?v8",construction:"unicode/1f6a7.png?v8",construction_worker:"unicode/1f477.png?v8",construction_worker_man:"unicode/1f477-2642.png?v8",construction_worker_woman:"unicode/1f477-2640.png?v8",control_knobs:"unicode/1f39b.png?v8",convenience_store:"unicode/1f3ea.png?v8",cook:"unicode/1f9d1-1f373.png?v8",cook_islands:"unicode/1f1e8-1f1f0.png?v8",cookie:"unicode/1f36a.png?v8",cool:"unicode/1f192.png?v8",cop:"unicode/1f46e.png?v8",copyright:"unicode/00a9.png?v8",corn:"unicode/1f33d.png?v8",costa_rica:"unicode/1f1e8-1f1f7.png?v8",cote_divoire:"unicode/1f1e8-1f1ee.png?v8",couch_and_lamp:"unicode/1f6cb.png?v8",couple:"unicode/1f46b.png?v8",couple_with_heart:"unicode/1f491.png?v8",couple_with_heart_man_man:"unicode/1f468-2764-1f468.png?v8",couple_with_heart_woman_man:"unicode/1f469-2764-1f468.png?v8",couple_with_heart_woman_woman:"unicode/1f469-2764-1f469.png?v8",couplekiss:"unicode/1f48f.png?v8",couplekiss_man_man:"unicode/1f468-2764-1f48b-1f468.png?v8",couplekiss_man_woman:"unicode/1f469-2764-1f48b-1f468.png?v8",couplekiss_woman_woman:"unicode/1f469-2764-1f48b-1f469.png?v8",cow:"unicode/1f42e.png?v8",cow2:"unicode/1f404.png?v8",cowboy_hat_face:"unicode/1f920.png?v8",crab:"unicode/1f980.png?v8",crayon:"unicode/1f58d.png?v8",credit_card:"unicode/1f4b3.png?v8",crescent_moon:"unicode/1f319.png?v8",cricket:"unicode/1f997.png?v8",cricket_game:"unicode/1f3cf.png?v8",croatia:"unicode/1f1ed-1f1f7.png?v8",crocodile:"unicode/1f40a.png?v8",croissant:"unicode/1f950.png?v8",crossed_fingers:"unicode/1f91e.png?v8",crossed_flags:"unicode/1f38c.png?v8",crossed_swords:"unicode/2694.png?v8",crown:"unicode/1f451.png?v8",cry:"unicode/1f622.png?v8",crying_cat_face:"unicode/1f63f.png?v8",crystal_ball:"unicode/1f52e.png?v8",cuba:"unicode/1f1e8-1f1fa.png?v8",cucumber:"unicode/1f952.png?v8",cup_with_straw:"unicode/1f964.png?v8",cupcake:"unicode/1f9c1.png?v8",cupid:"unicode/1f498.png?v8",curacao:"unicode/1f1e8-1f1fc.png?v8",curling_stone:"unicode/1f94c.png?v8",curly_haired_man:"unicode/1f468-1f9b1.png?v8",curly_haired_woman:"unicode/1f469-1f9b1.png?v8",curly_loop:"unicode/27b0.png?v8",currency_exchange:"unicode/1f4b1.png?v8",curry:"unicode/1f35b.png?v8",cursing_face:"unicode/1f92c.png?v8",custard:"unicode/1f36e.png?v8",customs:"unicode/1f6c3.png?v8",cut_of_meat:"unicode/1f969.png?v8",cyclone:"unicode/1f300.png?v8",cyprus:"unicode/1f1e8-1f1fe.png?v8",czech_republic:"unicode/1f1e8-1f1ff.png?v8",dagger:"unicode/1f5e1.png?v8",dancer:"unicode/1f483.png?v8",dancers:"unicode/1f46f.png?v8",dancing_men:"unicode/1f46f-2642.png?v8",dancing_women:"unicode/1f46f-2640.png?v8",dango:"unicode/1f361.png?v8",dark_sunglasses:"unicode/1f576.png?v8",dart:"unicode/1f3af.png?v8",dash:"unicode/1f4a8.png?v8",date:"unicode/1f4c5.png?v8",de:"unicode/1f1e9-1f1ea.png?v8",deaf_man:"unicode/1f9cf-2642.png?v8",deaf_person:"unicode/1f9cf.png?v8",deaf_woman:"unicode/1f9cf-2640.png?v8",deciduous_tree:"unicode/1f333.png?v8",deer:"unicode/1f98c.png?v8",denmark:"unicode/1f1e9-1f1f0.png?v8",department_store:"unicode/1f3ec.png?v8",dependabot:"dependabot.png?v8",derelict_house:"unicode/1f3da.png?v8",desert:"unicode/1f3dc.png?v8",desert_island:"unicode/1f3dd.png?v8",desktop_computer:"unicode/1f5a5.png?v8",detective:"unicode/1f575.png?v8",diamond_shape_with_a_dot_inside:"unicode/1f4a0.png?v8",diamonds:"unicode/2666.png?v8",diego_garcia:"unicode/1f1e9-1f1ec.png?v8",disappointed:"unicode/1f61e.png?v8",disappointed_relieved:"unicode/1f625.png?v8",disguised_face:"unicode/1f978.png?v8",diving_mask:"unicode/1f93f.png?v8",diya_lamp:"unicode/1fa94.png?v8",dizzy:"unicode/1f4ab.png?v8",dizzy_face:"unicode/1f635.png?v8",djibouti:"unicode/1f1e9-1f1ef.png?v8",dna:"unicode/1f9ec.png?v8",do_not_litter:"unicode/1f6af.png?v8",dodo:"unicode/1f9a4.png?v8",dog:"unicode/1f436.png?v8",dog2:"unicode/1f415.png?v8",dollar:"unicode/1f4b5.png?v8",dolls:"unicode/1f38e.png?v8",dolphin:"unicode/1f42c.png?v8",dominica:"unicode/1f1e9-1f1f2.png?v8",dominican_republic:"unicode/1f1e9-1f1f4.png?v8",door:"unicode/1f6aa.png?v8",doughnut:"unicode/1f369.png?v8",dove:"unicode/1f54a.png?v8",dragon:"unicode/1f409.png?v8",dragon_face:"unicode/1f432.png?v8",dress:"unicode/1f457.png?v8",dromedary_camel:"unicode/1f42a.png?v8",drooling_face:"unicode/1f924.png?v8",drop_of_blood:"unicode/1fa78.png?v8",droplet:"unicode/1f4a7.png?v8",drum:"unicode/1f941.png?v8",duck:"unicode/1f986.png?v8",dumpling:"unicode/1f95f.png?v8",dvd:"unicode/1f4c0.png?v8","e-mail":"unicode/1f4e7.png?v8",eagle:"unicode/1f985.png?v8",ear:"unicode/1f442.png?v8",ear_of_rice:"unicode/1f33e.png?v8",ear_with_hearing_aid:"unicode/1f9bb.png?v8",earth_africa:"unicode/1f30d.png?v8",earth_americas:"unicode/1f30e.png?v8",earth_asia:"unicode/1f30f.png?v8",ecuador:"unicode/1f1ea-1f1e8.png?v8",egg:"unicode/1f95a.png?v8",eggplant:"unicode/1f346.png?v8",egypt:"unicode/1f1ea-1f1ec.png?v8",eight:"unicode/0038-20e3.png?v8",eight_pointed_black_star:"unicode/2734.png?v8",eight_spoked_asterisk:"unicode/2733.png?v8",eject_button:"unicode/23cf.png?v8",el_salvador:"unicode/1f1f8-1f1fb.png?v8",electric_plug:"unicode/1f50c.png?v8",electron:"electron.png?v8",elephant:"unicode/1f418.png?v8",elevator:"unicode/1f6d7.png?v8",elf:"unicode/1f9dd.png?v8",elf_man:"unicode/1f9dd-2642.png?v8",elf_woman:"unicode/1f9dd-2640.png?v8",email:"unicode/1f4e7.png?v8",end:"unicode/1f51a.png?v8",england:"unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png?v8",envelope:"unicode/2709.png?v8",envelope_with_arrow:"unicode/1f4e9.png?v8",equatorial_guinea:"unicode/1f1ec-1f1f6.png?v8",eritrea:"unicode/1f1ea-1f1f7.png?v8",es:"unicode/1f1ea-1f1f8.png?v8",estonia:"unicode/1f1ea-1f1ea.png?v8",ethiopia:"unicode/1f1ea-1f1f9.png?v8",eu:"unicode/1f1ea-1f1fa.png?v8",euro:"unicode/1f4b6.png?v8",european_castle:"unicode/1f3f0.png?v8",european_post_office:"unicode/1f3e4.png?v8",european_union:"unicode/1f1ea-1f1fa.png?v8",evergreen_tree:"unicode/1f332.png?v8",exclamation:"unicode/2757.png?v8",exploding_head:"unicode/1f92f.png?v8",expressionless:"unicode/1f611.png?v8",eye:"unicode/1f441.png?v8",eye_speech_bubble:"unicode/1f441-1f5e8.png?v8",eyeglasses:"unicode/1f453.png?v8",eyes:"unicode/1f440.png?v8",face_exhaling:"unicode/1f62e-1f4a8.png?v8",face_in_clouds:"unicode/1f636-1f32b.png?v8",face_with_head_bandage:"unicode/1f915.png?v8",face_with_spiral_eyes:"unicode/1f635-1f4ab.png?v8",face_with_thermometer:"unicode/1f912.png?v8",facepalm:"unicode/1f926.png?v8",facepunch:"unicode/1f44a.png?v8",factory:"unicode/1f3ed.png?v8",factory_worker:"unicode/1f9d1-1f3ed.png?v8",fairy:"unicode/1f9da.png?v8",fairy_man:"unicode/1f9da-2642.png?v8",fairy_woman:"unicode/1f9da-2640.png?v8",falafel:"unicode/1f9c6.png?v8",falkland_islands:"unicode/1f1eb-1f1f0.png?v8",fallen_leaf:"unicode/1f342.png?v8",family:"unicode/1f46a.png?v8",family_man_boy:"unicode/1f468-1f466.png?v8",family_man_boy_boy:"unicode/1f468-1f466-1f466.png?v8",family_man_girl:"unicode/1f468-1f467.png?v8",family_man_girl_boy:"unicode/1f468-1f467-1f466.png?v8",family_man_girl_girl:"unicode/1f468-1f467-1f467.png?v8",family_man_man_boy:"unicode/1f468-1f468-1f466.png?v8",family_man_man_boy_boy:"unicode/1f468-1f468-1f466-1f466.png?v8",family_man_man_girl:"unicode/1f468-1f468-1f467.png?v8",family_man_man_girl_boy:"unicode/1f468-1f468-1f467-1f466.png?v8",family_man_man_girl_girl:"unicode/1f468-1f468-1f467-1f467.png?v8",family_man_woman_boy:"unicode/1f468-1f469-1f466.png?v8",family_man_woman_boy_boy:"unicode/1f468-1f469-1f466-1f466.png?v8",family_man_woman_girl:"unicode/1f468-1f469-1f467.png?v8",family_man_woman_girl_boy:"unicode/1f468-1f469-1f467-1f466.png?v8",family_man_woman_girl_girl:"unicode/1f468-1f469-1f467-1f467.png?v8",family_woman_boy:"unicode/1f469-1f466.png?v8",family_woman_boy_boy:"unicode/1f469-1f466-1f466.png?v8",family_woman_girl:"unicode/1f469-1f467.png?v8",family_woman_girl_boy:"unicode/1f469-1f467-1f466.png?v8",family_woman_girl_girl:"unicode/1f469-1f467-1f467.png?v8",family_woman_woman_boy:"unicode/1f469-1f469-1f466.png?v8",family_woman_woman_boy_boy:"unicode/1f469-1f469-1f466-1f466.png?v8",family_woman_woman_girl:"unicode/1f469-1f469-1f467.png?v8",family_woman_woman_girl_boy:"unicode/1f469-1f469-1f467-1f466.png?v8",family_woman_woman_girl_girl:"unicode/1f469-1f469-1f467-1f467.png?v8",farmer:"unicode/1f9d1-1f33e.png?v8",faroe_islands:"unicode/1f1eb-1f1f4.png?v8",fast_forward:"unicode/23e9.png?v8",fax:"unicode/1f4e0.png?v8",fearful:"unicode/1f628.png?v8",feather:"unicode/1fab6.png?v8",feelsgood:"feelsgood.png?v8",feet:"unicode/1f43e.png?v8",female_detective:"unicode/1f575-2640.png?v8",female_sign:"unicode/2640.png?v8",ferris_wheel:"unicode/1f3a1.png?v8",ferry:"unicode/26f4.png?v8",field_hockey:"unicode/1f3d1.png?v8",fiji:"unicode/1f1eb-1f1ef.png?v8",file_cabinet:"unicode/1f5c4.png?v8",file_folder:"unicode/1f4c1.png?v8",film_projector:"unicode/1f4fd.png?v8",film_strip:"unicode/1f39e.png?v8",finland:"unicode/1f1eb-1f1ee.png?v8",finnadie:"finnadie.png?v8",fire:"unicode/1f525.png?v8",fire_engine:"unicode/1f692.png?v8",fire_extinguisher:"unicode/1f9ef.png?v8",firecracker:"unicode/1f9e8.png?v8",firefighter:"unicode/1f9d1-1f692.png?v8",fireworks:"unicode/1f386.png?v8",first_quarter_moon:"unicode/1f313.png?v8",first_quarter_moon_with_face:"unicode/1f31b.png?v8",fish:"unicode/1f41f.png?v8",fish_cake:"unicode/1f365.png?v8",fishing_pole_and_fish:"unicode/1f3a3.png?v8",fishsticks:"fishsticks.png?v8",fist:"unicode/270a.png?v8",fist_left:"unicode/1f91b.png?v8",fist_oncoming:"unicode/1f44a.png?v8",fist_raised:"unicode/270a.png?v8",fist_right:"unicode/1f91c.png?v8",five:"unicode/0035-20e3.png?v8",flags:"unicode/1f38f.png?v8",flamingo:"unicode/1f9a9.png?v8",flashlight:"unicode/1f526.png?v8",flat_shoe:"unicode/1f97f.png?v8",flatbread:"unicode/1fad3.png?v8",fleur_de_lis:"unicode/269c.png?v8",flight_arrival:"unicode/1f6ec.png?v8",flight_departure:"unicode/1f6eb.png?v8",flipper:"unicode/1f42c.png?v8",floppy_disk:"unicode/1f4be.png?v8",flower_playing_cards:"unicode/1f3b4.png?v8",flushed:"unicode/1f633.png?v8",fly:"unicode/1fab0.png?v8",flying_disc:"unicode/1f94f.png?v8",flying_saucer:"unicode/1f6f8.png?v8",fog:"unicode/1f32b.png?v8",foggy:"unicode/1f301.png?v8",fondue:"unicode/1fad5.png?v8",foot:"unicode/1f9b6.png?v8",football:"unicode/1f3c8.png?v8",footprints:"unicode/1f463.png?v8",fork_and_knife:"unicode/1f374.png?v8",fortune_cookie:"unicode/1f960.png?v8",fountain:"unicode/26f2.png?v8",fountain_pen:"unicode/1f58b.png?v8",four:"unicode/0034-20e3.png?v8",four_leaf_clover:"unicode/1f340.png?v8",fox_face:"unicode/1f98a.png?v8",fr:"unicode/1f1eb-1f1f7.png?v8",framed_picture:"unicode/1f5bc.png?v8",free:"unicode/1f193.png?v8",french_guiana:"unicode/1f1ec-1f1eb.png?v8",french_polynesia:"unicode/1f1f5-1f1eb.png?v8",french_southern_territories:"unicode/1f1f9-1f1eb.png?v8",fried_egg:"unicode/1f373.png?v8",fried_shrimp:"unicode/1f364.png?v8",fries:"unicode/1f35f.png?v8",frog:"unicode/1f438.png?v8",frowning:"unicode/1f626.png?v8",frowning_face:"unicode/2639.png?v8",frowning_man:"unicode/1f64d-2642.png?v8",frowning_person:"unicode/1f64d.png?v8",frowning_woman:"unicode/1f64d-2640.png?v8",fu:"unicode/1f595.png?v8",fuelpump:"unicode/26fd.png?v8",full_moon:"unicode/1f315.png?v8",full_moon_with_face:"unicode/1f31d.png?v8",funeral_urn:"unicode/26b1.png?v8",gabon:"unicode/1f1ec-1f1e6.png?v8",gambia:"unicode/1f1ec-1f1f2.png?v8",game_die:"unicode/1f3b2.png?v8",garlic:"unicode/1f9c4.png?v8",gb:"unicode/1f1ec-1f1e7.png?v8",gear:"unicode/2699.png?v8",gem:"unicode/1f48e.png?v8",gemini:"unicode/264a.png?v8",genie:"unicode/1f9de.png?v8",genie_man:"unicode/1f9de-2642.png?v8",genie_woman:"unicode/1f9de-2640.png?v8",georgia:"unicode/1f1ec-1f1ea.png?v8",ghana:"unicode/1f1ec-1f1ed.png?v8",ghost:"unicode/1f47b.png?v8",gibraltar:"unicode/1f1ec-1f1ee.png?v8",gift:"unicode/1f381.png?v8",gift_heart:"unicode/1f49d.png?v8",giraffe:"unicode/1f992.png?v8",girl:"unicode/1f467.png?v8",globe_with_meridians:"unicode/1f310.png?v8",gloves:"unicode/1f9e4.png?v8",goal_net:"unicode/1f945.png?v8",goat:"unicode/1f410.png?v8",goberserk:"goberserk.png?v8",godmode:"godmode.png?v8",goggles:"unicode/1f97d.png?v8",golf:"unicode/26f3.png?v8",golfing:"unicode/1f3cc.png?v8",golfing_man:"unicode/1f3cc-2642.png?v8",golfing_woman:"unicode/1f3cc-2640.png?v8",gorilla:"unicode/1f98d.png?v8",grapes:"unicode/1f347.png?v8",greece:"unicode/1f1ec-1f1f7.png?v8",green_apple:"unicode/1f34f.png?v8",green_book:"unicode/1f4d7.png?v8",green_circle:"unicode/1f7e2.png?v8",green_heart:"unicode/1f49a.png?v8",green_salad:"unicode/1f957.png?v8",green_square:"unicode/1f7e9.png?v8",greenland:"unicode/1f1ec-1f1f1.png?v8",grenada:"unicode/1f1ec-1f1e9.png?v8",grey_exclamation:"unicode/2755.png?v8",grey_question:"unicode/2754.png?v8",grimacing:"unicode/1f62c.png?v8",grin:"unicode/1f601.png?v8",grinning:"unicode/1f600.png?v8",guadeloupe:"unicode/1f1ec-1f1f5.png?v8",guam:"unicode/1f1ec-1f1fa.png?v8",guard:"unicode/1f482.png?v8",guardsman:"unicode/1f482-2642.png?v8",guardswoman:"unicode/1f482-2640.png?v8",guatemala:"unicode/1f1ec-1f1f9.png?v8",guernsey:"unicode/1f1ec-1f1ec.png?v8",guide_dog:"unicode/1f9ae.png?v8",guinea:"unicode/1f1ec-1f1f3.png?v8",guinea_bissau:"unicode/1f1ec-1f1fc.png?v8",guitar:"unicode/1f3b8.png?v8",gun:"unicode/1f52b.png?v8",guyana:"unicode/1f1ec-1f1fe.png?v8",haircut:"unicode/1f487.png?v8",haircut_man:"unicode/1f487-2642.png?v8",haircut_woman:"unicode/1f487-2640.png?v8",haiti:"unicode/1f1ed-1f1f9.png?v8",hamburger:"unicode/1f354.png?v8",hammer:"unicode/1f528.png?v8",hammer_and_pick:"unicode/2692.png?v8",hammer_and_wrench:"unicode/1f6e0.png?v8",hamster:"unicode/1f439.png?v8",hand:"unicode/270b.png?v8",hand_over_mouth:"unicode/1f92d.png?v8",handbag:"unicode/1f45c.png?v8",handball_person:"unicode/1f93e.png?v8",handshake:"unicode/1f91d.png?v8",hankey:"unicode/1f4a9.png?v8",hash:"unicode/0023-20e3.png?v8",hatched_chick:"unicode/1f425.png?v8",hatching_chick:"unicode/1f423.png?v8",headphones:"unicode/1f3a7.png?v8",headstone:"unicode/1faa6.png?v8",health_worker:"unicode/1f9d1-2695.png?v8",hear_no_evil:"unicode/1f649.png?v8",heard_mcdonald_islands:"unicode/1f1ed-1f1f2.png?v8",heart:"unicode/2764.png?v8",heart_decoration:"unicode/1f49f.png?v8",heart_eyes:"unicode/1f60d.png?v8",heart_eyes_cat:"unicode/1f63b.png?v8",heart_on_fire:"unicode/2764-1f525.png?v8",heartbeat:"unicode/1f493.png?v8",heartpulse:"unicode/1f497.png?v8",hearts:"unicode/2665.png?v8",heavy_check_mark:"unicode/2714.png?v8",heavy_division_sign:"unicode/2797.png?v8",heavy_dollar_sign:"unicode/1f4b2.png?v8",heavy_exclamation_mark:"unicode/2757.png?v8",heavy_heart_exclamation:"unicode/2763.png?v8",heavy_minus_sign:"unicode/2796.png?v8",heavy_multiplication_x:"unicode/2716.png?v8",heavy_plus_sign:"unicode/2795.png?v8",hedgehog:"unicode/1f994.png?v8",helicopter:"unicode/1f681.png?v8",herb:"unicode/1f33f.png?v8",hibiscus:"unicode/1f33a.png?v8",high_brightness:"unicode/1f506.png?v8",high_heel:"unicode/1f460.png?v8",hiking_boot:"unicode/1f97e.png?v8",hindu_temple:"unicode/1f6d5.png?v8",hippopotamus:"unicode/1f99b.png?v8",hocho:"unicode/1f52a.png?v8",hole:"unicode/1f573.png?v8",honduras:"unicode/1f1ed-1f1f3.png?v8",honey_pot:"unicode/1f36f.png?v8",honeybee:"unicode/1f41d.png?v8",hong_kong:"unicode/1f1ed-1f1f0.png?v8",hook:"unicode/1fa9d.png?v8",horse:"unicode/1f434.png?v8",horse_racing:"unicode/1f3c7.png?v8",hospital:"unicode/1f3e5.png?v8",hot_face:"unicode/1f975.png?v8",hot_pepper:"unicode/1f336.png?v8",hotdog:"unicode/1f32d.png?v8",hotel:"unicode/1f3e8.png?v8",hotsprings:"unicode/2668.png?v8",hourglass:"unicode/231b.png?v8",hourglass_flowing_sand:"unicode/23f3.png?v8",house:"unicode/1f3e0.png?v8",house_with_garden:"unicode/1f3e1.png?v8",houses:"unicode/1f3d8.png?v8",hugs:"unicode/1f917.png?v8",hungary:"unicode/1f1ed-1f1fa.png?v8",hurtrealbad:"hurtrealbad.png?v8",hushed:"unicode/1f62f.png?v8",hut:"unicode/1f6d6.png?v8",ice_cream:"unicode/1f368.png?v8",ice_cube:"unicode/1f9ca.png?v8",ice_hockey:"unicode/1f3d2.png?v8",ice_skate:"unicode/26f8.png?v8",icecream:"unicode/1f366.png?v8",iceland:"unicode/1f1ee-1f1f8.png?v8",id:"unicode/1f194.png?v8",ideograph_advantage:"unicode/1f250.png?v8",imp:"unicode/1f47f.png?v8",inbox_tray:"unicode/1f4e5.png?v8",incoming_envelope:"unicode/1f4e8.png?v8",india:"unicode/1f1ee-1f1f3.png?v8",indonesia:"unicode/1f1ee-1f1e9.png?v8",infinity:"unicode/267e.png?v8",information_desk_person:"unicode/1f481.png?v8",information_source:"unicode/2139.png?v8",innocent:"unicode/1f607.png?v8",interrobang:"unicode/2049.png?v8",iphone:"unicode/1f4f1.png?v8",iran:"unicode/1f1ee-1f1f7.png?v8",iraq:"unicode/1f1ee-1f1f6.png?v8",ireland:"unicode/1f1ee-1f1ea.png?v8",isle_of_man:"unicode/1f1ee-1f1f2.png?v8",israel:"unicode/1f1ee-1f1f1.png?v8",it:"unicode/1f1ee-1f1f9.png?v8",izakaya_lantern:"unicode/1f3ee.png?v8",jack_o_lantern:"unicode/1f383.png?v8",jamaica:"unicode/1f1ef-1f1f2.png?v8",japan:"unicode/1f5fe.png?v8",japanese_castle:"unicode/1f3ef.png?v8",japanese_goblin:"unicode/1f47a.png?v8",japanese_ogre:"unicode/1f479.png?v8",jeans:"unicode/1f456.png?v8",jersey:"unicode/1f1ef-1f1ea.png?v8",jigsaw:"unicode/1f9e9.png?v8",jordan:"unicode/1f1ef-1f1f4.png?v8",joy:"unicode/1f602.png?v8",joy_cat:"unicode/1f639.png?v8",joystick:"unicode/1f579.png?v8",jp:"unicode/1f1ef-1f1f5.png?v8",judge:"unicode/1f9d1-2696.png?v8",juggling_person:"unicode/1f939.png?v8",kaaba:"unicode/1f54b.png?v8",kangaroo:"unicode/1f998.png?v8",kazakhstan:"unicode/1f1f0-1f1ff.png?v8",kenya:"unicode/1f1f0-1f1ea.png?v8",key:"unicode/1f511.png?v8",keyboard:"unicode/2328.png?v8",keycap_ten:"unicode/1f51f.png?v8",kick_scooter:"unicode/1f6f4.png?v8",kimono:"unicode/1f458.png?v8",kiribati:"unicode/1f1f0-1f1ee.png?v8",kiss:"unicode/1f48b.png?v8",kissing:"unicode/1f617.png?v8",kissing_cat:"unicode/1f63d.png?v8",kissing_closed_eyes:"unicode/1f61a.png?v8",kissing_heart:"unicode/1f618.png?v8",kissing_smiling_eyes:"unicode/1f619.png?v8",kite:"unicode/1fa81.png?v8",kiwi_fruit:"unicode/1f95d.png?v8",kneeling_man:"unicode/1f9ce-2642.png?v8",kneeling_person:"unicode/1f9ce.png?v8",kneeling_woman:"unicode/1f9ce-2640.png?v8",knife:"unicode/1f52a.png?v8",knot:"unicode/1faa2.png?v8",koala:"unicode/1f428.png?v8",koko:"unicode/1f201.png?v8",kosovo:"unicode/1f1fd-1f1f0.png?v8",kr:"unicode/1f1f0-1f1f7.png?v8",kuwait:"unicode/1f1f0-1f1fc.png?v8",kyrgyzstan:"unicode/1f1f0-1f1ec.png?v8",lab_coat:"unicode/1f97c.png?v8",label:"unicode/1f3f7.png?v8",lacrosse:"unicode/1f94d.png?v8",ladder:"unicode/1fa9c.png?v8",lady_beetle:"unicode/1f41e.png?v8",lantern:"unicode/1f3ee.png?v8",laos:"unicode/1f1f1-1f1e6.png?v8",large_blue_circle:"unicode/1f535.png?v8",large_blue_diamond:"unicode/1f537.png?v8",large_orange_diamond:"unicode/1f536.png?v8",last_quarter_moon:"unicode/1f317.png?v8",last_quarter_moon_with_face:"unicode/1f31c.png?v8",latin_cross:"unicode/271d.png?v8",latvia:"unicode/1f1f1-1f1fb.png?v8",laughing:"unicode/1f606.png?v8",leafy_green:"unicode/1f96c.png?v8",leaves:"unicode/1f343.png?v8",lebanon:"unicode/1f1f1-1f1e7.png?v8",ledger:"unicode/1f4d2.png?v8",left_luggage:"unicode/1f6c5.png?v8",left_right_arrow:"unicode/2194.png?v8",left_speech_bubble:"unicode/1f5e8.png?v8",leftwards_arrow_with_hook:"unicode/21a9.png?v8",leg:"unicode/1f9b5.png?v8",lemon:"unicode/1f34b.png?v8",leo:"unicode/264c.png?v8",leopard:"unicode/1f406.png?v8",lesotho:"unicode/1f1f1-1f1f8.png?v8",level_slider:"unicode/1f39a.png?v8",liberia:"unicode/1f1f1-1f1f7.png?v8",libra:"unicode/264e.png?v8",libya:"unicode/1f1f1-1f1fe.png?v8",liechtenstein:"unicode/1f1f1-1f1ee.png?v8",light_rail:"unicode/1f688.png?v8",link:"unicode/1f517.png?v8",lion:"unicode/1f981.png?v8",lips:"unicode/1f444.png?v8",lipstick:"unicode/1f484.png?v8",lithuania:"unicode/1f1f1-1f1f9.png?v8",lizard:"unicode/1f98e.png?v8",llama:"unicode/1f999.png?v8",lobster:"unicode/1f99e.png?v8",lock:"unicode/1f512.png?v8",lock_with_ink_pen:"unicode/1f50f.png?v8",lollipop:"unicode/1f36d.png?v8",long_drum:"unicode/1fa98.png?v8",loop:"unicode/27bf.png?v8",lotion_bottle:"unicode/1f9f4.png?v8",lotus_position:"unicode/1f9d8.png?v8",lotus_position_man:"unicode/1f9d8-2642.png?v8",lotus_position_woman:"unicode/1f9d8-2640.png?v8",loud_sound:"unicode/1f50a.png?v8",loudspeaker:"unicode/1f4e2.png?v8",love_hotel:"unicode/1f3e9.png?v8",love_letter:"unicode/1f48c.png?v8",love_you_gesture:"unicode/1f91f.png?v8",low_brightness:"unicode/1f505.png?v8",luggage:"unicode/1f9f3.png?v8",lungs:"unicode/1fac1.png?v8",luxembourg:"unicode/1f1f1-1f1fa.png?v8",lying_face:"unicode/1f925.png?v8",m:"unicode/24c2.png?v8",macau:"unicode/1f1f2-1f1f4.png?v8",macedonia:"unicode/1f1f2-1f1f0.png?v8",madagascar:"unicode/1f1f2-1f1ec.png?v8",mag:"unicode/1f50d.png?v8",mag_right:"unicode/1f50e.png?v8",mage:"unicode/1f9d9.png?v8",mage_man:"unicode/1f9d9-2642.png?v8",mage_woman:"unicode/1f9d9-2640.png?v8",magic_wand:"unicode/1fa84.png?v8",magnet:"unicode/1f9f2.png?v8",mahjong:"unicode/1f004.png?v8",mailbox:"unicode/1f4eb.png?v8",mailbox_closed:"unicode/1f4ea.png?v8",mailbox_with_mail:"unicode/1f4ec.png?v8",mailbox_with_no_mail:"unicode/1f4ed.png?v8",malawi:"unicode/1f1f2-1f1fc.png?v8",malaysia:"unicode/1f1f2-1f1fe.png?v8",maldives:"unicode/1f1f2-1f1fb.png?v8",male_detective:"unicode/1f575-2642.png?v8",male_sign:"unicode/2642.png?v8",mali:"unicode/1f1f2-1f1f1.png?v8",malta:"unicode/1f1f2-1f1f9.png?v8",mammoth:"unicode/1f9a3.png?v8",man:"unicode/1f468.png?v8",man_artist:"unicode/1f468-1f3a8.png?v8",man_astronaut:"unicode/1f468-1f680.png?v8",man_beard:"unicode/1f9d4-2642.png?v8",man_cartwheeling:"unicode/1f938-2642.png?v8",man_cook:"unicode/1f468-1f373.png?v8",man_dancing:"unicode/1f57a.png?v8",man_facepalming:"unicode/1f926-2642.png?v8",man_factory_worker:"unicode/1f468-1f3ed.png?v8",man_farmer:"unicode/1f468-1f33e.png?v8",man_feeding_baby:"unicode/1f468-1f37c.png?v8",man_firefighter:"unicode/1f468-1f692.png?v8",man_health_worker:"unicode/1f468-2695.png?v8",man_in_manual_wheelchair:"unicode/1f468-1f9bd.png?v8",man_in_motorized_wheelchair:"unicode/1f468-1f9bc.png?v8",man_in_tuxedo:"unicode/1f935-2642.png?v8",man_judge:"unicode/1f468-2696.png?v8",man_juggling:"unicode/1f939-2642.png?v8",man_mechanic:"unicode/1f468-1f527.png?v8",man_office_worker:"unicode/1f468-1f4bc.png?v8",man_pilot:"unicode/1f468-2708.png?v8",man_playing_handball:"unicode/1f93e-2642.png?v8",man_playing_water_polo:"unicode/1f93d-2642.png?v8",man_scientist:"unicode/1f468-1f52c.png?v8",man_shrugging:"unicode/1f937-2642.png?v8",man_singer:"unicode/1f468-1f3a4.png?v8",man_student:"unicode/1f468-1f393.png?v8",man_teacher:"unicode/1f468-1f3eb.png?v8",man_technologist:"unicode/1f468-1f4bb.png?v8",man_with_gua_pi_mao:"unicode/1f472.png?v8",man_with_probing_cane:"unicode/1f468-1f9af.png?v8",man_with_turban:"unicode/1f473-2642.png?v8",man_with_veil:"unicode/1f470-2642.png?v8",mandarin:"unicode/1f34a.png?v8",mango:"unicode/1f96d.png?v8",mans_shoe:"unicode/1f45e.png?v8",mantelpiece_clock:"unicode/1f570.png?v8",manual_wheelchair:"unicode/1f9bd.png?v8",maple_leaf:"unicode/1f341.png?v8",marshall_islands:"unicode/1f1f2-1f1ed.png?v8",martial_arts_uniform:"unicode/1f94b.png?v8",martinique:"unicode/1f1f2-1f1f6.png?v8",mask:"unicode/1f637.png?v8",massage:"unicode/1f486.png?v8",massage_man:"unicode/1f486-2642.png?v8",massage_woman:"unicode/1f486-2640.png?v8",mate:"unicode/1f9c9.png?v8",mauritania:"unicode/1f1f2-1f1f7.png?v8",mauritius:"unicode/1f1f2-1f1fa.png?v8",mayotte:"unicode/1f1fe-1f1f9.png?v8",meat_on_bone:"unicode/1f356.png?v8",mechanic:"unicode/1f9d1-1f527.png?v8",mechanical_arm:"unicode/1f9be.png?v8",mechanical_leg:"unicode/1f9bf.png?v8",medal_military:"unicode/1f396.png?v8",medal_sports:"unicode/1f3c5.png?v8",medical_symbol:"unicode/2695.png?v8",mega:"unicode/1f4e3.png?v8",melon:"unicode/1f348.png?v8",memo:"unicode/1f4dd.png?v8",men_wrestling:"unicode/1f93c-2642.png?v8",mending_heart:"unicode/2764-1fa79.png?v8",menorah:"unicode/1f54e.png?v8",mens:"unicode/1f6b9.png?v8",mermaid:"unicode/1f9dc-2640.png?v8",merman:"unicode/1f9dc-2642.png?v8",merperson:"unicode/1f9dc.png?v8",metal:"unicode/1f918.png?v8",metro:"unicode/1f687.png?v8",mexico:"unicode/1f1f2-1f1fd.png?v8",microbe:"unicode/1f9a0.png?v8",micronesia:"unicode/1f1eb-1f1f2.png?v8",microphone:"unicode/1f3a4.png?v8",microscope:"unicode/1f52c.png?v8",middle_finger:"unicode/1f595.png?v8",military_helmet:"unicode/1fa96.png?v8",milk_glass:"unicode/1f95b.png?v8",milky_way:"unicode/1f30c.png?v8",minibus:"unicode/1f690.png?v8",minidisc:"unicode/1f4bd.png?v8",mirror:"unicode/1fa9e.png?v8",mobile_phone_off:"unicode/1f4f4.png?v8",moldova:"unicode/1f1f2-1f1e9.png?v8",monaco:"unicode/1f1f2-1f1e8.png?v8",money_mouth_face:"unicode/1f911.png?v8",money_with_wings:"unicode/1f4b8.png?v8",moneybag:"unicode/1f4b0.png?v8",mongolia:"unicode/1f1f2-1f1f3.png?v8",monkey:"unicode/1f412.png?v8",monkey_face:"unicode/1f435.png?v8",monocle_face:"unicode/1f9d0.png?v8",monorail:"unicode/1f69d.png?v8",montenegro:"unicode/1f1f2-1f1ea.png?v8",montserrat:"unicode/1f1f2-1f1f8.png?v8",moon:"unicode/1f314.png?v8",moon_cake:"unicode/1f96e.png?v8",morocco:"unicode/1f1f2-1f1e6.png?v8",mortar_board:"unicode/1f393.png?v8",mosque:"unicode/1f54c.png?v8",mosquito:"unicode/1f99f.png?v8",motor_boat:"unicode/1f6e5.png?v8",motor_scooter:"unicode/1f6f5.png?v8",motorcycle:"unicode/1f3cd.png?v8",motorized_wheelchair:"unicode/1f9bc.png?v8",motorway:"unicode/1f6e3.png?v8",mount_fuji:"unicode/1f5fb.png?v8",mountain:"unicode/26f0.png?v8",mountain_bicyclist:"unicode/1f6b5.png?v8",mountain_biking_man:"unicode/1f6b5-2642.png?v8",mountain_biking_woman:"unicode/1f6b5-2640.png?v8",mountain_cableway:"unicode/1f6a0.png?v8",mountain_railway:"unicode/1f69e.png?v8",mountain_snow:"unicode/1f3d4.png?v8",mouse:"unicode/1f42d.png?v8",mouse2:"unicode/1f401.png?v8",mouse_trap:"unicode/1faa4.png?v8",movie_camera:"unicode/1f3a5.png?v8",moyai:"unicode/1f5ff.png?v8",mozambique:"unicode/1f1f2-1f1ff.png?v8",mrs_claus:"unicode/1f936.png?v8",muscle:"unicode/1f4aa.png?v8",mushroom:"unicode/1f344.png?v8",musical_keyboard:"unicode/1f3b9.png?v8",musical_note:"unicode/1f3b5.png?v8",musical_score:"unicode/1f3bc.png?v8",mute:"unicode/1f507.png?v8",mx_claus:"unicode/1f9d1-1f384.png?v8",myanmar:"unicode/1f1f2-1f1f2.png?v8",nail_care:"unicode/1f485.png?v8",name_badge:"unicode/1f4db.png?v8",namibia:"unicode/1f1f3-1f1e6.png?v8",national_park:"unicode/1f3de.png?v8",nauru:"unicode/1f1f3-1f1f7.png?v8",nauseated_face:"unicode/1f922.png?v8",nazar_amulet:"unicode/1f9ff.png?v8",neckbeard:"neckbeard.png?v8",necktie:"unicode/1f454.png?v8",negative_squared_cross_mark:"unicode/274e.png?v8",nepal:"unicode/1f1f3-1f1f5.png?v8",nerd_face:"unicode/1f913.png?v8",nesting_dolls:"unicode/1fa86.png?v8",netherlands:"unicode/1f1f3-1f1f1.png?v8",neutral_face:"unicode/1f610.png?v8",new:"unicode/1f195.png?v8",new_caledonia:"unicode/1f1f3-1f1e8.png?v8",new_moon:"unicode/1f311.png?v8",new_moon_with_face:"unicode/1f31a.png?v8",new_zealand:"unicode/1f1f3-1f1ff.png?v8",newspaper:"unicode/1f4f0.png?v8",newspaper_roll:"unicode/1f5de.png?v8",next_track_button:"unicode/23ed.png?v8",ng:"unicode/1f196.png?v8",ng_man:"unicode/1f645-2642.png?v8",ng_woman:"unicode/1f645-2640.png?v8",nicaragua:"unicode/1f1f3-1f1ee.png?v8",niger:"unicode/1f1f3-1f1ea.png?v8",nigeria:"unicode/1f1f3-1f1ec.png?v8",night_with_stars:"unicode/1f303.png?v8",nine:"unicode/0039-20e3.png?v8",ninja:"unicode/1f977.png?v8",niue:"unicode/1f1f3-1f1fa.png?v8",no_bell:"unicode/1f515.png?v8",no_bicycles:"unicode/1f6b3.png?v8",no_entry:"unicode/26d4.png?v8",no_entry_sign:"unicode/1f6ab.png?v8",no_good:"unicode/1f645.png?v8",no_good_man:"unicode/1f645-2642.png?v8",no_good_woman:"unicode/1f645-2640.png?v8",no_mobile_phones:"unicode/1f4f5.png?v8",no_mouth:"unicode/1f636.png?v8",no_pedestrians:"unicode/1f6b7.png?v8",no_smoking:"unicode/1f6ad.png?v8","non-potable_water":"unicode/1f6b1.png?v8",norfolk_island:"unicode/1f1f3-1f1eb.png?v8",north_korea:"unicode/1f1f0-1f1f5.png?v8",northern_mariana_islands:"unicode/1f1f2-1f1f5.png?v8",norway:"unicode/1f1f3-1f1f4.png?v8",nose:"unicode/1f443.png?v8",notebook:"unicode/1f4d3.png?v8",notebook_with_decorative_cover:"unicode/1f4d4.png?v8",notes:"unicode/1f3b6.png?v8",nut_and_bolt:"unicode/1f529.png?v8",o:"unicode/2b55.png?v8",o2:"unicode/1f17e.png?v8",ocean:"unicode/1f30a.png?v8",octocat:"octocat.png?v8",octopus:"unicode/1f419.png?v8",oden:"unicode/1f362.png?v8",office:"unicode/1f3e2.png?v8",office_worker:"unicode/1f9d1-1f4bc.png?v8",oil_drum:"unicode/1f6e2.png?v8",ok:"unicode/1f197.png?v8",ok_hand:"unicode/1f44c.png?v8",ok_man:"unicode/1f646-2642.png?v8",ok_person:"unicode/1f646.png?v8",ok_woman:"unicode/1f646-2640.png?v8",old_key:"unicode/1f5dd.png?v8",older_adult:"unicode/1f9d3.png?v8",older_man:"unicode/1f474.png?v8",older_woman:"unicode/1f475.png?v8",olive:"unicode/1fad2.png?v8",om:"unicode/1f549.png?v8",oman:"unicode/1f1f4-1f1f2.png?v8",on:"unicode/1f51b.png?v8",oncoming_automobile:"unicode/1f698.png?v8",oncoming_bus:"unicode/1f68d.png?v8",oncoming_police_car:"unicode/1f694.png?v8",oncoming_taxi:"unicode/1f696.png?v8",one:"unicode/0031-20e3.png?v8",one_piece_swimsuit:"unicode/1fa71.png?v8",onion:"unicode/1f9c5.png?v8",open_book:"unicode/1f4d6.png?v8",open_file_folder:"unicode/1f4c2.png?v8",open_hands:"unicode/1f450.png?v8",open_mouth:"unicode/1f62e.png?v8",open_umbrella:"unicode/2602.png?v8",ophiuchus:"unicode/26ce.png?v8",orange:"unicode/1f34a.png?v8",orange_book:"unicode/1f4d9.png?v8",orange_circle:"unicode/1f7e0.png?v8",orange_heart:"unicode/1f9e1.png?v8",orange_square:"unicode/1f7e7.png?v8",orangutan:"unicode/1f9a7.png?v8",orthodox_cross:"unicode/2626.png?v8",otter:"unicode/1f9a6.png?v8",outbox_tray:"unicode/1f4e4.png?v8",owl:"unicode/1f989.png?v8",ox:"unicode/1f402.png?v8",oyster:"unicode/1f9aa.png?v8",package:"unicode/1f4e6.png?v8",page_facing_up:"unicode/1f4c4.png?v8",page_with_curl:"unicode/1f4c3.png?v8",pager:"unicode/1f4df.png?v8",paintbrush:"unicode/1f58c.png?v8",pakistan:"unicode/1f1f5-1f1f0.png?v8",palau:"unicode/1f1f5-1f1fc.png?v8",palestinian_territories:"unicode/1f1f5-1f1f8.png?v8",palm_tree:"unicode/1f334.png?v8",palms_up_together:"unicode/1f932.png?v8",panama:"unicode/1f1f5-1f1e6.png?v8",pancakes:"unicode/1f95e.png?v8",panda_face:"unicode/1f43c.png?v8",paperclip:"unicode/1f4ce.png?v8",paperclips:"unicode/1f587.png?v8",papua_new_guinea:"unicode/1f1f5-1f1ec.png?v8",parachute:"unicode/1fa82.png?v8",paraguay:"unicode/1f1f5-1f1fe.png?v8",parasol_on_ground:"unicode/26f1.png?v8",parking:"unicode/1f17f.png?v8",parrot:"unicode/1f99c.png?v8",part_alternation_mark:"unicode/303d.png?v8",partly_sunny:"unicode/26c5.png?v8",partying_face:"unicode/1f973.png?v8",passenger_ship:"unicode/1f6f3.png?v8",passport_control:"unicode/1f6c2.png?v8",pause_button:"unicode/23f8.png?v8",paw_prints:"unicode/1f43e.png?v8",peace_symbol:"unicode/262e.png?v8",peach:"unicode/1f351.png?v8",peacock:"unicode/1f99a.png?v8",peanuts:"unicode/1f95c.png?v8",pear:"unicode/1f350.png?v8",pen:"unicode/1f58a.png?v8",pencil:"unicode/1f4dd.png?v8",pencil2:"unicode/270f.png?v8",penguin:"unicode/1f427.png?v8",pensive:"unicode/1f614.png?v8",people_holding_hands:"unicode/1f9d1-1f91d-1f9d1.png?v8",people_hugging:"unicode/1fac2.png?v8",performing_arts:"unicode/1f3ad.png?v8",persevere:"unicode/1f623.png?v8",person_bald:"unicode/1f9d1-1f9b2.png?v8",person_curly_hair:"unicode/1f9d1-1f9b1.png?v8",person_feeding_baby:"unicode/1f9d1-1f37c.png?v8",person_fencing:"unicode/1f93a.png?v8",person_in_manual_wheelchair:"unicode/1f9d1-1f9bd.png?v8",person_in_motorized_wheelchair:"unicode/1f9d1-1f9bc.png?v8",person_in_tuxedo:"unicode/1f935.png?v8",person_red_hair:"unicode/1f9d1-1f9b0.png?v8",person_white_hair:"unicode/1f9d1-1f9b3.png?v8",person_with_probing_cane:"unicode/1f9d1-1f9af.png?v8",person_with_turban:"unicode/1f473.png?v8",person_with_veil:"unicode/1f470.png?v8",peru:"unicode/1f1f5-1f1ea.png?v8",petri_dish:"unicode/1f9eb.png?v8",philippines:"unicode/1f1f5-1f1ed.png?v8",phone:"unicode/260e.png?v8",pick:"unicode/26cf.png?v8",pickup_truck:"unicode/1f6fb.png?v8",pie:"unicode/1f967.png?v8",pig:"unicode/1f437.png?v8",pig2:"unicode/1f416.png?v8",pig_nose:"unicode/1f43d.png?v8",pill:"unicode/1f48a.png?v8",pilot:"unicode/1f9d1-2708.png?v8",pinata:"unicode/1fa85.png?v8",pinched_fingers:"unicode/1f90c.png?v8",pinching_hand:"unicode/1f90f.png?v8",pineapple:"unicode/1f34d.png?v8",ping_pong:"unicode/1f3d3.png?v8",pirate_flag:"unicode/1f3f4-2620.png?v8",pisces:"unicode/2653.png?v8",pitcairn_islands:"unicode/1f1f5-1f1f3.png?v8",pizza:"unicode/1f355.png?v8",placard:"unicode/1faa7.png?v8",place_of_worship:"unicode/1f6d0.png?v8",plate_with_cutlery:"unicode/1f37d.png?v8",play_or_pause_button:"unicode/23ef.png?v8",pleading_face:"unicode/1f97a.png?v8",plunger:"unicode/1faa0.png?v8",point_down:"unicode/1f447.png?v8",point_left:"unicode/1f448.png?v8",point_right:"unicode/1f449.png?v8",point_up:"unicode/261d.png?v8",point_up_2:"unicode/1f446.png?v8",poland:"unicode/1f1f5-1f1f1.png?v8",polar_bear:"unicode/1f43b-2744.png?v8",police_car:"unicode/1f693.png?v8",police_officer:"unicode/1f46e.png?v8",policeman:"unicode/1f46e-2642.png?v8",policewoman:"unicode/1f46e-2640.png?v8",poodle:"unicode/1f429.png?v8",poop:"unicode/1f4a9.png?v8",popcorn:"unicode/1f37f.png?v8",portugal:"unicode/1f1f5-1f1f9.png?v8",post_office:"unicode/1f3e3.png?v8",postal_horn:"unicode/1f4ef.png?v8",postbox:"unicode/1f4ee.png?v8",potable_water:"unicode/1f6b0.png?v8",potato:"unicode/1f954.png?v8",potted_plant:"unicode/1fab4.png?v8",pouch:"unicode/1f45d.png?v8",poultry_leg:"unicode/1f357.png?v8",pound:"unicode/1f4b7.png?v8",pout:"unicode/1f621.png?v8",pouting_cat:"unicode/1f63e.png?v8",pouting_face:"unicode/1f64e.png?v8",pouting_man:"unicode/1f64e-2642.png?v8",pouting_woman:"unicode/1f64e-2640.png?v8",pray:"unicode/1f64f.png?v8",prayer_beads:"unicode/1f4ff.png?v8",pregnant_woman:"unicode/1f930.png?v8",pretzel:"unicode/1f968.png?v8",previous_track_button:"unicode/23ee.png?v8",prince:"unicode/1f934.png?v8",princess:"unicode/1f478.png?v8",printer:"unicode/1f5a8.png?v8",probing_cane:"unicode/1f9af.png?v8",puerto_rico:"unicode/1f1f5-1f1f7.png?v8",punch:"unicode/1f44a.png?v8",purple_circle:"unicode/1f7e3.png?v8",purple_heart:"unicode/1f49c.png?v8",purple_square:"unicode/1f7ea.png?v8",purse:"unicode/1f45b.png?v8",pushpin:"unicode/1f4cc.png?v8",put_litter_in_its_place:"unicode/1f6ae.png?v8",qatar:"unicode/1f1f6-1f1e6.png?v8",question:"unicode/2753.png?v8",rabbit:"unicode/1f430.png?v8",rabbit2:"unicode/1f407.png?v8",raccoon:"unicode/1f99d.png?v8",racehorse:"unicode/1f40e.png?v8",racing_car:"unicode/1f3ce.png?v8",radio:"unicode/1f4fb.png?v8",radio_button:"unicode/1f518.png?v8",radioactive:"unicode/2622.png?v8",rage:"unicode/1f621.png?v8",rage1:"rage1.png?v8",rage2:"rage2.png?v8",rage3:"rage3.png?v8",rage4:"rage4.png?v8",railway_car:"unicode/1f683.png?v8",railway_track:"unicode/1f6e4.png?v8",rainbow:"unicode/1f308.png?v8",rainbow_flag:"unicode/1f3f3-1f308.png?v8",raised_back_of_hand:"unicode/1f91a.png?v8",raised_eyebrow:"unicode/1f928.png?v8",raised_hand:"unicode/270b.png?v8",raised_hand_with_fingers_splayed:"unicode/1f590.png?v8",raised_hands:"unicode/1f64c.png?v8",raising_hand:"unicode/1f64b.png?v8",raising_hand_man:"unicode/1f64b-2642.png?v8",raising_hand_woman:"unicode/1f64b-2640.png?v8",ram:"unicode/1f40f.png?v8",ramen:"unicode/1f35c.png?v8",rat:"unicode/1f400.png?v8",razor:"unicode/1fa92.png?v8",receipt:"unicode/1f9fe.png?v8",record_button:"unicode/23fa.png?v8",recycle:"unicode/267b.png?v8",red_car:"unicode/1f697.png?v8",red_circle:"unicode/1f534.png?v8",red_envelope:"unicode/1f9e7.png?v8",red_haired_man:"unicode/1f468-1f9b0.png?v8",red_haired_woman:"unicode/1f469-1f9b0.png?v8",red_square:"unicode/1f7e5.png?v8",registered:"unicode/00ae.png?v8",relaxed:"unicode/263a.png?v8",relieved:"unicode/1f60c.png?v8",reminder_ribbon:"unicode/1f397.png?v8",repeat:"unicode/1f501.png?v8",repeat_one:"unicode/1f502.png?v8",rescue_worker_helmet:"unicode/26d1.png?v8",restroom:"unicode/1f6bb.png?v8",reunion:"unicode/1f1f7-1f1ea.png?v8",revolving_hearts:"unicode/1f49e.png?v8",rewind:"unicode/23ea.png?v8",rhinoceros:"unicode/1f98f.png?v8",ribbon:"unicode/1f380.png?v8",rice:"unicode/1f35a.png?v8",rice_ball:"unicode/1f359.png?v8",rice_cracker:"unicode/1f358.png?v8",rice_scene:"unicode/1f391.png?v8",right_anger_bubble:"unicode/1f5ef.png?v8",ring:"unicode/1f48d.png?v8",ringed_planet:"unicode/1fa90.png?v8",robot:"unicode/1f916.png?v8",rock:"unicode/1faa8.png?v8",rocket:"unicode/1f680.png?v8",rofl:"unicode/1f923.png?v8",roll_eyes:"unicode/1f644.png?v8",roll_of_paper:"unicode/1f9fb.png?v8",roller_coaster:"unicode/1f3a2.png?v8",roller_skate:"unicode/1f6fc.png?v8",romania:"unicode/1f1f7-1f1f4.png?v8",rooster:"unicode/1f413.png?v8",rose:"unicode/1f339.png?v8",rosette:"unicode/1f3f5.png?v8",rotating_light:"unicode/1f6a8.png?v8",round_pushpin:"unicode/1f4cd.png?v8",rowboat:"unicode/1f6a3.png?v8",rowing_man:"unicode/1f6a3-2642.png?v8",rowing_woman:"unicode/1f6a3-2640.png?v8",ru:"unicode/1f1f7-1f1fa.png?v8",rugby_football:"unicode/1f3c9.png?v8",runner:"unicode/1f3c3.png?v8",running:"unicode/1f3c3.png?v8",running_man:"unicode/1f3c3-2642.png?v8",running_shirt_with_sash:"unicode/1f3bd.png?v8",running_woman:"unicode/1f3c3-2640.png?v8",rwanda:"unicode/1f1f7-1f1fc.png?v8",sa:"unicode/1f202.png?v8",safety_pin:"unicode/1f9f7.png?v8",safety_vest:"unicode/1f9ba.png?v8",sagittarius:"unicode/2650.png?v8",sailboat:"unicode/26f5.png?v8",sake:"unicode/1f376.png?v8",salt:"unicode/1f9c2.png?v8",samoa:"unicode/1f1fc-1f1f8.png?v8",san_marino:"unicode/1f1f8-1f1f2.png?v8",sandal:"unicode/1f461.png?v8",sandwich:"unicode/1f96a.png?v8",santa:"unicode/1f385.png?v8",sao_tome_principe:"unicode/1f1f8-1f1f9.png?v8",sari:"unicode/1f97b.png?v8",sassy_man:"unicode/1f481-2642.png?v8",sassy_woman:"unicode/1f481-2640.png?v8",satellite:"unicode/1f4e1.png?v8",satisfied:"unicode/1f606.png?v8",saudi_arabia:"unicode/1f1f8-1f1e6.png?v8",sauna_man:"unicode/1f9d6-2642.png?v8",sauna_person:"unicode/1f9d6.png?v8",sauna_woman:"unicode/1f9d6-2640.png?v8",sauropod:"unicode/1f995.png?v8",saxophone:"unicode/1f3b7.png?v8",scarf:"unicode/1f9e3.png?v8",school:"unicode/1f3eb.png?v8",school_satchel:"unicode/1f392.png?v8",scientist:"unicode/1f9d1-1f52c.png?v8",scissors:"unicode/2702.png?v8",scorpion:"unicode/1f982.png?v8",scorpius:"unicode/264f.png?v8",scotland:"unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png?v8",scream:"unicode/1f631.png?v8",scream_cat:"unicode/1f640.png?v8",screwdriver:"unicode/1fa9b.png?v8",scroll:"unicode/1f4dc.png?v8",seal:"unicode/1f9ad.png?v8",seat:"unicode/1f4ba.png?v8",secret:"unicode/3299.png?v8",see_no_evil:"unicode/1f648.png?v8",seedling:"unicode/1f331.png?v8",selfie:"unicode/1f933.png?v8",senegal:"unicode/1f1f8-1f1f3.png?v8",serbia:"unicode/1f1f7-1f1f8.png?v8",service_dog:"unicode/1f415-1f9ba.png?v8",seven:"unicode/0037-20e3.png?v8",sewing_needle:"unicode/1faa1.png?v8",seychelles:"unicode/1f1f8-1f1e8.png?v8",shallow_pan_of_food:"unicode/1f958.png?v8",shamrock:"unicode/2618.png?v8",shark:"unicode/1f988.png?v8",shaved_ice:"unicode/1f367.png?v8",sheep:"unicode/1f411.png?v8",shell:"unicode/1f41a.png?v8",shield:"unicode/1f6e1.png?v8",shinto_shrine:"unicode/26e9.png?v8",ship:"unicode/1f6a2.png?v8",shipit:"shipit.png?v8",shirt:"unicode/1f455.png?v8",shit:"unicode/1f4a9.png?v8",shoe:"unicode/1f45e.png?v8",shopping:"unicode/1f6cd.png?v8",shopping_cart:"unicode/1f6d2.png?v8",shorts:"unicode/1fa73.png?v8",shower:"unicode/1f6bf.png?v8",shrimp:"unicode/1f990.png?v8",shrug:"unicode/1f937.png?v8",shushing_face:"unicode/1f92b.png?v8",sierra_leone:"unicode/1f1f8-1f1f1.png?v8",signal_strength:"unicode/1f4f6.png?v8",singapore:"unicode/1f1f8-1f1ec.png?v8",singer:"unicode/1f9d1-1f3a4.png?v8",sint_maarten:"unicode/1f1f8-1f1fd.png?v8",six:"unicode/0036-20e3.png?v8",six_pointed_star:"unicode/1f52f.png?v8",skateboard:"unicode/1f6f9.png?v8",ski:"unicode/1f3bf.png?v8",skier:"unicode/26f7.png?v8",skull:"unicode/1f480.png?v8",skull_and_crossbones:"unicode/2620.png?v8",skunk:"unicode/1f9a8.png?v8",sled:"unicode/1f6f7.png?v8",sleeping:"unicode/1f634.png?v8",sleeping_bed:"unicode/1f6cc.png?v8",sleepy:"unicode/1f62a.png?v8",slightly_frowning_face:"unicode/1f641.png?v8",slightly_smiling_face:"unicode/1f642.png?v8",slot_machine:"unicode/1f3b0.png?v8",sloth:"unicode/1f9a5.png?v8",slovakia:"unicode/1f1f8-1f1f0.png?v8",slovenia:"unicode/1f1f8-1f1ee.png?v8",small_airplane:"unicode/1f6e9.png?v8",small_blue_diamond:"unicode/1f539.png?v8",small_orange_diamond:"unicode/1f538.png?v8",small_red_triangle:"unicode/1f53a.png?v8",small_red_triangle_down:"unicode/1f53b.png?v8",smile:"unicode/1f604.png?v8",smile_cat:"unicode/1f638.png?v8",smiley:"unicode/1f603.png?v8",smiley_cat:"unicode/1f63a.png?v8",smiling_face_with_tear:"unicode/1f972.png?v8",smiling_face_with_three_hearts:"unicode/1f970.png?v8",smiling_imp:"unicode/1f608.png?v8",smirk:"unicode/1f60f.png?v8",smirk_cat:"unicode/1f63c.png?v8",smoking:"unicode/1f6ac.png?v8",snail:"unicode/1f40c.png?v8",snake:"unicode/1f40d.png?v8",sneezing_face:"unicode/1f927.png?v8",snowboarder:"unicode/1f3c2.png?v8",snowflake:"unicode/2744.png?v8",snowman:"unicode/26c4.png?v8",snowman_with_snow:"unicode/2603.png?v8",soap:"unicode/1f9fc.png?v8",sob:"unicode/1f62d.png?v8",soccer:"unicode/26bd.png?v8",socks:"unicode/1f9e6.png?v8",softball:"unicode/1f94e.png?v8",solomon_islands:"unicode/1f1f8-1f1e7.png?v8",somalia:"unicode/1f1f8-1f1f4.png?v8",soon:"unicode/1f51c.png?v8",sos:"unicode/1f198.png?v8",sound:"unicode/1f509.png?v8",south_africa:"unicode/1f1ff-1f1e6.png?v8",south_georgia_south_sandwich_islands:"unicode/1f1ec-1f1f8.png?v8",south_sudan:"unicode/1f1f8-1f1f8.png?v8",space_invader:"unicode/1f47e.png?v8",spades:"unicode/2660.png?v8",spaghetti:"unicode/1f35d.png?v8",sparkle:"unicode/2747.png?v8",sparkler:"unicode/1f387.png?v8",sparkles:"unicode/2728.png?v8",sparkling_heart:"unicode/1f496.png?v8",speak_no_evil:"unicode/1f64a.png?v8",speaker:"unicode/1f508.png?v8",speaking_head:"unicode/1f5e3.png?v8",speech_balloon:"unicode/1f4ac.png?v8",speedboat:"unicode/1f6a4.png?v8",spider:"unicode/1f577.png?v8",spider_web:"unicode/1f578.png?v8",spiral_calendar:"unicode/1f5d3.png?v8",spiral_notepad:"unicode/1f5d2.png?v8",sponge:"unicode/1f9fd.png?v8",spoon:"unicode/1f944.png?v8",squid:"unicode/1f991.png?v8",sri_lanka:"unicode/1f1f1-1f1f0.png?v8",st_barthelemy:"unicode/1f1e7-1f1f1.png?v8",st_helena:"unicode/1f1f8-1f1ed.png?v8",st_kitts_nevis:"unicode/1f1f0-1f1f3.png?v8",st_lucia:"unicode/1f1f1-1f1e8.png?v8",st_martin:"unicode/1f1f2-1f1eb.png?v8",st_pierre_miquelon:"unicode/1f1f5-1f1f2.png?v8",st_vincent_grenadines:"unicode/1f1fb-1f1e8.png?v8",stadium:"unicode/1f3df.png?v8",standing_man:"unicode/1f9cd-2642.png?v8",standing_person:"unicode/1f9cd.png?v8",standing_woman:"unicode/1f9cd-2640.png?v8",star:"unicode/2b50.png?v8",star2:"unicode/1f31f.png?v8",star_and_crescent:"unicode/262a.png?v8",star_of_david:"unicode/2721.png?v8",star_struck:"unicode/1f929.png?v8",stars:"unicode/1f320.png?v8",station:"unicode/1f689.png?v8",statue_of_liberty:"unicode/1f5fd.png?v8",steam_locomotive:"unicode/1f682.png?v8",stethoscope:"unicode/1fa7a.png?v8",stew:"unicode/1f372.png?v8",stop_button:"unicode/23f9.png?v8",stop_sign:"unicode/1f6d1.png?v8",stopwatch:"unicode/23f1.png?v8",straight_ruler:"unicode/1f4cf.png?v8",strawberry:"unicode/1f353.png?v8",stuck_out_tongue:"unicode/1f61b.png?v8",stuck_out_tongue_closed_eyes:"unicode/1f61d.png?v8",stuck_out_tongue_winking_eye:"unicode/1f61c.png?v8",student:"unicode/1f9d1-1f393.png?v8",studio_microphone:"unicode/1f399.png?v8",stuffed_flatbread:"unicode/1f959.png?v8",sudan:"unicode/1f1f8-1f1e9.png?v8",sun_behind_large_cloud:"unicode/1f325.png?v8",sun_behind_rain_cloud:"unicode/1f326.png?v8",sun_behind_small_cloud:"unicode/1f324.png?v8",sun_with_face:"unicode/1f31e.png?v8",sunflower:"unicode/1f33b.png?v8",sunglasses:"unicode/1f60e.png?v8",sunny:"unicode/2600.png?v8",sunrise:"unicode/1f305.png?v8",sunrise_over_mountains:"unicode/1f304.png?v8",superhero:"unicode/1f9b8.png?v8",superhero_man:"unicode/1f9b8-2642.png?v8",superhero_woman:"unicode/1f9b8-2640.png?v8",supervillain:"unicode/1f9b9.png?v8",supervillain_man:"unicode/1f9b9-2642.png?v8",supervillain_woman:"unicode/1f9b9-2640.png?v8",surfer:"unicode/1f3c4.png?v8",surfing_man:"unicode/1f3c4-2642.png?v8",surfing_woman:"unicode/1f3c4-2640.png?v8",suriname:"unicode/1f1f8-1f1f7.png?v8",sushi:"unicode/1f363.png?v8",suspect:"suspect.png?v8",suspension_railway:"unicode/1f69f.png?v8",svalbard_jan_mayen:"unicode/1f1f8-1f1ef.png?v8",swan:"unicode/1f9a2.png?v8",swaziland:"unicode/1f1f8-1f1ff.png?v8",sweat:"unicode/1f613.png?v8",sweat_drops:"unicode/1f4a6.png?v8",sweat_smile:"unicode/1f605.png?v8",sweden:"unicode/1f1f8-1f1ea.png?v8",sweet_potato:"unicode/1f360.png?v8",swim_brief:"unicode/1fa72.png?v8",swimmer:"unicode/1f3ca.png?v8",swimming_man:"unicode/1f3ca-2642.png?v8",swimming_woman:"unicode/1f3ca-2640.png?v8",switzerland:"unicode/1f1e8-1f1ed.png?v8",symbols:"unicode/1f523.png?v8",synagogue:"unicode/1f54d.png?v8",syria:"unicode/1f1f8-1f1fe.png?v8",syringe:"unicode/1f489.png?v8","t-rex":"unicode/1f996.png?v8",taco:"unicode/1f32e.png?v8",tada:"unicode/1f389.png?v8",taiwan:"unicode/1f1f9-1f1fc.png?v8",tajikistan:"unicode/1f1f9-1f1ef.png?v8",takeout_box:"unicode/1f961.png?v8",tamale:"unicode/1fad4.png?v8",tanabata_tree:"unicode/1f38b.png?v8",tangerine:"unicode/1f34a.png?v8",tanzania:"unicode/1f1f9-1f1ff.png?v8",taurus:"unicode/2649.png?v8",taxi:"unicode/1f695.png?v8",tea:"unicode/1f375.png?v8",teacher:"unicode/1f9d1-1f3eb.png?v8",teapot:"unicode/1fad6.png?v8",technologist:"unicode/1f9d1-1f4bb.png?v8",teddy_bear:"unicode/1f9f8.png?v8",telephone:"unicode/260e.png?v8",telephone_receiver:"unicode/1f4de.png?v8",telescope:"unicode/1f52d.png?v8",tennis:"unicode/1f3be.png?v8",tent:"unicode/26fa.png?v8",test_tube:"unicode/1f9ea.png?v8",thailand:"unicode/1f1f9-1f1ed.png?v8",thermometer:"unicode/1f321.png?v8",thinking:"unicode/1f914.png?v8",thong_sandal:"unicode/1fa74.png?v8",thought_balloon:"unicode/1f4ad.png?v8",thread:"unicode/1f9f5.png?v8",three:"unicode/0033-20e3.png?v8",thumbsdown:"unicode/1f44e.png?v8",thumbsup:"unicode/1f44d.png?v8",ticket:"unicode/1f3ab.png?v8",tickets:"unicode/1f39f.png?v8",tiger:"unicode/1f42f.png?v8",tiger2:"unicode/1f405.png?v8",timer_clock:"unicode/23f2.png?v8",timor_leste:"unicode/1f1f9-1f1f1.png?v8",tipping_hand_man:"unicode/1f481-2642.png?v8",tipping_hand_person:"unicode/1f481.png?v8",tipping_hand_woman:"unicode/1f481-2640.png?v8",tired_face:"unicode/1f62b.png?v8",tm:"unicode/2122.png?v8",togo:"unicode/1f1f9-1f1ec.png?v8",toilet:"unicode/1f6bd.png?v8",tokelau:"unicode/1f1f9-1f1f0.png?v8",tokyo_tower:"unicode/1f5fc.png?v8",tomato:"unicode/1f345.png?v8",tonga:"unicode/1f1f9-1f1f4.png?v8",tongue:"unicode/1f445.png?v8",toolbox:"unicode/1f9f0.png?v8",tooth:"unicode/1f9b7.png?v8",toothbrush:"unicode/1faa5.png?v8",top:"unicode/1f51d.png?v8",tophat:"unicode/1f3a9.png?v8",tornado:"unicode/1f32a.png?v8",tr:"unicode/1f1f9-1f1f7.png?v8",trackball:"unicode/1f5b2.png?v8",tractor:"unicode/1f69c.png?v8",traffic_light:"unicode/1f6a5.png?v8",train:"unicode/1f68b.png?v8",train2:"unicode/1f686.png?v8",tram:"unicode/1f68a.png?v8",transgender_flag:"unicode/1f3f3-26a7.png?v8",transgender_symbol:"unicode/26a7.png?v8",triangular_flag_on_post:"unicode/1f6a9.png?v8",triangular_ruler:"unicode/1f4d0.png?v8",trident:"unicode/1f531.png?v8",trinidad_tobago:"unicode/1f1f9-1f1f9.png?v8",tristan_da_cunha:"unicode/1f1f9-1f1e6.png?v8",triumph:"unicode/1f624.png?v8",trolleybus:"unicode/1f68e.png?v8",trollface:"trollface.png?v8",trophy:"unicode/1f3c6.png?v8",tropical_drink:"unicode/1f379.png?v8",tropical_fish:"unicode/1f420.png?v8",truck:"unicode/1f69a.png?v8",trumpet:"unicode/1f3ba.png?v8",tshirt:"unicode/1f455.png?v8",tulip:"unicode/1f337.png?v8",tumbler_glass:"unicode/1f943.png?v8",tunisia:"unicode/1f1f9-1f1f3.png?v8",turkey:"unicode/1f983.png?v8",turkmenistan:"unicode/1f1f9-1f1f2.png?v8",turks_caicos_islands:"unicode/1f1f9-1f1e8.png?v8",turtle:"unicode/1f422.png?v8",tuvalu:"unicode/1f1f9-1f1fb.png?v8",tv:"unicode/1f4fa.png?v8",twisted_rightwards_arrows:"unicode/1f500.png?v8",two:"unicode/0032-20e3.png?v8",two_hearts:"unicode/1f495.png?v8",two_men_holding_hands:"unicode/1f46c.png?v8",two_women_holding_hands:"unicode/1f46d.png?v8",u5272:"unicode/1f239.png?v8",u5408:"unicode/1f234.png?v8",u55b6:"unicode/1f23a.png?v8",u6307:"unicode/1f22f.png?v8",u6708:"unicode/1f237.png?v8",u6709:"unicode/1f236.png?v8",u6e80:"unicode/1f235.png?v8",u7121:"unicode/1f21a.png?v8",u7533:"unicode/1f238.png?v8",u7981:"unicode/1f232.png?v8",u7a7a:"unicode/1f233.png?v8",uganda:"unicode/1f1fa-1f1ec.png?v8",uk:"unicode/1f1ec-1f1e7.png?v8",ukraine:"unicode/1f1fa-1f1e6.png?v8",umbrella:"unicode/2614.png?v8",unamused:"unicode/1f612.png?v8",underage:"unicode/1f51e.png?v8",unicorn:"unicode/1f984.png?v8",united_arab_emirates:"unicode/1f1e6-1f1ea.png?v8",united_nations:"unicode/1f1fa-1f1f3.png?v8",unlock:"unicode/1f513.png?v8",up:"unicode/1f199.png?v8",upside_down_face:"unicode/1f643.png?v8",uruguay:"unicode/1f1fa-1f1fe.png?v8",us:"unicode/1f1fa-1f1f8.png?v8",us_outlying_islands:"unicode/1f1fa-1f1f2.png?v8",us_virgin_islands:"unicode/1f1fb-1f1ee.png?v8",uzbekistan:"unicode/1f1fa-1f1ff.png?v8",v:"unicode/270c.png?v8",vampire:"unicode/1f9db.png?v8",vampire_man:"unicode/1f9db-2642.png?v8",vampire_woman:"unicode/1f9db-2640.png?v8",vanuatu:"unicode/1f1fb-1f1fa.png?v8",vatican_city:"unicode/1f1fb-1f1e6.png?v8",venezuela:"unicode/1f1fb-1f1ea.png?v8",vertical_traffic_light:"unicode/1f6a6.png?v8",vhs:"unicode/1f4fc.png?v8",vibration_mode:"unicode/1f4f3.png?v8",video_camera:"unicode/1f4f9.png?v8",video_game:"unicode/1f3ae.png?v8",vietnam:"unicode/1f1fb-1f1f3.png?v8",violin:"unicode/1f3bb.png?v8",virgo:"unicode/264d.png?v8",volcano:"unicode/1f30b.png?v8",volleyball:"unicode/1f3d0.png?v8",vomiting_face:"unicode/1f92e.png?v8",vs:"unicode/1f19a.png?v8",vulcan_salute:"unicode/1f596.png?v8",waffle:"unicode/1f9c7.png?v8",wales:"unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png?v8",walking:"unicode/1f6b6.png?v8",walking_man:"unicode/1f6b6-2642.png?v8",walking_woman:"unicode/1f6b6-2640.png?v8",wallis_futuna:"unicode/1f1fc-1f1eb.png?v8",waning_crescent_moon:"unicode/1f318.png?v8",waning_gibbous_moon:"unicode/1f316.png?v8",warning:"unicode/26a0.png?v8",wastebasket:"unicode/1f5d1.png?v8",watch:"unicode/231a.png?v8",water_buffalo:"unicode/1f403.png?v8",water_polo:"unicode/1f93d.png?v8",watermelon:"unicode/1f349.png?v8",wave:"unicode/1f44b.png?v8",wavy_dash:"unicode/3030.png?v8",waxing_crescent_moon:"unicode/1f312.png?v8",waxing_gibbous_moon:"unicode/1f314.png?v8",wc:"unicode/1f6be.png?v8",weary:"unicode/1f629.png?v8",wedding:"unicode/1f492.png?v8",weight_lifting:"unicode/1f3cb.png?v8",weight_lifting_man:"unicode/1f3cb-2642.png?v8",weight_lifting_woman:"unicode/1f3cb-2640.png?v8",western_sahara:"unicode/1f1ea-1f1ed.png?v8",whale:"unicode/1f433.png?v8",whale2:"unicode/1f40b.png?v8",wheel_of_dharma:"unicode/2638.png?v8",wheelchair:"unicode/267f.png?v8",white_check_mark:"unicode/2705.png?v8",white_circle:"unicode/26aa.png?v8",white_flag:"unicode/1f3f3.png?v8",white_flower:"unicode/1f4ae.png?v8",white_haired_man:"unicode/1f468-1f9b3.png?v8",white_haired_woman:"unicode/1f469-1f9b3.png?v8",white_heart:"unicode/1f90d.png?v8",white_large_square:"unicode/2b1c.png?v8",white_medium_small_square:"unicode/25fd.png?v8",white_medium_square:"unicode/25fb.png?v8",white_small_square:"unicode/25ab.png?v8",white_square_button:"unicode/1f533.png?v8",wilted_flower:"unicode/1f940.png?v8",wind_chime:"unicode/1f390.png?v8",wind_face:"unicode/1f32c.png?v8",window:"unicode/1fa9f.png?v8",wine_glass:"unicode/1f377.png?v8",wink:"unicode/1f609.png?v8",wolf:"unicode/1f43a.png?v8",woman:"unicode/1f469.png?v8",woman_artist:"unicode/1f469-1f3a8.png?v8",woman_astronaut:"unicode/1f469-1f680.png?v8",woman_beard:"unicode/1f9d4-2640.png?v8",woman_cartwheeling:"unicode/1f938-2640.png?v8",woman_cook:"unicode/1f469-1f373.png?v8",woman_dancing:"unicode/1f483.png?v8",woman_facepalming:"unicode/1f926-2640.png?v8",woman_factory_worker:"unicode/1f469-1f3ed.png?v8",woman_farmer:"unicode/1f469-1f33e.png?v8",woman_feeding_baby:"unicode/1f469-1f37c.png?v8",woman_firefighter:"unicode/1f469-1f692.png?v8",woman_health_worker:"unicode/1f469-2695.png?v8",woman_in_manual_wheelchair:"unicode/1f469-1f9bd.png?v8",woman_in_motorized_wheelchair:"unicode/1f469-1f9bc.png?v8",woman_in_tuxedo:"unicode/1f935-2640.png?v8",woman_judge:"unicode/1f469-2696.png?v8",woman_juggling:"unicode/1f939-2640.png?v8",woman_mechanic:"unicode/1f469-1f527.png?v8",woman_office_worker:"unicode/1f469-1f4bc.png?v8",woman_pilot:"unicode/1f469-2708.png?v8",woman_playing_handball:"unicode/1f93e-2640.png?v8",woman_playing_water_polo:"unicode/1f93d-2640.png?v8",woman_scientist:"unicode/1f469-1f52c.png?v8",woman_shrugging:"unicode/1f937-2640.png?v8",woman_singer:"unicode/1f469-1f3a4.png?v8",woman_student:"unicode/1f469-1f393.png?v8",woman_teacher:"unicode/1f469-1f3eb.png?v8",woman_technologist:"unicode/1f469-1f4bb.png?v8",woman_with_headscarf:"unicode/1f9d5.png?v8",woman_with_probing_cane:"unicode/1f469-1f9af.png?v8",woman_with_turban:"unicode/1f473-2640.png?v8",woman_with_veil:"unicode/1f470-2640.png?v8",womans_clothes:"unicode/1f45a.png?v8",womans_hat:"unicode/1f452.png?v8",women_wrestling:"unicode/1f93c-2640.png?v8",womens:"unicode/1f6ba.png?v8",wood:"unicode/1fab5.png?v8",woozy_face:"unicode/1f974.png?v8",world_map:"unicode/1f5fa.png?v8",worm:"unicode/1fab1.png?v8",worried:"unicode/1f61f.png?v8",wrench:"unicode/1f527.png?v8",wrestling:"unicode/1f93c.png?v8",writing_hand:"unicode/270d.png?v8",x:"unicode/274c.png?v8",yarn:"unicode/1f9f6.png?v8",yawning_face:"unicode/1f971.png?v8",yellow_circle:"unicode/1f7e1.png?v8",yellow_heart:"unicode/1f49b.png?v8",yellow_square:"unicode/1f7e8.png?v8",yemen:"unicode/1f1fe-1f1ea.png?v8",yen:"unicode/1f4b4.png?v8",yin_yang:"unicode/262f.png?v8",yo_yo:"unicode/1fa80.png?v8",yum:"unicode/1f60b.png?v8",zambia:"unicode/1f1ff-1f1f2.png?v8",zany_face:"unicode/1f92a.png?v8",zap:"unicode/26a1.png?v8",zebra:"unicode/1f993.png?v8",zero:"unicode/0030-20e3.png?v8",zimbabwe:"unicode/1f1ff-1f1fc.png?v8",zipper_mouth_face:"unicode/1f910.png?v8",zombie:"unicode/1f9df.png?v8",zombie_man:"unicode/1f9df-2642.png?v8",zombie_woman:"unicode/1f9df-2640.png?v8",zzz:"unicode/1f4a4.png?v8"}};function jn(e,t){return e.replace(/<(code|pre|script|template)[^>]*?>[\s\S]+?<\/(code|pre|script|template)>/g,function(e){return e.replace(/:/g,"__colon__")}).replace(//g,function(e){return e.replace(/:/g,"__colon__")}).replace(/([a-z]{2,}:)?\/\/[^\s'">)]+/gi,function(e){return e.replace(/:/g,"__colon__")}).replace(/:([a-z0-9_\-+]+?):/g,function(e,n){return i=e,o=n,e=t,n=Cn.data[o],i,i=n?e&&/unicode/.test(n)?''+n.replace("unicode/","").replace(/\.png.*/,"").split("-").map(function(e){return"&#x"+e+";"}).join("‍").concat("︎")+"":''+o+'':i;var i,o}).replace(/__colon__/g,":")}function Ln(e){var o={};return{str:e=(e=void 0===e?"":e)&&e.replace(/^('|")/,"").replace(/('|")$/,"").replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g,function(e,n,i){return-1===n.indexOf(":")?(o[n]=i&&i.replace(/"/g,"")||!0,""):e}).trim(),config:o}}function On(e){return(e=void 0===e?"":e).replace(/(<\/?a.*?>)/gi,"")}var qn,Pn=be(function(e){var u,f,p,d,n,g=function(u){var i=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,e={},R={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof T?new T(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=r.reach);m+=_.value.length,_=_.next){var b=_.value;if(i.length>n.length)return;if(!(b instanceof T)){var k,w=1;if(l){if(!(k=C(h,m,n,s))||k.index>=n.length)break;var y=k.index,x=k.index+k[0].length,S=m;for(S+=_.value.length;S<=y;)_=_.next,S+=_.value.length;if(S-=_.value.length,m=S,_.value instanceof T)continue;for(var A=_;A!==i.tail&&(Sr.reach&&(r.reach=E);b=_.prev;z&&(b=j(i,b,z),m+=z.length),L(i,b,w);$=new T(c,g?R.tokenize($,g):$,v,$);_=j(i,b,$),F&&j(i,_,F),1r.reach&&(r.reach=E.reach))}}}}}(e,t,n,t.head,0),function(e){var n=[],i=e.head.next;for(;i!==e.tail;)n.push(i.value),i=i.next;return n}(t)},hooks:{all:{},add:function(e,n){var i=R.hooks.all;i[e]=i[e]||[],i[e].push(n)},run:function(e,n){var i=R.hooks.all[e];if(i&&i.length)for(var o,t=0;o=i[t++];)o(n)}},Token:T};function T(e,n,i,o){this.type=e,this.content=n,this.alias=i,this.length=0|(o||"").length}function C(e,n,i,o){e.lastIndex=n;i=e.exec(i);return i&&o&&i[1]&&(o=i[1].length,i.index+=o,i[0]=i[0].slice(o)),i}function a(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function j(e,n,i){var o=n.next,i={value:i,prev:n,next:o};return n.next=i,o.prev=i,e.length++,i}function L(e,n,i){for(var o=n.next,t=0;t"+t.content+""},!u.document)return u.addEventListener&&(R.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),i=n.language,e=n.code,n=n.immediateClose;u.postMessage(R.highlight(e,R.languages[i],i)),n&&u.close()},!1)),R;var o=R.util.currentScript();function t(){R.manual||R.highlightAll()}return o&&(R.filename=o.src,o.hasAttribute("data-manual")&&(R.manual=!0)),R.manual||("loading"===(e=document.readyState)||"interactive"===e&&o&&o.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)),R}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=g),void 0!==me&&(me.Prism=g),g.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},g.languages.markup.tag.inside["attr-value"].inside.entity=g.languages.markup.entity,g.languages.markup.doctype.inside["internal-subset"].inside=g.languages.markup,g.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(g.languages.markup.tag,"addInlined",{value:function(e,n){var i={};i["language-"+n]={pattern:/(^$)/i,lookbehind:!0,inside:g.languages[n]},i.cdata=/^$/i;i={"included-cdata":{pattern://i,inside:i}};i["language-"+n]={pattern:/[\s\S]+/,inside:g.languages[n]};n={};n[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:i},g.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(g.languages.markup.tag,"addAttribute",{value:function(e,n){g.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[n,"language-"+n],inside:g.languages[n]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),g.languages.html=g.languages.markup,g.languages.mathml=g.languages.markup,g.languages.svg=g.languages.markup,g.languages.xml=g.languages.extend("markup",{}),g.languages.ssml=g.languages.xml,g.languages.atom=g.languages.xml,g.languages.rss=g.languages.xml,function(e){var n=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+n.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+n.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+n.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+n.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:n,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;e=e.languages.markup;e&&(e.tag.addInlined("style","css"),e.tag.addAttribute("style","css"))}(g),g.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},g.languages.javascript=g.languages.extend("clike",{"class-name":[g.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),g.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,g.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:g.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:g.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:g.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:g.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:g.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),g.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:g.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),g.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),g.languages.markup&&(g.languages.markup.tag.addInlined("script","javascript"),g.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),g.languages.js=g.languages.javascript,void 0!==g&&"undefined"!=typeof document&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),u={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},d="pre[data-src]:not(["+(f="data-src-status")+'="loaded"]):not(['+f+'="'+(p="loading")+'"])',g.hooks.add("before-highlightall",function(e){e.selector+=", "+d}),g.hooks.add("before-sanity-check",function(e){var t,n,i,o,a,r,c=e.element;c.matches(d)&&(e.code="",c.setAttribute(f,p),(t=c.appendChild(document.createElement("CODE"))).textContent="Loading…",i=c.getAttribute("data-src"),"none"===(e=e.language)&&(n=(/\.(\w+)$/.exec(i)||[,"none"])[1],e=u[n]||n),g.util.setLanguage(t,e),g.util.setLanguage(c,e),(n=g.plugins.autoloader)&&n.loadLanguages(e),i=i,o=function(e){c.setAttribute(f,"loaded");var n,i,o=function(e){if(i=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||"")){var n=Number(i[1]),e=i[2],i=i[3];return e?i?[n,Number(i)]:[n,void 0]:[n,n]}}(c.getAttribute("data-range"));o&&(n=e.split(/\r\n?|\n/g),i=o[0],o=null==o[1]?n.length:o[1],i<0&&(i+=n.length),i=Math.max(0,Math.min(i-1,n.length)),o<0&&(o+=n.length),o=Math.max(0,Math.min(o,n.length)),e=n.slice(i,o).join("\n"),c.hasAttribute("data-start")||c.setAttribute("data-start",String(i+1))),t.textContent=e,g.highlightElement(t)},a=function(e){c.setAttribute(f,"failed"),t.textContent=e},(r=new XMLHttpRequest).open("GET",i,!0),r.onreadystatechange=function(){4==r.readyState&&(r.status<400&&r.responseText?o(r.responseText):400<=r.status?a("✖ Error "+r.status+" while fetching file: "+r.statusText):a("✖ Error: File does not exist or is empty"))},r.send(null))}),n=!(g.plugins.fileHighlight={highlight:function(e){for(var n,i=(e||document).querySelectorAll(d),o=0;n=i[o++];)g.highlightElement(n)}}),g.fileHighlight=function(){n||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),n=!0),g.plugins.fileHighlight.highlight.apply(this,arguments)})});function Mn(e,n){return"___"+e.toUpperCase()+n+"___"}qn=Prism,Object.defineProperties(qn.languages["markup-templating"]={},{buildPlaceholders:{value:function(o,t,e,a){var r;o.language===t&&(r=o.tokenStack=[],o.code=o.code.replace(e,function(e){if("function"==typeof a&&!a(e))return e;for(var n,i=r.length;-1!==o.code.indexOf(n=Mn(t,i));)++i;return r[i]=e,n}),o.grammar=qn.languages.markup)}},tokenizePlaceholders:{value:function(f,p){var d,g;f.language===p&&f.tokenStack&&(f.grammar=qn.languages[p],d=0,g=Object.keys(f.tokenStack),function e(n){for(var i=0;i=g.length);i++){var o,t,a,r,c,u=n[i];"string"==typeof u||u.content&&"string"==typeof u.content?(t=g[d],a=f.tokenStack[t],o="string"==typeof u?u:u.content,c=Mn(p,t),-1<(r=o.indexOf(c))&&(++d,t=o.substring(0,r),a=new qn.Token(p,qn.tokenize(a,f.grammar),"language-"+p,a),r=o.substring(r+c.length),c=[],t&&c.push.apply(c,e([t])),c.push(a),r&&c.push.apply(c,e([r])),"string"==typeof u?n.splice.apply(n,[i,1].concat(c)):u.content=c)):u.content&&e(u.content)}return n}(f.tokens))}}});function In(t,e){var a=this;this.config=t,this.router=e,this.cacheTree={},this.toc=[],this.cacheTOC={},this.linkTarget=t.externalLinkTarget||"_blank",this.linkRel="_blank"===this.linkTarget?t.externalLinkRel||"noopener":"",this.contentBase=e.getBasePath();var n=this._initRenderer();this.heading=n.heading;var r=o(e=t.markdown||{})?e(Sn,n):(Sn.setOptions(m(e,{renderer:m(n,e.renderer)})),Sn);this._marked=r,this.compile=function(i){var o=!0,e=c(function(e){o=!1;var n="";return i&&(n=f(i)?r(i):r.parser(i),n=t.noEmoji?n:jn(n,t.nativeEmoji),Tn.clear(),n)})(i),n=a.router.parse().file;return o?a.toc=a.cacheTOC[n]:a.cacheTOC[n]=[].concat(a.toc),e}}var Nn={},Hn={markdown:function(e){return{url:e}},mermaid:function(e){return{url:e}},iframe:function(e,n){return{html:'"}},video:function(e,n){return{html:'"}},audio:function(e,n){return{html:'"}},code:function(e,n){var i=e.match(/\.(\w+)$/);return{url:e,lang:i="md"===(i=n||i&&i[1])?"markdown":i}}};In.prototype.compileEmbed=function(e,n){var i,o,t=Ln(n),a=t.str,t=t.config;if(n=a,t.include)return R(e)||(e=q(this.contentBase,C(this.router.getCurrentPath()),e)),t.type&&(o=Hn[t.type])?(i=o.call(this,e,n)).type=t.type:(o="code",/\.(md|markdown)/.test(e)?o="markdown":/\.mmd/.test(e)?o="mermaid":/\.html?/.test(e)?o="iframe":/\.(mp4|ogg)/.test(e)?o="video":/\.mp3/.test(e)&&(o="audio"),(i=Hn[o].call(this,e,n)).type=o),i.fragment=t.fragment,i},In.prototype._matchNotCompileLink=function(e){for(var n=this.config.noCompileLinks||[],i=0;i/g.test(o)&&(o=o.replace("\x3c!-- {docsify-ignore} --\x3e",""),e.title=On(o),e.ignoreSubHeading=!0),/{docsify-ignore}/g.test(o)&&(o=o.replace("{docsify-ignore}",""),e.title=On(o),e.ignoreSubHeading=!0),//g.test(o)&&(o=o.replace("\x3c!-- {docsify-ignore-all} --\x3e",""),e.title=On(o),e.ignoreAllSubs=!0),/{docsify-ignore-all}/g.test(o)&&(o=o.replace("{docsify-ignore-all}",""),e.title=On(o),e.ignoreAllSubs=!0);i=Tn(t.id||o),t=a.toURL(a.getCurrentPath(),{id:i});return e.slug=t,g.toc.push(e),"'+o+""},t.code={renderer:e}.renderer.code=function(e,n){var i=Pn.languages[n=void 0===n?"markup":n]||Pn.languages.markup;return'
    '+Pn.highlight(e.replace(/@DOCSIFY_QM@/g,"`"),i,n)+"
    "},t.link=(i=(n={renderer:e,router:a,linkTarget:n,linkRel:i,compilerClass:g}).renderer,c=n.router,u=n.linkTarget,n.linkRel,f=n.compilerClass,i.link=function(e,n,i){var o=[],t=Ln(n=void 0===n?"":n),a=t.str,t=t.config;return u=t.target||u,r="_blank"===u?f.config.externalLinkRel||"noopener":"",n=a,R(e)||f._matchNotCompileLink(e)||t.ignore?(R(e)||"./"!==e.slice(0,2)||(e=document.URL.replace(/\/(?!.*\/).*/,"/").replace("#/./","")+e),o.push(0===e.indexOf("mailto:")?"":'target="'+u+'"'),o.push(0!==e.indexOf("mailto:")&&""!==r?' rel="'+r+'"':"")):(e===f.config.homepage&&(e="README"),e=c.toURL(e,null,c.getCurrentPath())),t.disabled&&(o.push("disabled"),e="javascript:void(0)"),t.class&&o.push('class="'+t.class+'"'),t.id&&o.push('id="'+t.id+'"'),n&&o.push('title="'+n+'"'),'"+i+""}),t.paragraph={renderer:e}.renderer.paragraph=function(e){e=/^!>/.test(e)?$n("tip",e):/^\?>/.test(e)?$n("warn",e):"

    "+e+"

    ";return e},t.image=(o=(i={renderer:e,contentBase:o,router:a}).renderer,p=i.contentBase,d=i.router,o.image=function(e,n,i){var o=e,t=[],a=Ln(n),r=a.str,a=a.config;return n=r,a["no-zoom"]&&t.push("data-no-zoom"),n&&t.push('title="'+n+'"'),a.size&&(n=(r=a.size.split("x"))[0],(r=r[1])?t.push('width="'+n+'" height="'+r+'"'):t.push('width="'+n+'"')),a.class&&t.push('class="'+a.class+'"'),a.id&&t.push('id="'+a.id+'"'),R(e)||(o=q(p,C(d.getCurrentPath()),e)),0":''+i+'"}),t.list={renderer:e}.renderer.list=function(e,n,i){n=n?"ol":"ul";return"<"+n+" "+[/
  • /.test(e.split('class="task-list"')[0])?'class="task-list"':"",i&&1"+e+""},t.listitem={renderer:e}.renderer.listitem=function(e){return/^(]*>)/.test(e)?'
  • ":"
  • "+e+"
  • "},e.origin=t,e},In.prototype.sidebar=function(e,n){var i=this.toc,o=this.router.getCurrentPath(),t="";if(e)t=this.compile(e);else{for(var a=0;a{inner}");this.cacheTree[o]=n}return t},In.prototype.subSidebar=function(e){if(e){var n=this.router.getCurrentPath(),i=this.cacheTree,o=this.toc;o[0]&&o[0].ignoreAllSubs&&o.splice(0),o[0]&&1===o[0].level&&o.shift();for(var t=0;t\n'+e+"\n"}]).links={}:(n=[{type:"html",text:e}]).links={}),a({token:t,embedToken:n}),++u>=c&&a({})}}(n);n.embed.url?X(n.embed.url).then(o):o(n.embed.html)}}({compile:i,embedTokens:c,fetch:n},function(e){var n,i=e.embedToken,e=e.token;e?(n=e.index,p.forEach(function(e){n>e.start&&(n+=e.length)}),m(f,i.links),r=r.slice(0,n).concat(i,r.slice(n+1)),p.push({start:n,length:i.length-1})):(Bn[t]=r.concat(),r.links=Bn[t].links=f,o(r))})}function Yn(e,n,i){var o,t,a,r;return n="function"==typeof i?i(n):"string"==typeof i?(a=[],r=0,(o=i).replace(V,function(n,e,i){a.push(o.substring(r,i-1)),r=i+=n.length+1,a.push(t&&t[n]||function(e){return("00"+("string"==typeof Y[n]?e[Y[n]]():Y[n](e))).slice(-n.length)})}),r!==o.length&&a.push(o.substring(r)),function(e){for(var n="",i=0,o=e||new Date;i404 - Not found","Vue"in window)for(var a=0,r=k(".markdown-section > *").filter(n);ascript").filter(function(e){return!/template/.test(e.type)})[0])||(e=e.innerText.trim())&&new Function(e)()),"Vue"in window){var u,f,p=[],d=Object.keys(i.vueComponents||{});2===t&&d.length&&d.forEach(function(e){window.Vue.options.components[e]||window.Vue.component(e,i.vueComponents[e])}),!Un&&i.vueGlobalOptions&&"function"==typeof i.vueGlobalOptions.data&&(Un=i.vueGlobalOptions.data()),p.push.apply(p,Object.keys(i.vueMounts||{}).map(function(e){return[b(o,e),i.vueMounts[e]]}).filter(function(e){var n=e[0];e[1];return n})),(i.vueGlobalOptions||d.length)&&(u=/{{2}[^{}]*}{2}/,f=/<[^>/]+\s([@:]|v-)[\w-:.[\]]+[=>\s]/,p.push.apply(p,k(".markdown-section > *").filter(function(i){return!p.some(function(e){var n=e[0];e[1];return n===i})}).filter(function(e){return e.tagName.toLowerCase()in(i.vueComponents||{})||e.querySelector(d.join(",")||null)||u.test(e.outerHTML)||f.test(e.outerHTML)}).map(function(e){var n=m({},i.vueGlobalOptions||{});return Un&&(n.data=function(){return Un}),[e,n]})));for(var g=0,s=p;g([^<]*?)

    $'))&&("color"===n[2]?o.style.background=n[1]+(n[3]||""):(e=n[1],S(o,"add","has-mask"),R(n[1])||(e=q(this.router.getBasePath(),n[1])),o.style.backgroundImage="url("+e+")",o.style.backgroundSize="cover",o.style.backgroundPosition="center center"),i=i.replace(n[0],"")),this._renderTo(".cover-main",i),K()):S(o,"remove","show")},n.prototype._updateRender=function(){var e,n,i,o;e=this,n=l(".app-name-link"),i=e.config.nameLink,o=e.route.path,n&&(f(e.config.nameLink)?n.setAttribute("href",i):"object"==typeof i&&(e=Object.keys(i).filter(function(e){return-1':"")),e.coverpage&&(f+=(o=", 100%, 85%",'
    \x3c!--cover--\x3e
    ')),e.logo&&(o=/^data:image/.test(e.logo),n=/(?:http[s]?:)?\/\//.test(e.logo),i=/^\./.test(e.logo),o||n||i||(e.logo=q(this.router.getBasePath(),e.logo))),f+=(i=(n=e).name||"","
    "+('')+'
    \x3c!--main--\x3e
    '),this._renderTo(u,f,!0)):this.rendered=!0,e.mergeNavbar&&s?p=b(".sidebar"):(c.classList.add("app-nav"),e.repo||c.classList.add("no-badge")),e.loadNavbar&&y(p,c),e.themeColor&&(v.head.appendChild(w("div","").firstElementChild),a=e.themeColor,window.CSS&&window.CSS.supports&&window.CSS.supports("(--v:red)")||(e=k("style:not(.inserted),link"),[].forEach.call(e,function(e){"STYLE"===e.nodeName?Q(e,a):"LINK"===e.nodeName&&(e=e.getAttribute("href"),/\.css$/.test(e)&&X(e).then(function(e){e=w("style",e);_.appendChild(e),Q(e,a)}))}))),this._updateRender(),S(h,"ready")},n}(function(e){function n(){e.apply(this,arguments)}return e&&(n.__proto__=e),((n.prototype=Object.create(e&&e.prototype)).constructor=n).prototype.routes=function(){return this.config.routes||{}},n.prototype.matchVirtualRoute=function(t){var a=this.routes(),r=Object.keys(a),c=function(){return null};function u(){var e=r.shift();if(!e)return c(null);var n=A(o=(i="^",0===(o=e).indexOf(i)?o:"^"+o),"$")?o:o+"$",i=t.match(n);if(!i)return u();var o=a[e];if("string"==typeof o)return c(o);if("function"!=typeof o)return u();n=o,e=Xn(),o=e[0];return(0,e[1])(function(e){return"string"==typeof e?c(e):!1===e?c(null):u()}),n.length<=2?o(n(t,i)):n(t,i,o)}return{then:function(e){c=e,u()}}},n}(function(i){function e(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];i.apply(this,e),this.route={}}return i&&(e.__proto__=i),((e.prototype=Object.create(i&&i.prototype)).constructor=e).prototype.updateRender=function(){this.router.normalize(),this.route=this.router.parse(),h.setAttribute("data-page",this.route.file)},e.prototype.initRouter=function(){var n=this,e=this.config,e=new("history"===(e.routerMode||"hash")&&t?D:H)(e);this.router=e,this.updateRender(),U=this.route,e.onchange(function(e){n.updateRender(),n._updateRender(),U.path!==n.route.path?(n.$fetch(d,n.$resetEvents.bind(n,e.source)),U=n.route):n.$resetEvents(e.source)})},e}(function(e){function n(){e.apply(this,arguments)}return e&&(n.__proto__=e),((n.prototype=Object.create(e&&e.prototype)).constructor=n).prototype.initLifecycle=function(){var i=this;this._hooks={},this._lifecycle={},["init","mounted","beforeEach","afterEach","doneEach","ready"].forEach(function(e){var n=i._hooks[e]=[];i._lifecycle[e]=function(e){return n.push(e)}})},n.prototype.callHook=function(e,t,a){void 0===a&&(a=d);var r=this._hooks[e],c=this.config.catchPluginErrors,u=function(n){var e=r[n];if(n>=r.length)a(t);else if("function"==typeof e){var i="Docsify plugin error";if(2===e.length)try{e(t,function(e){t=e,u(n+1)})}catch(e){if(!c)throw e;console.error(i,e),u(n+1)}else try{var o=e(t);t=void 0===o?t:o,u(n+1)}catch(e){if(!c)throw e;console.error(i,e),u(n+1)}}else u(n+1)};u(0)},n}(we))))))));function Kn(e,n,i){return Qn&&Qn.abort&&Qn.abort(),Qn=X(e,!0,i)}window.Docsify={util:Me,dom:n,get:X,slugify:Tn,version:"4.13.1"},window.DocsifyCompiler=In,window.marked=Sn,window.Prism=Pn,e(function(e){return new Jn})}(); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/emoji.min.js b/ruoyi-admin/src/main/resources/static/static/js/emoji.min.js new file mode 100644 index 00000000..89151602 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/emoji.min.js @@ -0,0 +1 @@ +!function(){var o="https://github.githubassets.com/images/icons/emoji/",i={100:"unicode/1f4af.png?v8",1234:"unicode/1f522.png?v8","+1":"unicode/1f44d.png?v8","-1":"unicode/1f44e.png?v8","1st_place_medal":"unicode/1f947.png?v8","2nd_place_medal":"unicode/1f948.png?v8","3rd_place_medal":"unicode/1f949.png?v8","8ball":"unicode/1f3b1.png?v8",a:"unicode/1f170.png?v8",ab:"unicode/1f18e.png?v8",abacus:"unicode/1f9ee.png?v8",abc:"unicode/1f524.png?v8",abcd:"unicode/1f521.png?v8",accept:"unicode/1f251.png?v8",accessibility:"accessibility.png?v8",accordion:"unicode/1fa97.png?v8",adhesive_bandage:"unicode/1fa79.png?v8",adult:"unicode/1f9d1.png?v8",aerial_tramway:"unicode/1f6a1.png?v8",afghanistan:"unicode/1f1e6-1f1eb.png?v8",airplane:"unicode/2708.png?v8",aland_islands:"unicode/1f1e6-1f1fd.png?v8",alarm_clock:"unicode/23f0.png?v8",albania:"unicode/1f1e6-1f1f1.png?v8",alembic:"unicode/2697.png?v8",algeria:"unicode/1f1e9-1f1ff.png?v8",alien:"unicode/1f47d.png?v8",ambulance:"unicode/1f691.png?v8",american_samoa:"unicode/1f1e6-1f1f8.png?v8",amphora:"unicode/1f3fa.png?v8",anatomical_heart:"unicode/1fac0.png?v8",anchor:"unicode/2693.png?v8",andorra:"unicode/1f1e6-1f1e9.png?v8",angel:"unicode/1f47c.png?v8",anger:"unicode/1f4a2.png?v8",angola:"unicode/1f1e6-1f1f4.png?v8",angry:"unicode/1f620.png?v8",anguilla:"unicode/1f1e6-1f1ee.png?v8",anguished:"unicode/1f627.png?v8",ant:"unicode/1f41c.png?v8",antarctica:"unicode/1f1e6-1f1f6.png?v8",antigua_barbuda:"unicode/1f1e6-1f1ec.png?v8",apple:"unicode/1f34e.png?v8",aquarius:"unicode/2652.png?v8",argentina:"unicode/1f1e6-1f1f7.png?v8",aries:"unicode/2648.png?v8",armenia:"unicode/1f1e6-1f1f2.png?v8",arrow_backward:"unicode/25c0.png?v8",arrow_double_down:"unicode/23ec.png?v8",arrow_double_up:"unicode/23eb.png?v8",arrow_down:"unicode/2b07.png?v8",arrow_down_small:"unicode/1f53d.png?v8",arrow_forward:"unicode/25b6.png?v8",arrow_heading_down:"unicode/2935.png?v8",arrow_heading_up:"unicode/2934.png?v8",arrow_left:"unicode/2b05.png?v8",arrow_lower_left:"unicode/2199.png?v8",arrow_lower_right:"unicode/2198.png?v8",arrow_right:"unicode/27a1.png?v8",arrow_right_hook:"unicode/21aa.png?v8",arrow_up:"unicode/2b06.png?v8",arrow_up_down:"unicode/2195.png?v8",arrow_up_small:"unicode/1f53c.png?v8",arrow_upper_left:"unicode/2196.png?v8",arrow_upper_right:"unicode/2197.png?v8",arrows_clockwise:"unicode/1f503.png?v8",arrows_counterclockwise:"unicode/1f504.png?v8",art:"unicode/1f3a8.png?v8",articulated_lorry:"unicode/1f69b.png?v8",artificial_satellite:"unicode/1f6f0.png?v8",artist:"unicode/1f9d1-1f3a8.png?v8",aruba:"unicode/1f1e6-1f1fc.png?v8",ascension_island:"unicode/1f1e6-1f1e8.png?v8",asterisk:"unicode/002a-20e3.png?v8",astonished:"unicode/1f632.png?v8",astronaut:"unicode/1f9d1-1f680.png?v8",athletic_shoe:"unicode/1f45f.png?v8",atm:"unicode/1f3e7.png?v8",atom:"atom.png?v8",atom_symbol:"unicode/269b.png?v8",australia:"unicode/1f1e6-1f1fa.png?v8",austria:"unicode/1f1e6-1f1f9.png?v8",auto_rickshaw:"unicode/1f6fa.png?v8",avocado:"unicode/1f951.png?v8",axe:"unicode/1fa93.png?v8",azerbaijan:"unicode/1f1e6-1f1ff.png?v8",b:"unicode/1f171.png?v8",baby:"unicode/1f476.png?v8",baby_bottle:"unicode/1f37c.png?v8",baby_chick:"unicode/1f424.png?v8",baby_symbol:"unicode/1f6bc.png?v8",back:"unicode/1f519.png?v8",bacon:"unicode/1f953.png?v8",badger:"unicode/1f9a1.png?v8",badminton:"unicode/1f3f8.png?v8",bagel:"unicode/1f96f.png?v8",baggage_claim:"unicode/1f6c4.png?v8",baguette_bread:"unicode/1f956.png?v8",bahamas:"unicode/1f1e7-1f1f8.png?v8",bahrain:"unicode/1f1e7-1f1ed.png?v8",balance_scale:"unicode/2696.png?v8",bald_man:"unicode/1f468-1f9b2.png?v8",bald_woman:"unicode/1f469-1f9b2.png?v8",ballet_shoes:"unicode/1fa70.png?v8",balloon:"unicode/1f388.png?v8",ballot_box:"unicode/1f5f3.png?v8",ballot_box_with_check:"unicode/2611.png?v8",bamboo:"unicode/1f38d.png?v8",banana:"unicode/1f34c.png?v8",bangbang:"unicode/203c.png?v8",bangladesh:"unicode/1f1e7-1f1e9.png?v8",banjo:"unicode/1fa95.png?v8",bank:"unicode/1f3e6.png?v8",bar_chart:"unicode/1f4ca.png?v8",barbados:"unicode/1f1e7-1f1e7.png?v8",barber:"unicode/1f488.png?v8",baseball:"unicode/26be.png?v8",basecamp:"basecamp.png?v8",basecampy:"basecampy.png?v8",basket:"unicode/1f9fa.png?v8",basketball:"unicode/1f3c0.png?v8",basketball_man:"unicode/26f9-2642.png?v8",basketball_woman:"unicode/26f9-2640.png?v8",bat:"unicode/1f987.png?v8",bath:"unicode/1f6c0.png?v8",bathtub:"unicode/1f6c1.png?v8",battery:"unicode/1f50b.png?v8",beach_umbrella:"unicode/1f3d6.png?v8",bear:"unicode/1f43b.png?v8",bearded_person:"unicode/1f9d4.png?v8",beaver:"unicode/1f9ab.png?v8",bed:"unicode/1f6cf.png?v8",bee:"unicode/1f41d.png?v8",beer:"unicode/1f37a.png?v8",beers:"unicode/1f37b.png?v8",beetle:"unicode/1fab2.png?v8",beginner:"unicode/1f530.png?v8",belarus:"unicode/1f1e7-1f1fe.png?v8",belgium:"unicode/1f1e7-1f1ea.png?v8",belize:"unicode/1f1e7-1f1ff.png?v8",bell:"unicode/1f514.png?v8",bell_pepper:"unicode/1fad1.png?v8",bellhop_bell:"unicode/1f6ce.png?v8",benin:"unicode/1f1e7-1f1ef.png?v8",bento:"unicode/1f371.png?v8",bermuda:"unicode/1f1e7-1f1f2.png?v8",beverage_box:"unicode/1f9c3.png?v8",bhutan:"unicode/1f1e7-1f1f9.png?v8",bicyclist:"unicode/1f6b4.png?v8",bike:"unicode/1f6b2.png?v8",biking_man:"unicode/1f6b4-2642.png?v8",biking_woman:"unicode/1f6b4-2640.png?v8",bikini:"unicode/1f459.png?v8",billed_cap:"unicode/1f9e2.png?v8",biohazard:"unicode/2623.png?v8",bird:"unicode/1f426.png?v8",birthday:"unicode/1f382.png?v8",bison:"unicode/1f9ac.png?v8",black_cat:"unicode/1f408-2b1b.png?v8",black_circle:"unicode/26ab.png?v8",black_flag:"unicode/1f3f4.png?v8",black_heart:"unicode/1f5a4.png?v8",black_joker:"unicode/1f0cf.png?v8",black_large_square:"unicode/2b1b.png?v8",black_medium_small_square:"unicode/25fe.png?v8",black_medium_square:"unicode/25fc.png?v8",black_nib:"unicode/2712.png?v8",black_small_square:"unicode/25aa.png?v8",black_square_button:"unicode/1f532.png?v8",blond_haired_man:"unicode/1f471-2642.png?v8",blond_haired_person:"unicode/1f471.png?v8",blond_haired_woman:"unicode/1f471-2640.png?v8",blonde_woman:"unicode/1f471-2640.png?v8",blossom:"unicode/1f33c.png?v8",blowfish:"unicode/1f421.png?v8",blue_book:"unicode/1f4d8.png?v8",blue_car:"unicode/1f699.png?v8",blue_heart:"unicode/1f499.png?v8",blue_square:"unicode/1f7e6.png?v8",blueberries:"unicode/1fad0.png?v8",blush:"unicode/1f60a.png?v8",boar:"unicode/1f417.png?v8",boat:"unicode/26f5.png?v8",bolivia:"unicode/1f1e7-1f1f4.png?v8",bomb:"unicode/1f4a3.png?v8",bone:"unicode/1f9b4.png?v8",book:"unicode/1f4d6.png?v8",bookmark:"unicode/1f516.png?v8",bookmark_tabs:"unicode/1f4d1.png?v8",books:"unicode/1f4da.png?v8",boom:"unicode/1f4a5.png?v8",boomerang:"unicode/1fa83.png?v8",boot:"unicode/1f462.png?v8",bosnia_herzegovina:"unicode/1f1e7-1f1e6.png?v8",botswana:"unicode/1f1e7-1f1fc.png?v8",bouncing_ball_man:"unicode/26f9-2642.png?v8",bouncing_ball_person:"unicode/26f9.png?v8",bouncing_ball_woman:"unicode/26f9-2640.png?v8",bouquet:"unicode/1f490.png?v8",bouvet_island:"unicode/1f1e7-1f1fb.png?v8",bow:"unicode/1f647.png?v8",bow_and_arrow:"unicode/1f3f9.png?v8",bowing_man:"unicode/1f647-2642.png?v8",bowing_woman:"unicode/1f647-2640.png?v8",bowl_with_spoon:"unicode/1f963.png?v8",bowling:"unicode/1f3b3.png?v8",bowtie:"bowtie.png?v8",boxing_glove:"unicode/1f94a.png?v8",boy:"unicode/1f466.png?v8",brain:"unicode/1f9e0.png?v8",brazil:"unicode/1f1e7-1f1f7.png?v8",bread:"unicode/1f35e.png?v8",breast_feeding:"unicode/1f931.png?v8",bricks:"unicode/1f9f1.png?v8",bride_with_veil:"unicode/1f470-2640.png?v8",bridge_at_night:"unicode/1f309.png?v8",briefcase:"unicode/1f4bc.png?v8",british_indian_ocean_territory:"unicode/1f1ee-1f1f4.png?v8",british_virgin_islands:"unicode/1f1fb-1f1ec.png?v8",broccoli:"unicode/1f966.png?v8",broken_heart:"unicode/1f494.png?v8",broom:"unicode/1f9f9.png?v8",brown_circle:"unicode/1f7e4.png?v8",brown_heart:"unicode/1f90e.png?v8",brown_square:"unicode/1f7eb.png?v8",brunei:"unicode/1f1e7-1f1f3.png?v8",bubble_tea:"unicode/1f9cb.png?v8",bucket:"unicode/1faa3.png?v8",bug:"unicode/1f41b.png?v8",building_construction:"unicode/1f3d7.png?v8",bulb:"unicode/1f4a1.png?v8",bulgaria:"unicode/1f1e7-1f1ec.png?v8",bullettrain_front:"unicode/1f685.png?v8",bullettrain_side:"unicode/1f684.png?v8",burkina_faso:"unicode/1f1e7-1f1eb.png?v8",burrito:"unicode/1f32f.png?v8",burundi:"unicode/1f1e7-1f1ee.png?v8",bus:"unicode/1f68c.png?v8",business_suit_levitating:"unicode/1f574.png?v8",busstop:"unicode/1f68f.png?v8",bust_in_silhouette:"unicode/1f464.png?v8",busts_in_silhouette:"unicode/1f465.png?v8",butter:"unicode/1f9c8.png?v8",butterfly:"unicode/1f98b.png?v8",cactus:"unicode/1f335.png?v8",cake:"unicode/1f370.png?v8",calendar:"unicode/1f4c6.png?v8",call_me_hand:"unicode/1f919.png?v8",calling:"unicode/1f4f2.png?v8",cambodia:"unicode/1f1f0-1f1ed.png?v8",camel:"unicode/1f42b.png?v8",camera:"unicode/1f4f7.png?v8",camera_flash:"unicode/1f4f8.png?v8",cameroon:"unicode/1f1e8-1f1f2.png?v8",camping:"unicode/1f3d5.png?v8",canada:"unicode/1f1e8-1f1e6.png?v8",canary_islands:"unicode/1f1ee-1f1e8.png?v8",cancer:"unicode/264b.png?v8",candle:"unicode/1f56f.png?v8",candy:"unicode/1f36c.png?v8",canned_food:"unicode/1f96b.png?v8",canoe:"unicode/1f6f6.png?v8",cape_verde:"unicode/1f1e8-1f1fb.png?v8",capital_abcd:"unicode/1f520.png?v8",capricorn:"unicode/2651.png?v8",car:"unicode/1f697.png?v8",card_file_box:"unicode/1f5c3.png?v8",card_index:"unicode/1f4c7.png?v8",card_index_dividers:"unicode/1f5c2.png?v8",caribbean_netherlands:"unicode/1f1e7-1f1f6.png?v8",carousel_horse:"unicode/1f3a0.png?v8",carpentry_saw:"unicode/1fa9a.png?v8",carrot:"unicode/1f955.png?v8",cartwheeling:"unicode/1f938.png?v8",cat:"unicode/1f431.png?v8",cat2:"unicode/1f408.png?v8",cayman_islands:"unicode/1f1f0-1f1fe.png?v8",cd:"unicode/1f4bf.png?v8",central_african_republic:"unicode/1f1e8-1f1eb.png?v8",ceuta_melilla:"unicode/1f1ea-1f1e6.png?v8",chad:"unicode/1f1f9-1f1e9.png?v8",chains:"unicode/26d3.png?v8",chair:"unicode/1fa91.png?v8",champagne:"unicode/1f37e.png?v8",chart:"unicode/1f4b9.png?v8",chart_with_downwards_trend:"unicode/1f4c9.png?v8",chart_with_upwards_trend:"unicode/1f4c8.png?v8",checkered_flag:"unicode/1f3c1.png?v8",cheese:"unicode/1f9c0.png?v8",cherries:"unicode/1f352.png?v8",cherry_blossom:"unicode/1f338.png?v8",chess_pawn:"unicode/265f.png?v8",chestnut:"unicode/1f330.png?v8",chicken:"unicode/1f414.png?v8",child:"unicode/1f9d2.png?v8",children_crossing:"unicode/1f6b8.png?v8",chile:"unicode/1f1e8-1f1f1.png?v8",chipmunk:"unicode/1f43f.png?v8",chocolate_bar:"unicode/1f36b.png?v8",chopsticks:"unicode/1f962.png?v8",christmas_island:"unicode/1f1e8-1f1fd.png?v8",christmas_tree:"unicode/1f384.png?v8",church:"unicode/26ea.png?v8",cinema:"unicode/1f3a6.png?v8",circus_tent:"unicode/1f3aa.png?v8",city_sunrise:"unicode/1f307.png?v8",city_sunset:"unicode/1f306.png?v8",cityscape:"unicode/1f3d9.png?v8",cl:"unicode/1f191.png?v8",clamp:"unicode/1f5dc.png?v8",clap:"unicode/1f44f.png?v8",clapper:"unicode/1f3ac.png?v8",classical_building:"unicode/1f3db.png?v8",climbing:"unicode/1f9d7.png?v8",climbing_man:"unicode/1f9d7-2642.png?v8",climbing_woman:"unicode/1f9d7-2640.png?v8",clinking_glasses:"unicode/1f942.png?v8",clipboard:"unicode/1f4cb.png?v8",clipperton_island:"unicode/1f1e8-1f1f5.png?v8",clock1:"unicode/1f550.png?v8",clock10:"unicode/1f559.png?v8",clock1030:"unicode/1f565.png?v8",clock11:"unicode/1f55a.png?v8",clock1130:"unicode/1f566.png?v8",clock12:"unicode/1f55b.png?v8",clock1230:"unicode/1f567.png?v8",clock130:"unicode/1f55c.png?v8",clock2:"unicode/1f551.png?v8",clock230:"unicode/1f55d.png?v8",clock3:"unicode/1f552.png?v8",clock330:"unicode/1f55e.png?v8",clock4:"unicode/1f553.png?v8",clock430:"unicode/1f55f.png?v8",clock5:"unicode/1f554.png?v8",clock530:"unicode/1f560.png?v8",clock6:"unicode/1f555.png?v8",clock630:"unicode/1f561.png?v8",clock7:"unicode/1f556.png?v8",clock730:"unicode/1f562.png?v8",clock8:"unicode/1f557.png?v8",clock830:"unicode/1f563.png?v8",clock9:"unicode/1f558.png?v8",clock930:"unicode/1f564.png?v8",closed_book:"unicode/1f4d5.png?v8",closed_lock_with_key:"unicode/1f510.png?v8",closed_umbrella:"unicode/1f302.png?v8",cloud:"unicode/2601.png?v8",cloud_with_lightning:"unicode/1f329.png?v8",cloud_with_lightning_and_rain:"unicode/26c8.png?v8",cloud_with_rain:"unicode/1f327.png?v8",cloud_with_snow:"unicode/1f328.png?v8",clown_face:"unicode/1f921.png?v8",clubs:"unicode/2663.png?v8",cn:"unicode/1f1e8-1f1f3.png?v8",coat:"unicode/1f9e5.png?v8",cockroach:"unicode/1fab3.png?v8",cocktail:"unicode/1f378.png?v8",coconut:"unicode/1f965.png?v8",cocos_islands:"unicode/1f1e8-1f1e8.png?v8",coffee:"unicode/2615.png?v8",coffin:"unicode/26b0.png?v8",coin:"unicode/1fa99.png?v8",cold_face:"unicode/1f976.png?v8",cold_sweat:"unicode/1f630.png?v8",collision:"unicode/1f4a5.png?v8",colombia:"unicode/1f1e8-1f1f4.png?v8",comet:"unicode/2604.png?v8",comoros:"unicode/1f1f0-1f1f2.png?v8",compass:"unicode/1f9ed.png?v8",computer:"unicode/1f4bb.png?v8",computer_mouse:"unicode/1f5b1.png?v8",confetti_ball:"unicode/1f38a.png?v8",confounded:"unicode/1f616.png?v8",confused:"unicode/1f615.png?v8",congo_brazzaville:"unicode/1f1e8-1f1ec.png?v8",congo_kinshasa:"unicode/1f1e8-1f1e9.png?v8",congratulations:"unicode/3297.png?v8",construction:"unicode/1f6a7.png?v8",construction_worker:"unicode/1f477.png?v8",construction_worker_man:"unicode/1f477-2642.png?v8",construction_worker_woman:"unicode/1f477-2640.png?v8",control_knobs:"unicode/1f39b.png?v8",convenience_store:"unicode/1f3ea.png?v8",cook:"unicode/1f9d1-1f373.png?v8",cook_islands:"unicode/1f1e8-1f1f0.png?v8",cookie:"unicode/1f36a.png?v8",cool:"unicode/1f192.png?v8",cop:"unicode/1f46e.png?v8",copyright:"unicode/00a9.png?v8",corn:"unicode/1f33d.png?v8",costa_rica:"unicode/1f1e8-1f1f7.png?v8",cote_divoire:"unicode/1f1e8-1f1ee.png?v8",couch_and_lamp:"unicode/1f6cb.png?v8",couple:"unicode/1f46b.png?v8",couple_with_heart:"unicode/1f491.png?v8",couple_with_heart_man_man:"unicode/1f468-2764-1f468.png?v8",couple_with_heart_woman_man:"unicode/1f469-2764-1f468.png?v8",couple_with_heart_woman_woman:"unicode/1f469-2764-1f469.png?v8",couplekiss:"unicode/1f48f.png?v8",couplekiss_man_man:"unicode/1f468-2764-1f48b-1f468.png?v8",couplekiss_man_woman:"unicode/1f469-2764-1f48b-1f468.png?v8",couplekiss_woman_woman:"unicode/1f469-2764-1f48b-1f469.png?v8",cow:"unicode/1f42e.png?v8",cow2:"unicode/1f404.png?v8",cowboy_hat_face:"unicode/1f920.png?v8",crab:"unicode/1f980.png?v8",crayon:"unicode/1f58d.png?v8",credit_card:"unicode/1f4b3.png?v8",crescent_moon:"unicode/1f319.png?v8",cricket:"unicode/1f997.png?v8",cricket_game:"unicode/1f3cf.png?v8",croatia:"unicode/1f1ed-1f1f7.png?v8",crocodile:"unicode/1f40a.png?v8",croissant:"unicode/1f950.png?v8",crossed_fingers:"unicode/1f91e.png?v8",crossed_flags:"unicode/1f38c.png?v8",crossed_swords:"unicode/2694.png?v8",crown:"unicode/1f451.png?v8",cry:"unicode/1f622.png?v8",crying_cat_face:"unicode/1f63f.png?v8",crystal_ball:"unicode/1f52e.png?v8",cuba:"unicode/1f1e8-1f1fa.png?v8",cucumber:"unicode/1f952.png?v8",cup_with_straw:"unicode/1f964.png?v8",cupcake:"unicode/1f9c1.png?v8",cupid:"unicode/1f498.png?v8",curacao:"unicode/1f1e8-1f1fc.png?v8",curling_stone:"unicode/1f94c.png?v8",curly_haired_man:"unicode/1f468-1f9b1.png?v8",curly_haired_woman:"unicode/1f469-1f9b1.png?v8",curly_loop:"unicode/27b0.png?v8",currency_exchange:"unicode/1f4b1.png?v8",curry:"unicode/1f35b.png?v8",cursing_face:"unicode/1f92c.png?v8",custard:"unicode/1f36e.png?v8",customs:"unicode/1f6c3.png?v8",cut_of_meat:"unicode/1f969.png?v8",cyclone:"unicode/1f300.png?v8",cyprus:"unicode/1f1e8-1f1fe.png?v8",czech_republic:"unicode/1f1e8-1f1ff.png?v8",dagger:"unicode/1f5e1.png?v8",dancer:"unicode/1f483.png?v8",dancers:"unicode/1f46f.png?v8",dancing_men:"unicode/1f46f-2642.png?v8",dancing_women:"unicode/1f46f-2640.png?v8",dango:"unicode/1f361.png?v8",dark_sunglasses:"unicode/1f576.png?v8",dart:"unicode/1f3af.png?v8",dash:"unicode/1f4a8.png?v8",date:"unicode/1f4c5.png?v8",de:"unicode/1f1e9-1f1ea.png?v8",deaf_man:"unicode/1f9cf-2642.png?v8",deaf_person:"unicode/1f9cf.png?v8",deaf_woman:"unicode/1f9cf-2640.png?v8",deciduous_tree:"unicode/1f333.png?v8",deer:"unicode/1f98c.png?v8",denmark:"unicode/1f1e9-1f1f0.png?v8",department_store:"unicode/1f3ec.png?v8",dependabot:"dependabot.png?v8",derelict_house:"unicode/1f3da.png?v8",desert:"unicode/1f3dc.png?v8",desert_island:"unicode/1f3dd.png?v8",desktop_computer:"unicode/1f5a5.png?v8",detective:"unicode/1f575.png?v8",diamond_shape_with_a_dot_inside:"unicode/1f4a0.png?v8",diamonds:"unicode/2666.png?v8",diego_garcia:"unicode/1f1e9-1f1ec.png?v8",disappointed:"unicode/1f61e.png?v8",disappointed_relieved:"unicode/1f625.png?v8",disguised_face:"unicode/1f978.png?v8",diving_mask:"unicode/1f93f.png?v8",diya_lamp:"unicode/1fa94.png?v8",dizzy:"unicode/1f4ab.png?v8",dizzy_face:"unicode/1f635.png?v8",djibouti:"unicode/1f1e9-1f1ef.png?v8",dna:"unicode/1f9ec.png?v8",do_not_litter:"unicode/1f6af.png?v8",dodo:"unicode/1f9a4.png?v8",dog:"unicode/1f436.png?v8",dog2:"unicode/1f415.png?v8",dollar:"unicode/1f4b5.png?v8",dolls:"unicode/1f38e.png?v8",dolphin:"unicode/1f42c.png?v8",dominica:"unicode/1f1e9-1f1f2.png?v8",dominican_republic:"unicode/1f1e9-1f1f4.png?v8",door:"unicode/1f6aa.png?v8",doughnut:"unicode/1f369.png?v8",dove:"unicode/1f54a.png?v8",dragon:"unicode/1f409.png?v8",dragon_face:"unicode/1f432.png?v8",dress:"unicode/1f457.png?v8",dromedary_camel:"unicode/1f42a.png?v8",drooling_face:"unicode/1f924.png?v8",drop_of_blood:"unicode/1fa78.png?v8",droplet:"unicode/1f4a7.png?v8",drum:"unicode/1f941.png?v8",duck:"unicode/1f986.png?v8",dumpling:"unicode/1f95f.png?v8",dvd:"unicode/1f4c0.png?v8","e-mail":"unicode/1f4e7.png?v8",eagle:"unicode/1f985.png?v8",ear:"unicode/1f442.png?v8",ear_of_rice:"unicode/1f33e.png?v8",ear_with_hearing_aid:"unicode/1f9bb.png?v8",earth_africa:"unicode/1f30d.png?v8",earth_americas:"unicode/1f30e.png?v8",earth_asia:"unicode/1f30f.png?v8",ecuador:"unicode/1f1ea-1f1e8.png?v8",egg:"unicode/1f95a.png?v8",eggplant:"unicode/1f346.png?v8",egypt:"unicode/1f1ea-1f1ec.png?v8",eight:"unicode/0038-20e3.png?v8",eight_pointed_black_star:"unicode/2734.png?v8",eight_spoked_asterisk:"unicode/2733.png?v8",eject_button:"unicode/23cf.png?v8",el_salvador:"unicode/1f1f8-1f1fb.png?v8",electric_plug:"unicode/1f50c.png?v8",electron:"electron.png?v8",elephant:"unicode/1f418.png?v8",elevator:"unicode/1f6d7.png?v8",elf:"unicode/1f9dd.png?v8",elf_man:"unicode/1f9dd-2642.png?v8",elf_woman:"unicode/1f9dd-2640.png?v8",email:"unicode/1f4e7.png?v8",end:"unicode/1f51a.png?v8",england:"unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png?v8",envelope:"unicode/2709.png?v8",envelope_with_arrow:"unicode/1f4e9.png?v8",equatorial_guinea:"unicode/1f1ec-1f1f6.png?v8",eritrea:"unicode/1f1ea-1f1f7.png?v8",es:"unicode/1f1ea-1f1f8.png?v8",estonia:"unicode/1f1ea-1f1ea.png?v8",ethiopia:"unicode/1f1ea-1f1f9.png?v8",eu:"unicode/1f1ea-1f1fa.png?v8",euro:"unicode/1f4b6.png?v8",european_castle:"unicode/1f3f0.png?v8",european_post_office:"unicode/1f3e4.png?v8",european_union:"unicode/1f1ea-1f1fa.png?v8",evergreen_tree:"unicode/1f332.png?v8",exclamation:"unicode/2757.png?v8",exploding_head:"unicode/1f92f.png?v8",expressionless:"unicode/1f611.png?v8",eye:"unicode/1f441.png?v8",eye_speech_bubble:"unicode/1f441-1f5e8.png?v8",eyeglasses:"unicode/1f453.png?v8",eyes:"unicode/1f440.png?v8",face_exhaling:"unicode/1f62e-1f4a8.png?v8",face_in_clouds:"unicode/1f636-1f32b.png?v8",face_with_head_bandage:"unicode/1f915.png?v8",face_with_spiral_eyes:"unicode/1f635-1f4ab.png?v8",face_with_thermometer:"unicode/1f912.png?v8",facepalm:"unicode/1f926.png?v8",facepunch:"unicode/1f44a.png?v8",factory:"unicode/1f3ed.png?v8",factory_worker:"unicode/1f9d1-1f3ed.png?v8",fairy:"unicode/1f9da.png?v8",fairy_man:"unicode/1f9da-2642.png?v8",fairy_woman:"unicode/1f9da-2640.png?v8",falafel:"unicode/1f9c6.png?v8",falkland_islands:"unicode/1f1eb-1f1f0.png?v8",fallen_leaf:"unicode/1f342.png?v8",family:"unicode/1f46a.png?v8",family_man_boy:"unicode/1f468-1f466.png?v8",family_man_boy_boy:"unicode/1f468-1f466-1f466.png?v8",family_man_girl:"unicode/1f468-1f467.png?v8",family_man_girl_boy:"unicode/1f468-1f467-1f466.png?v8",family_man_girl_girl:"unicode/1f468-1f467-1f467.png?v8",family_man_man_boy:"unicode/1f468-1f468-1f466.png?v8",family_man_man_boy_boy:"unicode/1f468-1f468-1f466-1f466.png?v8",family_man_man_girl:"unicode/1f468-1f468-1f467.png?v8",family_man_man_girl_boy:"unicode/1f468-1f468-1f467-1f466.png?v8",family_man_man_girl_girl:"unicode/1f468-1f468-1f467-1f467.png?v8",family_man_woman_boy:"unicode/1f468-1f469-1f466.png?v8",family_man_woman_boy_boy:"unicode/1f468-1f469-1f466-1f466.png?v8",family_man_woman_girl:"unicode/1f468-1f469-1f467.png?v8",family_man_woman_girl_boy:"unicode/1f468-1f469-1f467-1f466.png?v8",family_man_woman_girl_girl:"unicode/1f468-1f469-1f467-1f467.png?v8",family_woman_boy:"unicode/1f469-1f466.png?v8",family_woman_boy_boy:"unicode/1f469-1f466-1f466.png?v8",family_woman_girl:"unicode/1f469-1f467.png?v8",family_woman_girl_boy:"unicode/1f469-1f467-1f466.png?v8",family_woman_girl_girl:"unicode/1f469-1f467-1f467.png?v8",family_woman_woman_boy:"unicode/1f469-1f469-1f466.png?v8",family_woman_woman_boy_boy:"unicode/1f469-1f469-1f466-1f466.png?v8",family_woman_woman_girl:"unicode/1f469-1f469-1f467.png?v8",family_woman_woman_girl_boy:"unicode/1f469-1f469-1f467-1f466.png?v8",family_woman_woman_girl_girl:"unicode/1f469-1f469-1f467-1f467.png?v8",farmer:"unicode/1f9d1-1f33e.png?v8",faroe_islands:"unicode/1f1eb-1f1f4.png?v8",fast_forward:"unicode/23e9.png?v8",fax:"unicode/1f4e0.png?v8",fearful:"unicode/1f628.png?v8",feather:"unicode/1fab6.png?v8",feelsgood:"feelsgood.png?v8",feet:"unicode/1f43e.png?v8",female_detective:"unicode/1f575-2640.png?v8",female_sign:"unicode/2640.png?v8",ferris_wheel:"unicode/1f3a1.png?v8",ferry:"unicode/26f4.png?v8",field_hockey:"unicode/1f3d1.png?v8",fiji:"unicode/1f1eb-1f1ef.png?v8",file_cabinet:"unicode/1f5c4.png?v8",file_folder:"unicode/1f4c1.png?v8",film_projector:"unicode/1f4fd.png?v8",film_strip:"unicode/1f39e.png?v8",finland:"unicode/1f1eb-1f1ee.png?v8",finnadie:"finnadie.png?v8",fire:"unicode/1f525.png?v8",fire_engine:"unicode/1f692.png?v8",fire_extinguisher:"unicode/1f9ef.png?v8",firecracker:"unicode/1f9e8.png?v8",firefighter:"unicode/1f9d1-1f692.png?v8",fireworks:"unicode/1f386.png?v8",first_quarter_moon:"unicode/1f313.png?v8",first_quarter_moon_with_face:"unicode/1f31b.png?v8",fish:"unicode/1f41f.png?v8",fish_cake:"unicode/1f365.png?v8",fishing_pole_and_fish:"unicode/1f3a3.png?v8",fishsticks:"fishsticks.png?v8",fist:"unicode/270a.png?v8",fist_left:"unicode/1f91b.png?v8",fist_oncoming:"unicode/1f44a.png?v8",fist_raised:"unicode/270a.png?v8",fist_right:"unicode/1f91c.png?v8",five:"unicode/0035-20e3.png?v8",flags:"unicode/1f38f.png?v8",flamingo:"unicode/1f9a9.png?v8",flashlight:"unicode/1f526.png?v8",flat_shoe:"unicode/1f97f.png?v8",flatbread:"unicode/1fad3.png?v8",fleur_de_lis:"unicode/269c.png?v8",flight_arrival:"unicode/1f6ec.png?v8",flight_departure:"unicode/1f6eb.png?v8",flipper:"unicode/1f42c.png?v8",floppy_disk:"unicode/1f4be.png?v8",flower_playing_cards:"unicode/1f3b4.png?v8",flushed:"unicode/1f633.png?v8",fly:"unicode/1fab0.png?v8",flying_disc:"unicode/1f94f.png?v8",flying_saucer:"unicode/1f6f8.png?v8",fog:"unicode/1f32b.png?v8",foggy:"unicode/1f301.png?v8",fondue:"unicode/1fad5.png?v8",foot:"unicode/1f9b6.png?v8",football:"unicode/1f3c8.png?v8",footprints:"unicode/1f463.png?v8",fork_and_knife:"unicode/1f374.png?v8",fortune_cookie:"unicode/1f960.png?v8",fountain:"unicode/26f2.png?v8",fountain_pen:"unicode/1f58b.png?v8",four:"unicode/0034-20e3.png?v8",four_leaf_clover:"unicode/1f340.png?v8",fox_face:"unicode/1f98a.png?v8",fr:"unicode/1f1eb-1f1f7.png?v8",framed_picture:"unicode/1f5bc.png?v8",free:"unicode/1f193.png?v8",french_guiana:"unicode/1f1ec-1f1eb.png?v8",french_polynesia:"unicode/1f1f5-1f1eb.png?v8",french_southern_territories:"unicode/1f1f9-1f1eb.png?v8",fried_egg:"unicode/1f373.png?v8",fried_shrimp:"unicode/1f364.png?v8",fries:"unicode/1f35f.png?v8",frog:"unicode/1f438.png?v8",frowning:"unicode/1f626.png?v8",frowning_face:"unicode/2639.png?v8",frowning_man:"unicode/1f64d-2642.png?v8",frowning_person:"unicode/1f64d.png?v8",frowning_woman:"unicode/1f64d-2640.png?v8",fu:"unicode/1f595.png?v8",fuelpump:"unicode/26fd.png?v8",full_moon:"unicode/1f315.png?v8",full_moon_with_face:"unicode/1f31d.png?v8",funeral_urn:"unicode/26b1.png?v8",gabon:"unicode/1f1ec-1f1e6.png?v8",gambia:"unicode/1f1ec-1f1f2.png?v8",game_die:"unicode/1f3b2.png?v8",garlic:"unicode/1f9c4.png?v8",gb:"unicode/1f1ec-1f1e7.png?v8",gear:"unicode/2699.png?v8",gem:"unicode/1f48e.png?v8",gemini:"unicode/264a.png?v8",genie:"unicode/1f9de.png?v8",genie_man:"unicode/1f9de-2642.png?v8",genie_woman:"unicode/1f9de-2640.png?v8",georgia:"unicode/1f1ec-1f1ea.png?v8",ghana:"unicode/1f1ec-1f1ed.png?v8",ghost:"unicode/1f47b.png?v8",gibraltar:"unicode/1f1ec-1f1ee.png?v8",gift:"unicode/1f381.png?v8",gift_heart:"unicode/1f49d.png?v8",giraffe:"unicode/1f992.png?v8",girl:"unicode/1f467.png?v8",globe_with_meridians:"unicode/1f310.png?v8",gloves:"unicode/1f9e4.png?v8",goal_net:"unicode/1f945.png?v8",goat:"unicode/1f410.png?v8",goberserk:"goberserk.png?v8",godmode:"godmode.png?v8",goggles:"unicode/1f97d.png?v8",golf:"unicode/26f3.png?v8",golfing:"unicode/1f3cc.png?v8",golfing_man:"unicode/1f3cc-2642.png?v8",golfing_woman:"unicode/1f3cc-2640.png?v8",gorilla:"unicode/1f98d.png?v8",grapes:"unicode/1f347.png?v8",greece:"unicode/1f1ec-1f1f7.png?v8",green_apple:"unicode/1f34f.png?v8",green_book:"unicode/1f4d7.png?v8",green_circle:"unicode/1f7e2.png?v8",green_heart:"unicode/1f49a.png?v8",green_salad:"unicode/1f957.png?v8",green_square:"unicode/1f7e9.png?v8",greenland:"unicode/1f1ec-1f1f1.png?v8",grenada:"unicode/1f1ec-1f1e9.png?v8",grey_exclamation:"unicode/2755.png?v8",grey_question:"unicode/2754.png?v8",grimacing:"unicode/1f62c.png?v8",grin:"unicode/1f601.png?v8",grinning:"unicode/1f600.png?v8",guadeloupe:"unicode/1f1ec-1f1f5.png?v8",guam:"unicode/1f1ec-1f1fa.png?v8",guard:"unicode/1f482.png?v8",guardsman:"unicode/1f482-2642.png?v8",guardswoman:"unicode/1f482-2640.png?v8",guatemala:"unicode/1f1ec-1f1f9.png?v8",guernsey:"unicode/1f1ec-1f1ec.png?v8",guide_dog:"unicode/1f9ae.png?v8",guinea:"unicode/1f1ec-1f1f3.png?v8",guinea_bissau:"unicode/1f1ec-1f1fc.png?v8",guitar:"unicode/1f3b8.png?v8",gun:"unicode/1f52b.png?v8",guyana:"unicode/1f1ec-1f1fe.png?v8",haircut:"unicode/1f487.png?v8",haircut_man:"unicode/1f487-2642.png?v8",haircut_woman:"unicode/1f487-2640.png?v8",haiti:"unicode/1f1ed-1f1f9.png?v8",hamburger:"unicode/1f354.png?v8",hammer:"unicode/1f528.png?v8",hammer_and_pick:"unicode/2692.png?v8",hammer_and_wrench:"unicode/1f6e0.png?v8",hamster:"unicode/1f439.png?v8",hand:"unicode/270b.png?v8",hand_over_mouth:"unicode/1f92d.png?v8",handbag:"unicode/1f45c.png?v8",handball_person:"unicode/1f93e.png?v8",handshake:"unicode/1f91d.png?v8",hankey:"unicode/1f4a9.png?v8",hash:"unicode/0023-20e3.png?v8",hatched_chick:"unicode/1f425.png?v8",hatching_chick:"unicode/1f423.png?v8",headphones:"unicode/1f3a7.png?v8",headstone:"unicode/1faa6.png?v8",health_worker:"unicode/1f9d1-2695.png?v8",hear_no_evil:"unicode/1f649.png?v8",heard_mcdonald_islands:"unicode/1f1ed-1f1f2.png?v8",heart:"unicode/2764.png?v8",heart_decoration:"unicode/1f49f.png?v8",heart_eyes:"unicode/1f60d.png?v8",heart_eyes_cat:"unicode/1f63b.png?v8",heart_on_fire:"unicode/2764-1f525.png?v8",heartbeat:"unicode/1f493.png?v8",heartpulse:"unicode/1f497.png?v8",hearts:"unicode/2665.png?v8",heavy_check_mark:"unicode/2714.png?v8",heavy_division_sign:"unicode/2797.png?v8",heavy_dollar_sign:"unicode/1f4b2.png?v8",heavy_exclamation_mark:"unicode/2757.png?v8",heavy_heart_exclamation:"unicode/2763.png?v8",heavy_minus_sign:"unicode/2796.png?v8",heavy_multiplication_x:"unicode/2716.png?v8",heavy_plus_sign:"unicode/2795.png?v8",hedgehog:"unicode/1f994.png?v8",helicopter:"unicode/1f681.png?v8",herb:"unicode/1f33f.png?v8",hibiscus:"unicode/1f33a.png?v8",high_brightness:"unicode/1f506.png?v8",high_heel:"unicode/1f460.png?v8",hiking_boot:"unicode/1f97e.png?v8",hindu_temple:"unicode/1f6d5.png?v8",hippopotamus:"unicode/1f99b.png?v8",hocho:"unicode/1f52a.png?v8",hole:"unicode/1f573.png?v8",honduras:"unicode/1f1ed-1f1f3.png?v8",honey_pot:"unicode/1f36f.png?v8",honeybee:"unicode/1f41d.png?v8",hong_kong:"unicode/1f1ed-1f1f0.png?v8",hook:"unicode/1fa9d.png?v8",horse:"unicode/1f434.png?v8",horse_racing:"unicode/1f3c7.png?v8",hospital:"unicode/1f3e5.png?v8",hot_face:"unicode/1f975.png?v8",hot_pepper:"unicode/1f336.png?v8",hotdog:"unicode/1f32d.png?v8",hotel:"unicode/1f3e8.png?v8",hotsprings:"unicode/2668.png?v8",hourglass:"unicode/231b.png?v8",hourglass_flowing_sand:"unicode/23f3.png?v8",house:"unicode/1f3e0.png?v8",house_with_garden:"unicode/1f3e1.png?v8",houses:"unicode/1f3d8.png?v8",hugs:"unicode/1f917.png?v8",hungary:"unicode/1f1ed-1f1fa.png?v8",hurtrealbad:"hurtrealbad.png?v8",hushed:"unicode/1f62f.png?v8",hut:"unicode/1f6d6.png?v8",ice_cream:"unicode/1f368.png?v8",ice_cube:"unicode/1f9ca.png?v8",ice_hockey:"unicode/1f3d2.png?v8",ice_skate:"unicode/26f8.png?v8",icecream:"unicode/1f366.png?v8",iceland:"unicode/1f1ee-1f1f8.png?v8",id:"unicode/1f194.png?v8",ideograph_advantage:"unicode/1f250.png?v8",imp:"unicode/1f47f.png?v8",inbox_tray:"unicode/1f4e5.png?v8",incoming_envelope:"unicode/1f4e8.png?v8",india:"unicode/1f1ee-1f1f3.png?v8",indonesia:"unicode/1f1ee-1f1e9.png?v8",infinity:"unicode/267e.png?v8",information_desk_person:"unicode/1f481.png?v8",information_source:"unicode/2139.png?v8",innocent:"unicode/1f607.png?v8",interrobang:"unicode/2049.png?v8",iphone:"unicode/1f4f1.png?v8",iran:"unicode/1f1ee-1f1f7.png?v8",iraq:"unicode/1f1ee-1f1f6.png?v8",ireland:"unicode/1f1ee-1f1ea.png?v8",isle_of_man:"unicode/1f1ee-1f1f2.png?v8",israel:"unicode/1f1ee-1f1f1.png?v8",it:"unicode/1f1ee-1f1f9.png?v8",izakaya_lantern:"unicode/1f3ee.png?v8",jack_o_lantern:"unicode/1f383.png?v8",jamaica:"unicode/1f1ef-1f1f2.png?v8",japan:"unicode/1f5fe.png?v8",japanese_castle:"unicode/1f3ef.png?v8",japanese_goblin:"unicode/1f47a.png?v8",japanese_ogre:"unicode/1f479.png?v8",jeans:"unicode/1f456.png?v8",jersey:"unicode/1f1ef-1f1ea.png?v8",jigsaw:"unicode/1f9e9.png?v8",jordan:"unicode/1f1ef-1f1f4.png?v8",joy:"unicode/1f602.png?v8",joy_cat:"unicode/1f639.png?v8",joystick:"unicode/1f579.png?v8",jp:"unicode/1f1ef-1f1f5.png?v8",judge:"unicode/1f9d1-2696.png?v8",juggling_person:"unicode/1f939.png?v8",kaaba:"unicode/1f54b.png?v8",kangaroo:"unicode/1f998.png?v8",kazakhstan:"unicode/1f1f0-1f1ff.png?v8",kenya:"unicode/1f1f0-1f1ea.png?v8",key:"unicode/1f511.png?v8",keyboard:"unicode/2328.png?v8",keycap_ten:"unicode/1f51f.png?v8",kick_scooter:"unicode/1f6f4.png?v8",kimono:"unicode/1f458.png?v8",kiribati:"unicode/1f1f0-1f1ee.png?v8",kiss:"unicode/1f48b.png?v8",kissing:"unicode/1f617.png?v8",kissing_cat:"unicode/1f63d.png?v8",kissing_closed_eyes:"unicode/1f61a.png?v8",kissing_heart:"unicode/1f618.png?v8",kissing_smiling_eyes:"unicode/1f619.png?v8",kite:"unicode/1fa81.png?v8",kiwi_fruit:"unicode/1f95d.png?v8",kneeling_man:"unicode/1f9ce-2642.png?v8",kneeling_person:"unicode/1f9ce.png?v8",kneeling_woman:"unicode/1f9ce-2640.png?v8",knife:"unicode/1f52a.png?v8",knot:"unicode/1faa2.png?v8",koala:"unicode/1f428.png?v8",koko:"unicode/1f201.png?v8",kosovo:"unicode/1f1fd-1f1f0.png?v8",kr:"unicode/1f1f0-1f1f7.png?v8",kuwait:"unicode/1f1f0-1f1fc.png?v8",kyrgyzstan:"unicode/1f1f0-1f1ec.png?v8",lab_coat:"unicode/1f97c.png?v8",label:"unicode/1f3f7.png?v8",lacrosse:"unicode/1f94d.png?v8",ladder:"unicode/1fa9c.png?v8",lady_beetle:"unicode/1f41e.png?v8",lantern:"unicode/1f3ee.png?v8",laos:"unicode/1f1f1-1f1e6.png?v8",large_blue_circle:"unicode/1f535.png?v8",large_blue_diamond:"unicode/1f537.png?v8",large_orange_diamond:"unicode/1f536.png?v8",last_quarter_moon:"unicode/1f317.png?v8",last_quarter_moon_with_face:"unicode/1f31c.png?v8",latin_cross:"unicode/271d.png?v8",latvia:"unicode/1f1f1-1f1fb.png?v8",laughing:"unicode/1f606.png?v8",leafy_green:"unicode/1f96c.png?v8",leaves:"unicode/1f343.png?v8",lebanon:"unicode/1f1f1-1f1e7.png?v8",ledger:"unicode/1f4d2.png?v8",left_luggage:"unicode/1f6c5.png?v8",left_right_arrow:"unicode/2194.png?v8",left_speech_bubble:"unicode/1f5e8.png?v8",leftwards_arrow_with_hook:"unicode/21a9.png?v8",leg:"unicode/1f9b5.png?v8",lemon:"unicode/1f34b.png?v8",leo:"unicode/264c.png?v8",leopard:"unicode/1f406.png?v8",lesotho:"unicode/1f1f1-1f1f8.png?v8",level_slider:"unicode/1f39a.png?v8",liberia:"unicode/1f1f1-1f1f7.png?v8",libra:"unicode/264e.png?v8",libya:"unicode/1f1f1-1f1fe.png?v8",liechtenstein:"unicode/1f1f1-1f1ee.png?v8",light_rail:"unicode/1f688.png?v8",link:"unicode/1f517.png?v8",lion:"unicode/1f981.png?v8",lips:"unicode/1f444.png?v8",lipstick:"unicode/1f484.png?v8",lithuania:"unicode/1f1f1-1f1f9.png?v8",lizard:"unicode/1f98e.png?v8",llama:"unicode/1f999.png?v8",lobster:"unicode/1f99e.png?v8",lock:"unicode/1f512.png?v8",lock_with_ink_pen:"unicode/1f50f.png?v8",lollipop:"unicode/1f36d.png?v8",long_drum:"unicode/1fa98.png?v8",loop:"unicode/27bf.png?v8",lotion_bottle:"unicode/1f9f4.png?v8",lotus_position:"unicode/1f9d8.png?v8",lotus_position_man:"unicode/1f9d8-2642.png?v8",lotus_position_woman:"unicode/1f9d8-2640.png?v8",loud_sound:"unicode/1f50a.png?v8",loudspeaker:"unicode/1f4e2.png?v8",love_hotel:"unicode/1f3e9.png?v8",love_letter:"unicode/1f48c.png?v8",love_you_gesture:"unicode/1f91f.png?v8",low_brightness:"unicode/1f505.png?v8",luggage:"unicode/1f9f3.png?v8",lungs:"unicode/1fac1.png?v8",luxembourg:"unicode/1f1f1-1f1fa.png?v8",lying_face:"unicode/1f925.png?v8",m:"unicode/24c2.png?v8",macau:"unicode/1f1f2-1f1f4.png?v8",macedonia:"unicode/1f1f2-1f1f0.png?v8",madagascar:"unicode/1f1f2-1f1ec.png?v8",mag:"unicode/1f50d.png?v8",mag_right:"unicode/1f50e.png?v8",mage:"unicode/1f9d9.png?v8",mage_man:"unicode/1f9d9-2642.png?v8",mage_woman:"unicode/1f9d9-2640.png?v8",magic_wand:"unicode/1fa84.png?v8",magnet:"unicode/1f9f2.png?v8",mahjong:"unicode/1f004.png?v8",mailbox:"unicode/1f4eb.png?v8",mailbox_closed:"unicode/1f4ea.png?v8",mailbox_with_mail:"unicode/1f4ec.png?v8",mailbox_with_no_mail:"unicode/1f4ed.png?v8",malawi:"unicode/1f1f2-1f1fc.png?v8",malaysia:"unicode/1f1f2-1f1fe.png?v8",maldives:"unicode/1f1f2-1f1fb.png?v8",male_detective:"unicode/1f575-2642.png?v8",male_sign:"unicode/2642.png?v8",mali:"unicode/1f1f2-1f1f1.png?v8",malta:"unicode/1f1f2-1f1f9.png?v8",mammoth:"unicode/1f9a3.png?v8",man:"unicode/1f468.png?v8",man_artist:"unicode/1f468-1f3a8.png?v8",man_astronaut:"unicode/1f468-1f680.png?v8",man_beard:"unicode/1f9d4-2642.png?v8",man_cartwheeling:"unicode/1f938-2642.png?v8",man_cook:"unicode/1f468-1f373.png?v8",man_dancing:"unicode/1f57a.png?v8",man_facepalming:"unicode/1f926-2642.png?v8",man_factory_worker:"unicode/1f468-1f3ed.png?v8",man_farmer:"unicode/1f468-1f33e.png?v8",man_feeding_baby:"unicode/1f468-1f37c.png?v8",man_firefighter:"unicode/1f468-1f692.png?v8",man_health_worker:"unicode/1f468-2695.png?v8",man_in_manual_wheelchair:"unicode/1f468-1f9bd.png?v8",man_in_motorized_wheelchair:"unicode/1f468-1f9bc.png?v8",man_in_tuxedo:"unicode/1f935-2642.png?v8",man_judge:"unicode/1f468-2696.png?v8",man_juggling:"unicode/1f939-2642.png?v8",man_mechanic:"unicode/1f468-1f527.png?v8",man_office_worker:"unicode/1f468-1f4bc.png?v8",man_pilot:"unicode/1f468-2708.png?v8",man_playing_handball:"unicode/1f93e-2642.png?v8",man_playing_water_polo:"unicode/1f93d-2642.png?v8",man_scientist:"unicode/1f468-1f52c.png?v8",man_shrugging:"unicode/1f937-2642.png?v8",man_singer:"unicode/1f468-1f3a4.png?v8",man_student:"unicode/1f468-1f393.png?v8",man_teacher:"unicode/1f468-1f3eb.png?v8",man_technologist:"unicode/1f468-1f4bb.png?v8",man_with_gua_pi_mao:"unicode/1f472.png?v8",man_with_probing_cane:"unicode/1f468-1f9af.png?v8",man_with_turban:"unicode/1f473-2642.png?v8",man_with_veil:"unicode/1f470-2642.png?v8",mandarin:"unicode/1f34a.png?v8",mango:"unicode/1f96d.png?v8",mans_shoe:"unicode/1f45e.png?v8",mantelpiece_clock:"unicode/1f570.png?v8",manual_wheelchair:"unicode/1f9bd.png?v8",maple_leaf:"unicode/1f341.png?v8",marshall_islands:"unicode/1f1f2-1f1ed.png?v8",martial_arts_uniform:"unicode/1f94b.png?v8",martinique:"unicode/1f1f2-1f1f6.png?v8",mask:"unicode/1f637.png?v8",massage:"unicode/1f486.png?v8",massage_man:"unicode/1f486-2642.png?v8",massage_woman:"unicode/1f486-2640.png?v8",mate:"unicode/1f9c9.png?v8",mauritania:"unicode/1f1f2-1f1f7.png?v8",mauritius:"unicode/1f1f2-1f1fa.png?v8",mayotte:"unicode/1f1fe-1f1f9.png?v8",meat_on_bone:"unicode/1f356.png?v8",mechanic:"unicode/1f9d1-1f527.png?v8",mechanical_arm:"unicode/1f9be.png?v8",mechanical_leg:"unicode/1f9bf.png?v8",medal_military:"unicode/1f396.png?v8",medal_sports:"unicode/1f3c5.png?v8",medical_symbol:"unicode/2695.png?v8",mega:"unicode/1f4e3.png?v8",melon:"unicode/1f348.png?v8",memo:"unicode/1f4dd.png?v8",men_wrestling:"unicode/1f93c-2642.png?v8",mending_heart:"unicode/2764-1fa79.png?v8",menorah:"unicode/1f54e.png?v8",mens:"unicode/1f6b9.png?v8",mermaid:"unicode/1f9dc-2640.png?v8",merman:"unicode/1f9dc-2642.png?v8",merperson:"unicode/1f9dc.png?v8",metal:"unicode/1f918.png?v8",metro:"unicode/1f687.png?v8",mexico:"unicode/1f1f2-1f1fd.png?v8",microbe:"unicode/1f9a0.png?v8",micronesia:"unicode/1f1eb-1f1f2.png?v8",microphone:"unicode/1f3a4.png?v8",microscope:"unicode/1f52c.png?v8",middle_finger:"unicode/1f595.png?v8",military_helmet:"unicode/1fa96.png?v8",milk_glass:"unicode/1f95b.png?v8",milky_way:"unicode/1f30c.png?v8",minibus:"unicode/1f690.png?v8",minidisc:"unicode/1f4bd.png?v8",mirror:"unicode/1fa9e.png?v8",mobile_phone_off:"unicode/1f4f4.png?v8",moldova:"unicode/1f1f2-1f1e9.png?v8",monaco:"unicode/1f1f2-1f1e8.png?v8",money_mouth_face:"unicode/1f911.png?v8",money_with_wings:"unicode/1f4b8.png?v8",moneybag:"unicode/1f4b0.png?v8",mongolia:"unicode/1f1f2-1f1f3.png?v8",monkey:"unicode/1f412.png?v8",monkey_face:"unicode/1f435.png?v8",monocle_face:"unicode/1f9d0.png?v8",monorail:"unicode/1f69d.png?v8",montenegro:"unicode/1f1f2-1f1ea.png?v8",montserrat:"unicode/1f1f2-1f1f8.png?v8",moon:"unicode/1f314.png?v8",moon_cake:"unicode/1f96e.png?v8",morocco:"unicode/1f1f2-1f1e6.png?v8",mortar_board:"unicode/1f393.png?v8",mosque:"unicode/1f54c.png?v8",mosquito:"unicode/1f99f.png?v8",motor_boat:"unicode/1f6e5.png?v8",motor_scooter:"unicode/1f6f5.png?v8",motorcycle:"unicode/1f3cd.png?v8",motorized_wheelchair:"unicode/1f9bc.png?v8",motorway:"unicode/1f6e3.png?v8",mount_fuji:"unicode/1f5fb.png?v8",mountain:"unicode/26f0.png?v8",mountain_bicyclist:"unicode/1f6b5.png?v8",mountain_biking_man:"unicode/1f6b5-2642.png?v8",mountain_biking_woman:"unicode/1f6b5-2640.png?v8",mountain_cableway:"unicode/1f6a0.png?v8",mountain_railway:"unicode/1f69e.png?v8",mountain_snow:"unicode/1f3d4.png?v8",mouse:"unicode/1f42d.png?v8",mouse2:"unicode/1f401.png?v8",mouse_trap:"unicode/1faa4.png?v8",movie_camera:"unicode/1f3a5.png?v8",moyai:"unicode/1f5ff.png?v8",mozambique:"unicode/1f1f2-1f1ff.png?v8",mrs_claus:"unicode/1f936.png?v8",muscle:"unicode/1f4aa.png?v8",mushroom:"unicode/1f344.png?v8",musical_keyboard:"unicode/1f3b9.png?v8",musical_note:"unicode/1f3b5.png?v8",musical_score:"unicode/1f3bc.png?v8",mute:"unicode/1f507.png?v8",mx_claus:"unicode/1f9d1-1f384.png?v8",myanmar:"unicode/1f1f2-1f1f2.png?v8",nail_care:"unicode/1f485.png?v8",name_badge:"unicode/1f4db.png?v8",namibia:"unicode/1f1f3-1f1e6.png?v8",national_park:"unicode/1f3de.png?v8",nauru:"unicode/1f1f3-1f1f7.png?v8",nauseated_face:"unicode/1f922.png?v8",nazar_amulet:"unicode/1f9ff.png?v8",neckbeard:"neckbeard.png?v8",necktie:"unicode/1f454.png?v8",negative_squared_cross_mark:"unicode/274e.png?v8",nepal:"unicode/1f1f3-1f1f5.png?v8",nerd_face:"unicode/1f913.png?v8",nesting_dolls:"unicode/1fa86.png?v8",netherlands:"unicode/1f1f3-1f1f1.png?v8",neutral_face:"unicode/1f610.png?v8",new:"unicode/1f195.png?v8",new_caledonia:"unicode/1f1f3-1f1e8.png?v8",new_moon:"unicode/1f311.png?v8",new_moon_with_face:"unicode/1f31a.png?v8",new_zealand:"unicode/1f1f3-1f1ff.png?v8",newspaper:"unicode/1f4f0.png?v8",newspaper_roll:"unicode/1f5de.png?v8",next_track_button:"unicode/23ed.png?v8",ng:"unicode/1f196.png?v8",ng_man:"unicode/1f645-2642.png?v8",ng_woman:"unicode/1f645-2640.png?v8",nicaragua:"unicode/1f1f3-1f1ee.png?v8",niger:"unicode/1f1f3-1f1ea.png?v8",nigeria:"unicode/1f1f3-1f1ec.png?v8",night_with_stars:"unicode/1f303.png?v8",nine:"unicode/0039-20e3.png?v8",ninja:"unicode/1f977.png?v8",niue:"unicode/1f1f3-1f1fa.png?v8",no_bell:"unicode/1f515.png?v8",no_bicycles:"unicode/1f6b3.png?v8",no_entry:"unicode/26d4.png?v8",no_entry_sign:"unicode/1f6ab.png?v8",no_good:"unicode/1f645.png?v8",no_good_man:"unicode/1f645-2642.png?v8",no_good_woman:"unicode/1f645-2640.png?v8",no_mobile_phones:"unicode/1f4f5.png?v8",no_mouth:"unicode/1f636.png?v8",no_pedestrians:"unicode/1f6b7.png?v8",no_smoking:"unicode/1f6ad.png?v8","non-potable_water":"unicode/1f6b1.png?v8",norfolk_island:"unicode/1f1f3-1f1eb.png?v8",north_korea:"unicode/1f1f0-1f1f5.png?v8",northern_mariana_islands:"unicode/1f1f2-1f1f5.png?v8",norway:"unicode/1f1f3-1f1f4.png?v8",nose:"unicode/1f443.png?v8",notebook:"unicode/1f4d3.png?v8",notebook_with_decorative_cover:"unicode/1f4d4.png?v8",notes:"unicode/1f3b6.png?v8",nut_and_bolt:"unicode/1f529.png?v8",o:"unicode/2b55.png?v8",o2:"unicode/1f17e.png?v8",ocean:"unicode/1f30a.png?v8",octocat:"octocat.png?v8",octopus:"unicode/1f419.png?v8",oden:"unicode/1f362.png?v8",office:"unicode/1f3e2.png?v8",office_worker:"unicode/1f9d1-1f4bc.png?v8",oil_drum:"unicode/1f6e2.png?v8",ok:"unicode/1f197.png?v8",ok_hand:"unicode/1f44c.png?v8",ok_man:"unicode/1f646-2642.png?v8",ok_person:"unicode/1f646.png?v8",ok_woman:"unicode/1f646-2640.png?v8",old_key:"unicode/1f5dd.png?v8",older_adult:"unicode/1f9d3.png?v8",older_man:"unicode/1f474.png?v8",older_woman:"unicode/1f475.png?v8",olive:"unicode/1fad2.png?v8",om:"unicode/1f549.png?v8",oman:"unicode/1f1f4-1f1f2.png?v8",on:"unicode/1f51b.png?v8",oncoming_automobile:"unicode/1f698.png?v8",oncoming_bus:"unicode/1f68d.png?v8",oncoming_police_car:"unicode/1f694.png?v8",oncoming_taxi:"unicode/1f696.png?v8",one:"unicode/0031-20e3.png?v8",one_piece_swimsuit:"unicode/1fa71.png?v8",onion:"unicode/1f9c5.png?v8",open_book:"unicode/1f4d6.png?v8",open_file_folder:"unicode/1f4c2.png?v8",open_hands:"unicode/1f450.png?v8",open_mouth:"unicode/1f62e.png?v8",open_umbrella:"unicode/2602.png?v8",ophiuchus:"unicode/26ce.png?v8",orange:"unicode/1f34a.png?v8",orange_book:"unicode/1f4d9.png?v8",orange_circle:"unicode/1f7e0.png?v8",orange_heart:"unicode/1f9e1.png?v8",orange_square:"unicode/1f7e7.png?v8",orangutan:"unicode/1f9a7.png?v8",orthodox_cross:"unicode/2626.png?v8",otter:"unicode/1f9a6.png?v8",outbox_tray:"unicode/1f4e4.png?v8",owl:"unicode/1f989.png?v8",ox:"unicode/1f402.png?v8",oyster:"unicode/1f9aa.png?v8",package:"unicode/1f4e6.png?v8",page_facing_up:"unicode/1f4c4.png?v8",page_with_curl:"unicode/1f4c3.png?v8",pager:"unicode/1f4df.png?v8",paintbrush:"unicode/1f58c.png?v8",pakistan:"unicode/1f1f5-1f1f0.png?v8",palau:"unicode/1f1f5-1f1fc.png?v8",palestinian_territories:"unicode/1f1f5-1f1f8.png?v8",palm_tree:"unicode/1f334.png?v8",palms_up_together:"unicode/1f932.png?v8",panama:"unicode/1f1f5-1f1e6.png?v8",pancakes:"unicode/1f95e.png?v8",panda_face:"unicode/1f43c.png?v8",paperclip:"unicode/1f4ce.png?v8",paperclips:"unicode/1f587.png?v8",papua_new_guinea:"unicode/1f1f5-1f1ec.png?v8",parachute:"unicode/1fa82.png?v8",paraguay:"unicode/1f1f5-1f1fe.png?v8",parasol_on_ground:"unicode/26f1.png?v8",parking:"unicode/1f17f.png?v8",parrot:"unicode/1f99c.png?v8",part_alternation_mark:"unicode/303d.png?v8",partly_sunny:"unicode/26c5.png?v8",partying_face:"unicode/1f973.png?v8",passenger_ship:"unicode/1f6f3.png?v8",passport_control:"unicode/1f6c2.png?v8",pause_button:"unicode/23f8.png?v8",paw_prints:"unicode/1f43e.png?v8",peace_symbol:"unicode/262e.png?v8",peach:"unicode/1f351.png?v8",peacock:"unicode/1f99a.png?v8",peanuts:"unicode/1f95c.png?v8",pear:"unicode/1f350.png?v8",pen:"unicode/1f58a.png?v8",pencil:"unicode/1f4dd.png?v8",pencil2:"unicode/270f.png?v8",penguin:"unicode/1f427.png?v8",pensive:"unicode/1f614.png?v8",people_holding_hands:"unicode/1f9d1-1f91d-1f9d1.png?v8",people_hugging:"unicode/1fac2.png?v8",performing_arts:"unicode/1f3ad.png?v8",persevere:"unicode/1f623.png?v8",person_bald:"unicode/1f9d1-1f9b2.png?v8",person_curly_hair:"unicode/1f9d1-1f9b1.png?v8",person_feeding_baby:"unicode/1f9d1-1f37c.png?v8",person_fencing:"unicode/1f93a.png?v8",person_in_manual_wheelchair:"unicode/1f9d1-1f9bd.png?v8",person_in_motorized_wheelchair:"unicode/1f9d1-1f9bc.png?v8",person_in_tuxedo:"unicode/1f935.png?v8",person_red_hair:"unicode/1f9d1-1f9b0.png?v8",person_white_hair:"unicode/1f9d1-1f9b3.png?v8",person_with_probing_cane:"unicode/1f9d1-1f9af.png?v8",person_with_turban:"unicode/1f473.png?v8",person_with_veil:"unicode/1f470.png?v8",peru:"unicode/1f1f5-1f1ea.png?v8",petri_dish:"unicode/1f9eb.png?v8",philippines:"unicode/1f1f5-1f1ed.png?v8",phone:"unicode/260e.png?v8",pick:"unicode/26cf.png?v8",pickup_truck:"unicode/1f6fb.png?v8",pie:"unicode/1f967.png?v8",pig:"unicode/1f437.png?v8",pig2:"unicode/1f416.png?v8",pig_nose:"unicode/1f43d.png?v8",pill:"unicode/1f48a.png?v8",pilot:"unicode/1f9d1-2708.png?v8",pinata:"unicode/1fa85.png?v8",pinched_fingers:"unicode/1f90c.png?v8",pinching_hand:"unicode/1f90f.png?v8",pineapple:"unicode/1f34d.png?v8",ping_pong:"unicode/1f3d3.png?v8",pirate_flag:"unicode/1f3f4-2620.png?v8",pisces:"unicode/2653.png?v8",pitcairn_islands:"unicode/1f1f5-1f1f3.png?v8",pizza:"unicode/1f355.png?v8",placard:"unicode/1faa7.png?v8",place_of_worship:"unicode/1f6d0.png?v8",plate_with_cutlery:"unicode/1f37d.png?v8",play_or_pause_button:"unicode/23ef.png?v8",pleading_face:"unicode/1f97a.png?v8",plunger:"unicode/1faa0.png?v8",point_down:"unicode/1f447.png?v8",point_left:"unicode/1f448.png?v8",point_right:"unicode/1f449.png?v8",point_up:"unicode/261d.png?v8",point_up_2:"unicode/1f446.png?v8",poland:"unicode/1f1f5-1f1f1.png?v8",polar_bear:"unicode/1f43b-2744.png?v8",police_car:"unicode/1f693.png?v8",police_officer:"unicode/1f46e.png?v8",policeman:"unicode/1f46e-2642.png?v8",policewoman:"unicode/1f46e-2640.png?v8",poodle:"unicode/1f429.png?v8",poop:"unicode/1f4a9.png?v8",popcorn:"unicode/1f37f.png?v8",portugal:"unicode/1f1f5-1f1f9.png?v8",post_office:"unicode/1f3e3.png?v8",postal_horn:"unicode/1f4ef.png?v8",postbox:"unicode/1f4ee.png?v8",potable_water:"unicode/1f6b0.png?v8",potato:"unicode/1f954.png?v8",potted_plant:"unicode/1fab4.png?v8",pouch:"unicode/1f45d.png?v8",poultry_leg:"unicode/1f357.png?v8",pound:"unicode/1f4b7.png?v8",pout:"unicode/1f621.png?v8",pouting_cat:"unicode/1f63e.png?v8",pouting_face:"unicode/1f64e.png?v8",pouting_man:"unicode/1f64e-2642.png?v8",pouting_woman:"unicode/1f64e-2640.png?v8",pray:"unicode/1f64f.png?v8",prayer_beads:"unicode/1f4ff.png?v8",pregnant_woman:"unicode/1f930.png?v8",pretzel:"unicode/1f968.png?v8",previous_track_button:"unicode/23ee.png?v8",prince:"unicode/1f934.png?v8",princess:"unicode/1f478.png?v8",printer:"unicode/1f5a8.png?v8",probing_cane:"unicode/1f9af.png?v8",puerto_rico:"unicode/1f1f5-1f1f7.png?v8",punch:"unicode/1f44a.png?v8",purple_circle:"unicode/1f7e3.png?v8",purple_heart:"unicode/1f49c.png?v8",purple_square:"unicode/1f7ea.png?v8",purse:"unicode/1f45b.png?v8",pushpin:"unicode/1f4cc.png?v8",put_litter_in_its_place:"unicode/1f6ae.png?v8",qatar:"unicode/1f1f6-1f1e6.png?v8",question:"unicode/2753.png?v8",rabbit:"unicode/1f430.png?v8",rabbit2:"unicode/1f407.png?v8",raccoon:"unicode/1f99d.png?v8",racehorse:"unicode/1f40e.png?v8",racing_car:"unicode/1f3ce.png?v8",radio:"unicode/1f4fb.png?v8",radio_button:"unicode/1f518.png?v8",radioactive:"unicode/2622.png?v8",rage:"unicode/1f621.png?v8",rage1:"rage1.png?v8",rage2:"rage2.png?v8",rage3:"rage3.png?v8",rage4:"rage4.png?v8",railway_car:"unicode/1f683.png?v8",railway_track:"unicode/1f6e4.png?v8",rainbow:"unicode/1f308.png?v8",rainbow_flag:"unicode/1f3f3-1f308.png?v8",raised_back_of_hand:"unicode/1f91a.png?v8",raised_eyebrow:"unicode/1f928.png?v8",raised_hand:"unicode/270b.png?v8",raised_hand_with_fingers_splayed:"unicode/1f590.png?v8",raised_hands:"unicode/1f64c.png?v8",raising_hand:"unicode/1f64b.png?v8",raising_hand_man:"unicode/1f64b-2642.png?v8",raising_hand_woman:"unicode/1f64b-2640.png?v8",ram:"unicode/1f40f.png?v8",ramen:"unicode/1f35c.png?v8",rat:"unicode/1f400.png?v8",razor:"unicode/1fa92.png?v8",receipt:"unicode/1f9fe.png?v8",record_button:"unicode/23fa.png?v8",recycle:"unicode/267b.png?v8",red_car:"unicode/1f697.png?v8",red_circle:"unicode/1f534.png?v8",red_envelope:"unicode/1f9e7.png?v8",red_haired_man:"unicode/1f468-1f9b0.png?v8",red_haired_woman:"unicode/1f469-1f9b0.png?v8",red_square:"unicode/1f7e5.png?v8",registered:"unicode/00ae.png?v8",relaxed:"unicode/263a.png?v8",relieved:"unicode/1f60c.png?v8",reminder_ribbon:"unicode/1f397.png?v8",repeat:"unicode/1f501.png?v8",repeat_one:"unicode/1f502.png?v8",rescue_worker_helmet:"unicode/26d1.png?v8",restroom:"unicode/1f6bb.png?v8",reunion:"unicode/1f1f7-1f1ea.png?v8",revolving_hearts:"unicode/1f49e.png?v8",rewind:"unicode/23ea.png?v8",rhinoceros:"unicode/1f98f.png?v8",ribbon:"unicode/1f380.png?v8",rice:"unicode/1f35a.png?v8",rice_ball:"unicode/1f359.png?v8",rice_cracker:"unicode/1f358.png?v8",rice_scene:"unicode/1f391.png?v8",right_anger_bubble:"unicode/1f5ef.png?v8",ring:"unicode/1f48d.png?v8",ringed_planet:"unicode/1fa90.png?v8",robot:"unicode/1f916.png?v8",rock:"unicode/1faa8.png?v8",rocket:"unicode/1f680.png?v8",rofl:"unicode/1f923.png?v8",roll_eyes:"unicode/1f644.png?v8",roll_of_paper:"unicode/1f9fb.png?v8",roller_coaster:"unicode/1f3a2.png?v8",roller_skate:"unicode/1f6fc.png?v8",romania:"unicode/1f1f7-1f1f4.png?v8",rooster:"unicode/1f413.png?v8",rose:"unicode/1f339.png?v8",rosette:"unicode/1f3f5.png?v8",rotating_light:"unicode/1f6a8.png?v8",round_pushpin:"unicode/1f4cd.png?v8",rowboat:"unicode/1f6a3.png?v8",rowing_man:"unicode/1f6a3-2642.png?v8",rowing_woman:"unicode/1f6a3-2640.png?v8",ru:"unicode/1f1f7-1f1fa.png?v8",rugby_football:"unicode/1f3c9.png?v8",runner:"unicode/1f3c3.png?v8",running:"unicode/1f3c3.png?v8",running_man:"unicode/1f3c3-2642.png?v8",running_shirt_with_sash:"unicode/1f3bd.png?v8",running_woman:"unicode/1f3c3-2640.png?v8",rwanda:"unicode/1f1f7-1f1fc.png?v8",sa:"unicode/1f202.png?v8",safety_pin:"unicode/1f9f7.png?v8",safety_vest:"unicode/1f9ba.png?v8",sagittarius:"unicode/2650.png?v8",sailboat:"unicode/26f5.png?v8",sake:"unicode/1f376.png?v8",salt:"unicode/1f9c2.png?v8",samoa:"unicode/1f1fc-1f1f8.png?v8",san_marino:"unicode/1f1f8-1f1f2.png?v8",sandal:"unicode/1f461.png?v8",sandwich:"unicode/1f96a.png?v8",santa:"unicode/1f385.png?v8",sao_tome_principe:"unicode/1f1f8-1f1f9.png?v8",sari:"unicode/1f97b.png?v8",sassy_man:"unicode/1f481-2642.png?v8",sassy_woman:"unicode/1f481-2640.png?v8",satellite:"unicode/1f4e1.png?v8",satisfied:"unicode/1f606.png?v8",saudi_arabia:"unicode/1f1f8-1f1e6.png?v8",sauna_man:"unicode/1f9d6-2642.png?v8",sauna_person:"unicode/1f9d6.png?v8",sauna_woman:"unicode/1f9d6-2640.png?v8",sauropod:"unicode/1f995.png?v8",saxophone:"unicode/1f3b7.png?v8",scarf:"unicode/1f9e3.png?v8",school:"unicode/1f3eb.png?v8",school_satchel:"unicode/1f392.png?v8",scientist:"unicode/1f9d1-1f52c.png?v8",scissors:"unicode/2702.png?v8",scorpion:"unicode/1f982.png?v8",scorpius:"unicode/264f.png?v8",scotland:"unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png?v8",scream:"unicode/1f631.png?v8",scream_cat:"unicode/1f640.png?v8",screwdriver:"unicode/1fa9b.png?v8",scroll:"unicode/1f4dc.png?v8",seal:"unicode/1f9ad.png?v8",seat:"unicode/1f4ba.png?v8",secret:"unicode/3299.png?v8",see_no_evil:"unicode/1f648.png?v8",seedling:"unicode/1f331.png?v8",selfie:"unicode/1f933.png?v8",senegal:"unicode/1f1f8-1f1f3.png?v8",serbia:"unicode/1f1f7-1f1f8.png?v8",service_dog:"unicode/1f415-1f9ba.png?v8",seven:"unicode/0037-20e3.png?v8",sewing_needle:"unicode/1faa1.png?v8",seychelles:"unicode/1f1f8-1f1e8.png?v8",shallow_pan_of_food:"unicode/1f958.png?v8",shamrock:"unicode/2618.png?v8",shark:"unicode/1f988.png?v8",shaved_ice:"unicode/1f367.png?v8",sheep:"unicode/1f411.png?v8",shell:"unicode/1f41a.png?v8",shield:"unicode/1f6e1.png?v8",shinto_shrine:"unicode/26e9.png?v8",ship:"unicode/1f6a2.png?v8",shipit:"shipit.png?v8",shirt:"unicode/1f455.png?v8",shit:"unicode/1f4a9.png?v8",shoe:"unicode/1f45e.png?v8",shopping:"unicode/1f6cd.png?v8",shopping_cart:"unicode/1f6d2.png?v8",shorts:"unicode/1fa73.png?v8",shower:"unicode/1f6bf.png?v8",shrimp:"unicode/1f990.png?v8",shrug:"unicode/1f937.png?v8",shushing_face:"unicode/1f92b.png?v8",sierra_leone:"unicode/1f1f8-1f1f1.png?v8",signal_strength:"unicode/1f4f6.png?v8",singapore:"unicode/1f1f8-1f1ec.png?v8",singer:"unicode/1f9d1-1f3a4.png?v8",sint_maarten:"unicode/1f1f8-1f1fd.png?v8",six:"unicode/0036-20e3.png?v8",six_pointed_star:"unicode/1f52f.png?v8",skateboard:"unicode/1f6f9.png?v8",ski:"unicode/1f3bf.png?v8",skier:"unicode/26f7.png?v8",skull:"unicode/1f480.png?v8",skull_and_crossbones:"unicode/2620.png?v8",skunk:"unicode/1f9a8.png?v8",sled:"unicode/1f6f7.png?v8",sleeping:"unicode/1f634.png?v8",sleeping_bed:"unicode/1f6cc.png?v8",sleepy:"unicode/1f62a.png?v8",slightly_frowning_face:"unicode/1f641.png?v8",slightly_smiling_face:"unicode/1f642.png?v8",slot_machine:"unicode/1f3b0.png?v8",sloth:"unicode/1f9a5.png?v8",slovakia:"unicode/1f1f8-1f1f0.png?v8",slovenia:"unicode/1f1f8-1f1ee.png?v8",small_airplane:"unicode/1f6e9.png?v8",small_blue_diamond:"unicode/1f539.png?v8",small_orange_diamond:"unicode/1f538.png?v8",small_red_triangle:"unicode/1f53a.png?v8",small_red_triangle_down:"unicode/1f53b.png?v8",smile:"unicode/1f604.png?v8",smile_cat:"unicode/1f638.png?v8",smiley:"unicode/1f603.png?v8",smiley_cat:"unicode/1f63a.png?v8",smiling_face_with_tear:"unicode/1f972.png?v8",smiling_face_with_three_hearts:"unicode/1f970.png?v8",smiling_imp:"unicode/1f608.png?v8",smirk:"unicode/1f60f.png?v8",smirk_cat:"unicode/1f63c.png?v8",smoking:"unicode/1f6ac.png?v8",snail:"unicode/1f40c.png?v8",snake:"unicode/1f40d.png?v8",sneezing_face:"unicode/1f927.png?v8",snowboarder:"unicode/1f3c2.png?v8",snowflake:"unicode/2744.png?v8",snowman:"unicode/26c4.png?v8",snowman_with_snow:"unicode/2603.png?v8",soap:"unicode/1f9fc.png?v8",sob:"unicode/1f62d.png?v8",soccer:"unicode/26bd.png?v8",socks:"unicode/1f9e6.png?v8",softball:"unicode/1f94e.png?v8",solomon_islands:"unicode/1f1f8-1f1e7.png?v8",somalia:"unicode/1f1f8-1f1f4.png?v8",soon:"unicode/1f51c.png?v8",sos:"unicode/1f198.png?v8",sound:"unicode/1f509.png?v8",south_africa:"unicode/1f1ff-1f1e6.png?v8",south_georgia_south_sandwich_islands:"unicode/1f1ec-1f1f8.png?v8",south_sudan:"unicode/1f1f8-1f1f8.png?v8",space_invader:"unicode/1f47e.png?v8",spades:"unicode/2660.png?v8",spaghetti:"unicode/1f35d.png?v8",sparkle:"unicode/2747.png?v8",sparkler:"unicode/1f387.png?v8",sparkles:"unicode/2728.png?v8",sparkling_heart:"unicode/1f496.png?v8",speak_no_evil:"unicode/1f64a.png?v8",speaker:"unicode/1f508.png?v8",speaking_head:"unicode/1f5e3.png?v8",speech_balloon:"unicode/1f4ac.png?v8",speedboat:"unicode/1f6a4.png?v8",spider:"unicode/1f577.png?v8",spider_web:"unicode/1f578.png?v8",spiral_calendar:"unicode/1f5d3.png?v8",spiral_notepad:"unicode/1f5d2.png?v8",sponge:"unicode/1f9fd.png?v8",spoon:"unicode/1f944.png?v8",squid:"unicode/1f991.png?v8",sri_lanka:"unicode/1f1f1-1f1f0.png?v8",st_barthelemy:"unicode/1f1e7-1f1f1.png?v8",st_helena:"unicode/1f1f8-1f1ed.png?v8",st_kitts_nevis:"unicode/1f1f0-1f1f3.png?v8",st_lucia:"unicode/1f1f1-1f1e8.png?v8",st_martin:"unicode/1f1f2-1f1eb.png?v8",st_pierre_miquelon:"unicode/1f1f5-1f1f2.png?v8",st_vincent_grenadines:"unicode/1f1fb-1f1e8.png?v8",stadium:"unicode/1f3df.png?v8",standing_man:"unicode/1f9cd-2642.png?v8",standing_person:"unicode/1f9cd.png?v8",standing_woman:"unicode/1f9cd-2640.png?v8",star:"unicode/2b50.png?v8",star2:"unicode/1f31f.png?v8",star_and_crescent:"unicode/262a.png?v8",star_of_david:"unicode/2721.png?v8",star_struck:"unicode/1f929.png?v8",stars:"unicode/1f320.png?v8",station:"unicode/1f689.png?v8",statue_of_liberty:"unicode/1f5fd.png?v8",steam_locomotive:"unicode/1f682.png?v8",stethoscope:"unicode/1fa7a.png?v8",stew:"unicode/1f372.png?v8",stop_button:"unicode/23f9.png?v8",stop_sign:"unicode/1f6d1.png?v8",stopwatch:"unicode/23f1.png?v8",straight_ruler:"unicode/1f4cf.png?v8",strawberry:"unicode/1f353.png?v8",stuck_out_tongue:"unicode/1f61b.png?v8",stuck_out_tongue_closed_eyes:"unicode/1f61d.png?v8",stuck_out_tongue_winking_eye:"unicode/1f61c.png?v8",student:"unicode/1f9d1-1f393.png?v8",studio_microphone:"unicode/1f399.png?v8",stuffed_flatbread:"unicode/1f959.png?v8",sudan:"unicode/1f1f8-1f1e9.png?v8",sun_behind_large_cloud:"unicode/1f325.png?v8",sun_behind_rain_cloud:"unicode/1f326.png?v8",sun_behind_small_cloud:"unicode/1f324.png?v8",sun_with_face:"unicode/1f31e.png?v8",sunflower:"unicode/1f33b.png?v8",sunglasses:"unicode/1f60e.png?v8",sunny:"unicode/2600.png?v8",sunrise:"unicode/1f305.png?v8",sunrise_over_mountains:"unicode/1f304.png?v8",superhero:"unicode/1f9b8.png?v8",superhero_man:"unicode/1f9b8-2642.png?v8",superhero_woman:"unicode/1f9b8-2640.png?v8",supervillain:"unicode/1f9b9.png?v8",supervillain_man:"unicode/1f9b9-2642.png?v8",supervillain_woman:"unicode/1f9b9-2640.png?v8",surfer:"unicode/1f3c4.png?v8",surfing_man:"unicode/1f3c4-2642.png?v8",surfing_woman:"unicode/1f3c4-2640.png?v8",suriname:"unicode/1f1f8-1f1f7.png?v8",sushi:"unicode/1f363.png?v8",suspect:"suspect.png?v8",suspension_railway:"unicode/1f69f.png?v8",svalbard_jan_mayen:"unicode/1f1f8-1f1ef.png?v8",swan:"unicode/1f9a2.png?v8",swaziland:"unicode/1f1f8-1f1ff.png?v8",sweat:"unicode/1f613.png?v8",sweat_drops:"unicode/1f4a6.png?v8",sweat_smile:"unicode/1f605.png?v8",sweden:"unicode/1f1f8-1f1ea.png?v8",sweet_potato:"unicode/1f360.png?v8",swim_brief:"unicode/1fa72.png?v8",swimmer:"unicode/1f3ca.png?v8",swimming_man:"unicode/1f3ca-2642.png?v8",swimming_woman:"unicode/1f3ca-2640.png?v8",switzerland:"unicode/1f1e8-1f1ed.png?v8",symbols:"unicode/1f523.png?v8",synagogue:"unicode/1f54d.png?v8",syria:"unicode/1f1f8-1f1fe.png?v8",syringe:"unicode/1f489.png?v8","t-rex":"unicode/1f996.png?v8",taco:"unicode/1f32e.png?v8",tada:"unicode/1f389.png?v8",taiwan:"unicode/1f1f9-1f1fc.png?v8",tajikistan:"unicode/1f1f9-1f1ef.png?v8",takeout_box:"unicode/1f961.png?v8",tamale:"unicode/1fad4.png?v8",tanabata_tree:"unicode/1f38b.png?v8",tangerine:"unicode/1f34a.png?v8",tanzania:"unicode/1f1f9-1f1ff.png?v8",taurus:"unicode/2649.png?v8",taxi:"unicode/1f695.png?v8",tea:"unicode/1f375.png?v8",teacher:"unicode/1f9d1-1f3eb.png?v8",teapot:"unicode/1fad6.png?v8",technologist:"unicode/1f9d1-1f4bb.png?v8",teddy_bear:"unicode/1f9f8.png?v8",telephone:"unicode/260e.png?v8",telephone_receiver:"unicode/1f4de.png?v8",telescope:"unicode/1f52d.png?v8",tennis:"unicode/1f3be.png?v8",tent:"unicode/26fa.png?v8",test_tube:"unicode/1f9ea.png?v8",thailand:"unicode/1f1f9-1f1ed.png?v8",thermometer:"unicode/1f321.png?v8",thinking:"unicode/1f914.png?v8",thong_sandal:"unicode/1fa74.png?v8",thought_balloon:"unicode/1f4ad.png?v8",thread:"unicode/1f9f5.png?v8",three:"unicode/0033-20e3.png?v8",thumbsdown:"unicode/1f44e.png?v8",thumbsup:"unicode/1f44d.png?v8",ticket:"unicode/1f3ab.png?v8",tickets:"unicode/1f39f.png?v8",tiger:"unicode/1f42f.png?v8",tiger2:"unicode/1f405.png?v8",timer_clock:"unicode/23f2.png?v8",timor_leste:"unicode/1f1f9-1f1f1.png?v8",tipping_hand_man:"unicode/1f481-2642.png?v8",tipping_hand_person:"unicode/1f481.png?v8",tipping_hand_woman:"unicode/1f481-2640.png?v8",tired_face:"unicode/1f62b.png?v8",tm:"unicode/2122.png?v8",togo:"unicode/1f1f9-1f1ec.png?v8",toilet:"unicode/1f6bd.png?v8",tokelau:"unicode/1f1f9-1f1f0.png?v8",tokyo_tower:"unicode/1f5fc.png?v8",tomato:"unicode/1f345.png?v8",tonga:"unicode/1f1f9-1f1f4.png?v8",tongue:"unicode/1f445.png?v8",toolbox:"unicode/1f9f0.png?v8",tooth:"unicode/1f9b7.png?v8",toothbrush:"unicode/1faa5.png?v8",top:"unicode/1f51d.png?v8",tophat:"unicode/1f3a9.png?v8",tornado:"unicode/1f32a.png?v8",tr:"unicode/1f1f9-1f1f7.png?v8",trackball:"unicode/1f5b2.png?v8",tractor:"unicode/1f69c.png?v8",traffic_light:"unicode/1f6a5.png?v8",train:"unicode/1f68b.png?v8",train2:"unicode/1f686.png?v8",tram:"unicode/1f68a.png?v8",transgender_flag:"unicode/1f3f3-26a7.png?v8",transgender_symbol:"unicode/26a7.png?v8",triangular_flag_on_post:"unicode/1f6a9.png?v8",triangular_ruler:"unicode/1f4d0.png?v8",trident:"unicode/1f531.png?v8",trinidad_tobago:"unicode/1f1f9-1f1f9.png?v8",tristan_da_cunha:"unicode/1f1f9-1f1e6.png?v8",triumph:"unicode/1f624.png?v8",trolleybus:"unicode/1f68e.png?v8",trollface:"trollface.png?v8",trophy:"unicode/1f3c6.png?v8",tropical_drink:"unicode/1f379.png?v8",tropical_fish:"unicode/1f420.png?v8",truck:"unicode/1f69a.png?v8",trumpet:"unicode/1f3ba.png?v8",tshirt:"unicode/1f455.png?v8",tulip:"unicode/1f337.png?v8",tumbler_glass:"unicode/1f943.png?v8",tunisia:"unicode/1f1f9-1f1f3.png?v8",turkey:"unicode/1f983.png?v8",turkmenistan:"unicode/1f1f9-1f1f2.png?v8",turks_caicos_islands:"unicode/1f1f9-1f1e8.png?v8",turtle:"unicode/1f422.png?v8",tuvalu:"unicode/1f1f9-1f1fb.png?v8",tv:"unicode/1f4fa.png?v8",twisted_rightwards_arrows:"unicode/1f500.png?v8",two:"unicode/0032-20e3.png?v8",two_hearts:"unicode/1f495.png?v8",two_men_holding_hands:"unicode/1f46c.png?v8",two_women_holding_hands:"unicode/1f46d.png?v8",u5272:"unicode/1f239.png?v8",u5408:"unicode/1f234.png?v8",u55b6:"unicode/1f23a.png?v8",u6307:"unicode/1f22f.png?v8",u6708:"unicode/1f237.png?v8",u6709:"unicode/1f236.png?v8",u6e80:"unicode/1f235.png?v8",u7121:"unicode/1f21a.png?v8",u7533:"unicode/1f238.png?v8",u7981:"unicode/1f232.png?v8",u7a7a:"unicode/1f233.png?v8",uganda:"unicode/1f1fa-1f1ec.png?v8",uk:"unicode/1f1ec-1f1e7.png?v8",ukraine:"unicode/1f1fa-1f1e6.png?v8",umbrella:"unicode/2614.png?v8",unamused:"unicode/1f612.png?v8",underage:"unicode/1f51e.png?v8",unicorn:"unicode/1f984.png?v8",united_arab_emirates:"unicode/1f1e6-1f1ea.png?v8",united_nations:"unicode/1f1fa-1f1f3.png?v8",unlock:"unicode/1f513.png?v8",up:"unicode/1f199.png?v8",upside_down_face:"unicode/1f643.png?v8",uruguay:"unicode/1f1fa-1f1fe.png?v8",us:"unicode/1f1fa-1f1f8.png?v8",us_outlying_islands:"unicode/1f1fa-1f1f2.png?v8",us_virgin_islands:"unicode/1f1fb-1f1ee.png?v8",uzbekistan:"unicode/1f1fa-1f1ff.png?v8",v:"unicode/270c.png?v8",vampire:"unicode/1f9db.png?v8",vampire_man:"unicode/1f9db-2642.png?v8",vampire_woman:"unicode/1f9db-2640.png?v8",vanuatu:"unicode/1f1fb-1f1fa.png?v8",vatican_city:"unicode/1f1fb-1f1e6.png?v8",venezuela:"unicode/1f1fb-1f1ea.png?v8",vertical_traffic_light:"unicode/1f6a6.png?v8",vhs:"unicode/1f4fc.png?v8",vibration_mode:"unicode/1f4f3.png?v8",video_camera:"unicode/1f4f9.png?v8",video_game:"unicode/1f3ae.png?v8",vietnam:"unicode/1f1fb-1f1f3.png?v8",violin:"unicode/1f3bb.png?v8",virgo:"unicode/264d.png?v8",volcano:"unicode/1f30b.png?v8",volleyball:"unicode/1f3d0.png?v8",vomiting_face:"unicode/1f92e.png?v8",vs:"unicode/1f19a.png?v8",vulcan_salute:"unicode/1f596.png?v8",waffle:"unicode/1f9c7.png?v8",wales:"unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png?v8",walking:"unicode/1f6b6.png?v8",walking_man:"unicode/1f6b6-2642.png?v8",walking_woman:"unicode/1f6b6-2640.png?v8",wallis_futuna:"unicode/1f1fc-1f1eb.png?v8",waning_crescent_moon:"unicode/1f318.png?v8",waning_gibbous_moon:"unicode/1f316.png?v8",warning:"unicode/26a0.png?v8",wastebasket:"unicode/1f5d1.png?v8",watch:"unicode/231a.png?v8",water_buffalo:"unicode/1f403.png?v8",water_polo:"unicode/1f93d.png?v8",watermelon:"unicode/1f349.png?v8",wave:"unicode/1f44b.png?v8",wavy_dash:"unicode/3030.png?v8",waxing_crescent_moon:"unicode/1f312.png?v8",waxing_gibbous_moon:"unicode/1f314.png?v8",wc:"unicode/1f6be.png?v8",weary:"unicode/1f629.png?v8",wedding:"unicode/1f492.png?v8",weight_lifting:"unicode/1f3cb.png?v8",weight_lifting_man:"unicode/1f3cb-2642.png?v8",weight_lifting_woman:"unicode/1f3cb-2640.png?v8",western_sahara:"unicode/1f1ea-1f1ed.png?v8",whale:"unicode/1f433.png?v8",whale2:"unicode/1f40b.png?v8",wheel_of_dharma:"unicode/2638.png?v8",wheelchair:"unicode/267f.png?v8",white_check_mark:"unicode/2705.png?v8",white_circle:"unicode/26aa.png?v8",white_flag:"unicode/1f3f3.png?v8",white_flower:"unicode/1f4ae.png?v8",white_haired_man:"unicode/1f468-1f9b3.png?v8",white_haired_woman:"unicode/1f469-1f9b3.png?v8",white_heart:"unicode/1f90d.png?v8",white_large_square:"unicode/2b1c.png?v8",white_medium_small_square:"unicode/25fd.png?v8",white_medium_square:"unicode/25fb.png?v8",white_small_square:"unicode/25ab.png?v8",white_square_button:"unicode/1f533.png?v8",wilted_flower:"unicode/1f940.png?v8",wind_chime:"unicode/1f390.png?v8",wind_face:"unicode/1f32c.png?v8",window:"unicode/1fa9f.png?v8",wine_glass:"unicode/1f377.png?v8",wink:"unicode/1f609.png?v8",wolf:"unicode/1f43a.png?v8",woman:"unicode/1f469.png?v8",woman_artist:"unicode/1f469-1f3a8.png?v8",woman_astronaut:"unicode/1f469-1f680.png?v8",woman_beard:"unicode/1f9d4-2640.png?v8",woman_cartwheeling:"unicode/1f938-2640.png?v8",woman_cook:"unicode/1f469-1f373.png?v8",woman_dancing:"unicode/1f483.png?v8",woman_facepalming:"unicode/1f926-2640.png?v8",woman_factory_worker:"unicode/1f469-1f3ed.png?v8",woman_farmer:"unicode/1f469-1f33e.png?v8",woman_feeding_baby:"unicode/1f469-1f37c.png?v8",woman_firefighter:"unicode/1f469-1f692.png?v8",woman_health_worker:"unicode/1f469-2695.png?v8",woman_in_manual_wheelchair:"unicode/1f469-1f9bd.png?v8",woman_in_motorized_wheelchair:"unicode/1f469-1f9bc.png?v8",woman_in_tuxedo:"unicode/1f935-2640.png?v8",woman_judge:"unicode/1f469-2696.png?v8",woman_juggling:"unicode/1f939-2640.png?v8",woman_mechanic:"unicode/1f469-1f527.png?v8",woman_office_worker:"unicode/1f469-1f4bc.png?v8",woman_pilot:"unicode/1f469-2708.png?v8",woman_playing_handball:"unicode/1f93e-2640.png?v8",woman_playing_water_polo:"unicode/1f93d-2640.png?v8",woman_scientist:"unicode/1f469-1f52c.png?v8",woman_shrugging:"unicode/1f937-2640.png?v8",woman_singer:"unicode/1f469-1f3a4.png?v8",woman_student:"unicode/1f469-1f393.png?v8",woman_teacher:"unicode/1f469-1f3eb.png?v8",woman_technologist:"unicode/1f469-1f4bb.png?v8",woman_with_headscarf:"unicode/1f9d5.png?v8",woman_with_probing_cane:"unicode/1f469-1f9af.png?v8",woman_with_turban:"unicode/1f473-2640.png?v8",woman_with_veil:"unicode/1f470-2640.png?v8",womans_clothes:"unicode/1f45a.png?v8",womans_hat:"unicode/1f452.png?v8",women_wrestling:"unicode/1f93c-2640.png?v8",womens:"unicode/1f6ba.png?v8",wood:"unicode/1fab5.png?v8",woozy_face:"unicode/1f974.png?v8",world_map:"unicode/1f5fa.png?v8",worm:"unicode/1fab1.png?v8",worried:"unicode/1f61f.png?v8",wrench:"unicode/1f527.png?v8",wrestling:"unicode/1f93c.png?v8",writing_hand:"unicode/270d.png?v8",x:"unicode/274c.png?v8",yarn:"unicode/1f9f6.png?v8",yawning_face:"unicode/1f971.png?v8",yellow_circle:"unicode/1f7e1.png?v8",yellow_heart:"unicode/1f49b.png?v8",yellow_square:"unicode/1f7e8.png?v8",yemen:"unicode/1f1fe-1f1ea.png?v8",yen:"unicode/1f4b4.png?v8",yin_yang:"unicode/262f.png?v8",yo_yo:"unicode/1fa80.png?v8",yum:"unicode/1f60b.png?v8",zambia:"unicode/1f1ff-1f1f2.png?v8",zany_face:"unicode/1f92a.png?v8",zap:"unicode/26a1.png?v8",zebra:"unicode/1f993.png?v8",zero:"unicode/0030-20e3.png?v8",zimbabwe:"unicode/1f1ff-1f1fc.png?v8",zipper_mouth_face:"unicode/1f910.png?v8",zombie:"unicode/1f9df.png?v8",zombie_man:"unicode/1f9df-2642.png?v8",zombie_woman:"unicode/1f9df-2640.png?v8",zzz:"unicode/1f4a4.png?v8"};window&&window.console&&console.info("Docsify emoji plugin has been deprecated as of v4.13"),window.emojify=function(n,e){return!1===Object.prototype.hasOwnProperty.call(i,e)?n:''+e+''}}(); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/prism-bash.min.js b/ruoyi-admin/src/main/resources/static/static/js/prism-bash.min.js new file mode 100644 index 00000000..f1659f1e --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/prism-bash.min.js @@ -0,0 +1 @@ +!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",a={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},n={bash:a,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:a}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:n},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:n.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},a.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var s=e.languages.extend("typescript",{});delete s["class-name"],e.languages.typescript["class-name"].inside=s,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:s}}}}),e.languages.ts=e.languages.typescript}(Prism); \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/static/static/js/search.min.js b/ruoyi-admin/src/main/resources/static/static/js/search.min.js new file mode 100644 index 00000000..9719f653 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/static/js/search.min.js @@ -0,0 +1 @@ +!function(){function u(e){return e.replace(//,"").replace(/{docsify-ignore}/,"").replace(//,"").replace(/{docsify-ignore-all}/,"").trim()}var f={},m={EXPIRE_KEY:"docsify.search.expires",INDEX_KEY:"docsify.search.index"};function g(e){var n={"&":"&","<":"<",">":">",'"':""","'":"'"};return String(e).replace(/[&<>"']/g,function(e){return n[e]})}function y(e){return e.text||"table"!==e.type||(e.cells.unshift(e.header),e.text=e.cells.map(function(e){return e.join(" | ")}).join(" |\n ")),e.text}function v(e){return e.text||"list"!==e.type||(e.text=e.raw),e.text}function b(o,e,s,c){void 0===e&&(e="");var d,e=window.marked.lexer(e),l=window.Docsify.slugify,p={},h="";return e.forEach(function(e,n){var t,a,i,r;"heading"===e.type&&e.depth<=c?(t=(a=(i=e.text,r={},{str:i=(i=void 0===i?"":i)&&i.replace(/^('|")/,"").replace(/('|")$/,"").replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g,function(e,n,t){return-1===n.indexOf(":")?(r[n]=t&&t.replace(/"/g,"")||!0,""):e}).trim(),config:r})).str,i=a.config,a=u(e.text),d=i.id?s.toURL(o,{id:l(i.id)}):s.toURL(o,{id:l(g(a))}),t&&(h=u(t)),p[d]={slug:d,title:h,body:""}):(0===n&&(d=s.toURL(o),p[d]={slug:d,title:"/"!==o?o.slice(1):"Home Page",body:e.text||""}),d&&(p[d]?p[d].body?(e.text=y(e),e.text=v(e),p[d].body+="\n"+(e.text||"")):(e.text=y(e),e.text=v(e),p[d].body=e.text||""):p[d]={slug:d,title:"",body:""}))}),l.clear(),p}function p(e){return e&&e.normalize?e.normalize("NFD").replace(/[\u0300-\u036f]/g,""):e}function o(e){var n=[],t=[];Object.keys(f).forEach(function(n){t=t.concat(Object.keys(f[n]).map(function(e){return f[n][e]}))});var a=(e=e.trim()).split(/[\s\-,\\/]+/);1!==a.length&&(a=[].concat(e,a));for(var i=0;il.length&&(t=l.length),a=c&&"..."+c.substring(n,t).replace(a,function(e){return''+e+""})+"...",o+=a)}),0\n\n

    '+e.title+"

    \n

    "+e.content+"

    \n
    \n"}),t.classList.add("show"),a.classList.add("show"),t.innerHTML=r||'

    '+c+"

    ",s.hideOtherSidebarContent&&(i&&i.classList.add("hide"),n&&n.classList.add("hide"))}function l(e){s=e}function h(e,n){var t,a,i=n.router.parse().query.s;l(e),Docsify.dom.style("\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0.6em 7px;\n font-size: inherit;\n border: 1px solid transparent;\n}\n\n.search input:focus {\n box-shadow: 0 0 5px var(--theme-color, #42b983);\n border: 1px solid var(--theme-color, #42b983);\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.search input::-ms-clear {\n display: none;\n height: 0;\n width: 0;\n}\n\n.search .clear-button {\n cursor: pointer;\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}\n\n.app-name.hide, .sidebar-nav.hide {\n display: none;\n}"),function(e){void 0===e&&(e="");var n=Docsify.dom.create("div",'
    \n \n
    \n \n \n \n \n \n
    \n
    \n
    \n '),e=Docsify.dom.find("aside");Docsify.dom.toggleClass(n,"search"),Docsify.dom.before(e,n)}(i),n=Docsify.dom.find("div.search"),a=Docsify.dom.find(n,"input"),e=Docsify.dom.find(n,".input-wrap"),Docsify.dom.on(n,"click",function(e){return-1===["A","H2","P","EM"].indexOf(e.target.tagName)&&e.stopPropagation()}),Docsify.dom.on(a,"input",function(n){clearTimeout(t),t=setTimeout(function(e){return d(n.target.value.trim())},100)}),Docsify.dom.on(e,"click",function(e){"INPUT"!==e.target.tagName&&(a.value="",d())}),i&&setTimeout(function(e){return d(i)},500)}function x(e,n){var t,a,i,r,o;l(e),t=e.placeholder,a=n.route.path,(r=Docsify.dom.getNode('.search input[type="search"]'))&&("string"==typeof t?r.placeholder=t:(i=Object.keys(t).filter(function(e){return-1u.scrollOffset&&setTimeout(a,150))}),window.addEventListener("resize",a);var f={open:i,close:a,toggle:o,update:function(){var e=0 getParam() { + List list = new ArrayList<>(); + list.add("t1"); + list.add("t2"); + list.add("t3"); + return list.stream(); + } + + @BeforeEach + public void testBeforeEach() { + System.out.println("@BeforeEach =================="); + } + + @AfterEach + public void testAfterEach() { + System.out.println("@AfterEach =================="); + } + + +} diff --git a/ruoyi-admin/src/test/java/org/ruoyi/test/TagUnitTest.java b/ruoyi-admin/src/test/java/org/ruoyi/test/TagUnitTest.java new file mode 100644 index 00000000..b6b92a23 --- /dev/null +++ b/ruoyi-admin/src/test/java/org/ruoyi/test/TagUnitTest.java @@ -0,0 +1,54 @@ +package org.ruoyi.test; + +import org.junit.jupiter.api.*; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 标签单元测试案例 + * + * @author Lion Li + */ +@SpringBootTest +@DisplayName("标签单元测试案例") +public class TagUnitTest { + + @Tag("dev") + @DisplayName("测试 @Tag dev") + @Test + public void testTagDev() { + System.out.println("dev"); + } + + @Tag("prod") + @DisplayName("测试 @Tag prod") + @Test + public void testTagProd() { + System.out.println("prod"); + } + + @Tag("local") + @DisplayName("测试 @Tag local") + @Test + public void testTagLocal() { + System.out.println("local"); + } + + @Tag("exclude") + @DisplayName("测试 @Tag exclude") + @Test + public void testTagExclude() { + System.out.println("exclude"); + } + + @BeforeEach + public void testBeforeEach() { + System.out.println("@BeforeEach =================="); + } + + @AfterEach + public void testAfterEach() { + System.out.println("@AfterEach =================="); + } + + +} diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index d6a05d9f..4be322a0 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ruoyi-ai - com.xmzs + org.ruoyi ${revision} ../pom.xml @@ -16,7 +16,6 @@ ruoyi-common-doc ruoyi-common-excel ruoyi-common-idempotent - ruoyi-common-job ruoyi-common-log ruoyi-common-mail ruoyi-common-mybatis @@ -34,7 +33,6 @@ ruoyi-common-tenant ruoyi-common-chat ruoyi-common-pay - ruoyi-common-wechat ruoyi-common diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 3f069fb4..b1984ea7 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.xmzs + org.ruoyi ruoyi-common-bom ${revision} pom @@ -21,162 +21,148 @@ - com.xmzs + org.ruoyi ruoyi-common-core ${revision} - com.xmzs + org.ruoyi ruoyi-common-doc ${revision} - com.xmzs + org.ruoyi ruoyi-common-excel ${revision} - com.xmzs + org.ruoyi ruoyi-common-idempotent ${revision} - - - com.xmzs - ruoyi-common-job - ${revision} - - - com.xmzs + org.ruoyi ruoyi-common-log ${revision} - com.xmzs + org.ruoyi ruoyi-common-mail ${revision} - com.xmzs + org.ruoyi ruoyi-common-mybatis ${revision} - com.xmzs + org.ruoyi ruoyi-common-oss ${revision} - com.xmzs + org.ruoyi ruoyi-common-ratelimiter ${revision} - com.xmzs + org.ruoyi ruoyi-common-redis ${revision} - com.xmzs + org.ruoyi ruoyi-common-satoken ${revision} - com.xmzs + org.ruoyi ruoyi-common-security ${revision} - com.xmzs + org.ruoyi ruoyi-common-sms ${revision} - com.xmzs + org.ruoyi ruoyi-common-web ${revision} - com.xmzs + org.ruoyi ruoyi-common-translation ${revision} - com.xmzs + org.ruoyi ruoyi-common-sensitive ${revision} - com.xmzs + org.ruoyi ruoyi-common-json ${revision} - com.xmzs + org.ruoyi ruoyi-common-encrypt ${revision} - com.xmzs + org.ruoyi ruoyi-common-tenant ${revision} - com.xmzs + org.ruoyi ruoyi-common-chat ${revision} - - - com.xmzs - ruoyi-common-wechat - ${revision} - - - com.xmzs - ruoyi-midjourney + org.ruoyi + ruoyi-fusion ${revision} @@ -184,7 +170,7 @@ - com.xmzs + org.ruoyi ruoyi-common-pay ${revision} diff --git a/ruoyi-common/ruoyi-common-chat/pom.xml b/ruoyi-common/ruoyi-common-chat/pom.xml index 169f85d8..7bf6ef4f 100644 --- a/ruoyi-common/ruoyi-common-chat/pom.xml +++ b/ruoyi-common/ruoyi-common-chat/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -22,21 +22,23 @@ - com.xmzs + org.ruoyi ruoyi-common-core + - com.xmzs + org.ruoyi ruoyi-common-json + 1.0.0 - com.xmzs + org.ruoyi ruoyi-common-redis - com.xmzs + org.ruoyi ruoyi-common-satoken diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/ChatConfig.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/ChatConfig.java new file mode 100644 index 00000000..03c519b0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/ChatConfig.java @@ -0,0 +1,57 @@ +package org.ruoyi.common.chat.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.openai.function.KeyRandomStrategy; +import org.ruoyi.common.chat.openai.interceptor.OpenAILogger; +import org.ruoyi.common.core.service.ConfigService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +/** + * Chat配置类 + * + * @date: 2023/5/16 + */ +@Configuration +@RequiredArgsConstructor +public class ChatConfig { + + @Getter + private OpenAiStreamClient openAiStreamClient; + + private final ConfigService configService; + + // 重启才会生效 + @Bean + public OpenAiStreamClient openAiStreamClient() { + + String apiHost = configService.getConfigValue("chat", "apiHost"); + String apiKey = configService.getConfigValue("chat", "apiKey"); + openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey); + return openAiStreamClient; + } + + public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) { + HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger()); + httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .addInterceptor(httpLoggingInterceptor) + .connectTimeout(30, TimeUnit.SECONDS) + .writeTimeout(600, TimeUnit.SECONDS) + .readTimeout(600, TimeUnit.SECONDS) + .build(); + return OpenAiStreamClient.builder() + .apiHost(apiHost) + .apiKey(Collections.singletonList(apiKey)) + .keyStrategy(new KeyRandomStrategy()) + .okHttpClient(okHttpClient) + .build(); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/LocalCache.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/LocalCache.java new file mode 100644 index 00000000..e90286ee --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/LocalCache.java @@ -0,0 +1,36 @@ +package org.ruoyi.common.chat.config; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.date.DateUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @date 2023-03-10 + */ +@Slf4j +public class LocalCache { + /** + * 缓存时长 + */ + public static final long TIMEOUT = 30 * DateUnit.MINUTE.getMillis(); + /** + * 清理间隔 + */ + private static final long CLEAN_TIMEOUT = 30 * DateUnit.MINUTE.getMillis(); + + /** + * 缓存对象 + */ + public static final TimedCache CACHE = CacheUtil.newTimedCache(TIMEOUT); + + + static { + //启动定时任务 + CACHE.schedulePrune(CLEAN_TIMEOUT); + } + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/WebSocketConfig.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/WebSocketConfig.java new file mode 100644 index 00000000..7b090042 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/WebSocketConfig.java @@ -0,0 +1,60 @@ +package org.ruoyi.common.chat.config; + +import cn.hutool.core.util.StrUtil; +import org.ruoyi.common.chat.config.properties.WebSocketProperties; +import org.ruoyi.common.chat.handler.PlusWebSocketHandler; +import org.ruoyi.common.chat.interceptor.PlusWebSocketInterceptor; +import org.ruoyi.common.chat.listener.WebSocketTopicListener; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.server.HandshakeInterceptor; + +/** + * WebSocket 配置 + * + * @author zendwang + */ +@AutoConfiguration +@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true") +@EnableConfigurationProperties(WebSocketProperties.class) +@EnableWebSocket +public class WebSocketConfig { + + @Bean + public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor, + WebSocketHandler webSocketHandler, + WebSocketProperties webSocketProperties) { + if (StrUtil.isBlank(webSocketProperties.getPath())) { + webSocketProperties.setPath("/websocket"); + } + + if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) { + webSocketProperties.setAllowedOrigins("*"); + } + + return registry -> registry + .addHandler(webSocketHandler, webSocketProperties.getPath()) + .addInterceptors(handshakeInterceptor) + .setAllowedOrigins(webSocketProperties.getAllowedOrigins()); + } + + @Bean + public HandshakeInterceptor handshakeInterceptor() { + return new PlusWebSocketInterceptor(); + } + + @Bean + public WebSocketHandler webSocketHandler() { + return new PlusWebSocketHandler(); + } + + @Bean + public WebSocketTopicListener topicListener() { + return new WebSocketTopicListener(); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java new file mode 100644 index 00000000..eb36cc77 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.chat.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * WebSocket 配置项 + * + * @author zendwang + */ +@ConfigurationProperties("websocket") +@Data +public class WebSocketProperties { + + private Boolean enabled; + + /** + * 路径 + */ + private String path; + + /** + * 设置访问源地址 + */ + private String allowedOrigins; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/OpenAIConst.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/OpenAIConst.java new file mode 100644 index 00000000..bab2821c --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/OpenAIConst.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.constant; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-06 + */ +public class OpenAIConst { + + public final static String OPENAI_HOST = "https://api.openai.com/"; + + public final static int SUCCEED_CODE = 200; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/WebSocketConstants.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/WebSocketConstants.java new file mode 100644 index 00000000..c9501e50 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/constant/WebSocketConstants.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.chat.constant; + +/** + * websocket的常量配置 + * + * @author zendwang + */ +public interface WebSocketConstants { + /** + * websocketSession中的参数的key + */ + String LOGIN_USER_KEY = "loginUser"; + + /** + * 订阅的频道 + */ + String WEB_SOCKET_TOPIC = "global:websocket"; + + /** + * 前端心跳检查的命令 + */ + String PING = "ping"; + + /** + * 服务端心跳恢复的字符串 + */ + String PONG = "pong"; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java new file mode 100644 index 00000000..a667fff7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java @@ -0,0 +1,63 @@ +package org.ruoyi.common.chat.domain.request; + +import org.ruoyi.common.chat.entity.chat.Message; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @sine 2023-04-08 + */ +@Data +public class ChatRequest { + + + private String frequency_penalty; + + private String max_tokens; + + @NotEmpty(message = "对话消息不能为空") + List messages; + + @NotEmpty(message = "传入的模型不能为空") + private String model; + + private String presence_penalty; + + private String stream; + + private double temperature; + + private double top_p = 1; + +// private String userId; +// +// /** +// * 知识库id +// */ +// private String kid; +// +// /** +// * gpt的默认设置 +// */ +// private String systemMessage = ""; +// +// +// +// private double temperature = 0.2; +// +// /** +// * 上下文的条数 +// */ +// private Integer contentNumber = 10; +// +// /** +// * 是否携带上下文 +// */ +// private Boolean usingContext = Boolean.TRUE; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java new file mode 100644 index 00000000..df220566 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.chat.domain.request; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @sine 2023-04-08 + */ +@Data +public class Dall3Request { + + @NotEmpty(message = "传入的模型不能为空") + private String model; + + @NotEmpty(message = "提示词不能为空") + private String prompt; + + /** 图片大小 */ + @NotEmpty(message = "图片大小不能为空") + private String size ; + + /** 图片质量 */ + @NotEmpty(message = "图片质量不能为空") + private String quality; + + /** 图片风格 */ + @NotEmpty(message = "图片风格不能为空") + private String style; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TextToSpeech.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TextToSpeech.java new file mode 100644 index 00000000..46e47bf4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TextToSpeech.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.chat.entity.Tts; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class TextToSpeech { + + @Builder.Default + private String model = Model.TTS_1.getName(); + /** + * 音频声音源 + * + * @see TtsVoice + */ + private String voice; + /** + * 输入内容 + */ + private String input; + /** + * 输出音频文件格式 + * + * @see TtsFormat + */ + @JsonProperty("response_format") + private String responseFormat; + /** + * 速度调节,默认是1,取值范围0.25——4.0 + */ + private Double speed; + + + @Getter + @AllArgsConstructor + public enum Model { + TTS_1("tts-1"), + TTS_1_HD("tts-1-hd"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsFormat.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsFormat.java new file mode 100644 index 00000000..f6c21494 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsFormat.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.entity.Tts; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TtsFormat { + MP3("mp3"), + OPUS("opus"), + AAC("aac"), + FLAC("flac"), + ; + private final String name; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsVoice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsVoice.java new file mode 100644 index 00000000..9b2df8db --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/Tts/TtsVoice.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.chat.entity.Tts; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 生成不同声音的音频 + *

    具体语音效果参考:https://platform.openai.com/docs/guides/text-to-speech

    + */ +@Getter +@AllArgsConstructor +public enum TtsVoice { + + ALLOY("alloy"), + ECHO("echo"), + FABLE("fable"), + ONYX("onyx"), + NOVA("nova"), + SHIMMER("shimmer"), + ; + + private final String name; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/BillingUsage.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/BillingUsage.java new file mode 100644 index 00000000..2ad8b040 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/BillingUsage.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 描述:金额消耗信息 + * + * @author https:www.unfbx.com + * @since 2023-04-08 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class BillingUsage { + + @JsonProperty("object") + private String object; + /** + * 账号金额消耗明细 + */ + @JsonProperty("daily_costs") + private List dailyCosts; + /** + * 总使用金额:美分 + */ + @JsonProperty("total_usage") + private BigDecimal totalUsage; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/CreditGrantsResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/CreditGrantsResponse.java new file mode 100644 index 00000000..6b3629f9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/CreditGrantsResponse.java @@ -0,0 +1,39 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 描述:余额查询接口返回值 + * + * @author https:www.unfbx.com + * @since 2023-03-18 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CreditGrantsResponse implements Serializable { + private String object; + /** + * 总金额:美元 + */ + @JsonProperty("total_granted") + private BigDecimal totalGranted; + /** + * 总使用金额:美元 + */ + @JsonProperty("total_used") + private BigDecimal totalUsed; + /** + * 总剩余金额:美元 + */ + @JsonProperty("total_available") + private BigDecimal totalAvailable; + /** + * 余额明细 + */ + private Grants grants; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/DailyCost.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/DailyCost.java new file mode 100644 index 00000000..b11c9d77 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/DailyCost.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 描述:金额消耗列表 + * + * @author https:www.unfbx.com + * @since 2023-04-08 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DailyCost { + /** + * 时间戳 + */ + @JsonProperty("timestamp") + private long timestamp; + /** + * 模型消耗金额详情 + */ + @JsonProperty("line_items") + private List lineItems; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Datum.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Datum.java new file mode 100644 index 00000000..e2eb2e4b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Datum.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-18 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Datum { + private String object; + private String id; + /** + * 赠送金额:美元 + */ + @JsonProperty("grant_amount") + private BigDecimal grantAmount; + /** + * 使用金额:美元 + */ + @JsonProperty("used_amount") + private BigDecimal usedAmount; + /** + * 生效时间戳 + */ + @JsonProperty("effective_at") + private Long effectiveAt; + /** + * 过期时间戳 + */ + @JsonProperty("expires_at") + private Long expiresAt; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Grants.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Grants.java new file mode 100644 index 00000000..f8f4cce2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Grants.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-18 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Grants { + private String object; + @JsonProperty("data") + private List data; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/KeyInfo.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/KeyInfo.java new file mode 100644 index 00000000..3af11ada --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/KeyInfo.java @@ -0,0 +1,56 @@ +package org.ruoyi.common.chat.entity.billing; + +import lombok.*; + +import java.time.LocalDate; + +/** + * openKey信息 + * + * @author admin + * @date 2023/6/15 + */ +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class KeyInfo { + /** + * 订阅类型 + */ + private String planTitle; + /** + * key值 + */ + private String keyValue; + /** + * 剩余额度 + */ + private Double remaining; + + /** + * 账户总余额 + */ + private Double totalAmount; + + /** + * 已使用的额度 + */ + private Double totalUsage; + + /** + * 截至日期 + */ + private LocalDate limitDate; + + /** + * 是否绑卡 + */ + private Boolean isHasPaymentMethod; + + /** + * 最高可用模型 + */ + private String model; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/LineItem.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/LineItem.java new file mode 100644 index 00000000..90499bb6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/LineItem.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 描述:金额消耗列表 + * + * @author https:www.unfbx.com + * @since 2023-04-08 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class LineItem { + /** + * 模型名称 + */ + private String name; + /** + * 消耗金额 + */ + private BigDecimal cost; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Plan.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Plan.java new file mode 100644 index 00000000..de74e213 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Plan.java @@ -0,0 +1,17 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-04-08 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Plan { + private String title; + private String id; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Subscription.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Subscription.java new file mode 100644 index 00000000..f41c94b2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/billing/Subscription.java @@ -0,0 +1,73 @@ +package org.ruoyi.common.chat.entity.billing; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 描述:账户信息 + * + * @author https:www.unfbx.com + * @since 2023-04-08 + */ +@Data +public class Subscription { + + @JsonProperty("object") + private String object; + + /** + * 付款方式 + */ + @JsonProperty("has_payment_method") + private boolean hasPaymentMethod; + + @JsonProperty("canceled") + private boolean canceled; + @JsonProperty("canceled_at") + private Object canceledAt; + + @JsonProperty("delinquent") + private Object delinquent; + @JsonProperty("access_until") + private long accessUntil; + @JsonProperty("soft_limit") + private long softLimit; + @JsonProperty("hard_limit") + private long hardLimit; + @JsonProperty("system_hard_limit") + private long systemHardLimit; + @JsonProperty("soft_limit_usd") + private double softLimitUsd; + @JsonProperty("hard_limit_usd") + private double hardLimitUsd; + @JsonProperty("system_hard_limit_usd") + private double systemHardLimitUsd; + /** + * 计划 + */ + @JsonProperty("plan") + private Plan plan; + + /** + * 账户名称 + */ + @JsonProperty("account_name") + private String accountName; + + @JsonProperty("po_number") + private Object poNumber; + + /** + * 账单邮箱 + */ + @JsonProperty("billing_email") + private Object billingEmail; + @JsonProperty("tax_ids") + private Object taxIds; + @JsonProperty("billing_address") + private Object billingAddress; + @JsonProperty("business_address") + private Object businessAddress; + @JsonProperty("primary") + private Boolean primary; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseChatCompletion.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseChatCompletion.java new file mode 100644 index 00000000..c0bcd183 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseChatCompletion.java @@ -0,0 +1,255 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.experimental.SuperBuilder; +import org.ruoyi.common.chat.entity.chat.tool.Tools; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import static org.ruoyi.common.chat.entity.chat.BaseChatCompletion.Model.GPT_3_5_TURBO; + +/** + * 描述: chat模型基础类 + * + * @author https:www.unfbx.com + * @since 1.1.2 + * 2023-11-10 + */ +@Data +@SuperBuilder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class BaseChatCompletion implements Serializable { + + @NonNull + @Builder.Default + private String model = GPT_3_5_TURBO.getName(); + + /** + * 指定模型必须输出的格式的对象。 + * + * @since 1.1.2 + */ + @JsonProperty("response_format") + private ResponseFormat responseFormat; + + /** + * 已过时 + * + * @see #tools + */ + @Deprecated + private List functions; + + /** + * 取值:null,auto或者自定义 + * functions没有值的时候默认为:null + * functions存在值得时候默认为:auto + * 也可以自定义 + *

    已过时

    + * + * @see #toolChoice + */ + @Deprecated + @JsonProperty("function_call") + private Object functionCall; + + /** + * 模型可能调用的工具列表。 + * 当前版本仅支持:functions + * + * @since 1.1.2 + */ + private List tools; + + /** + * 取值:String或者ToolChoiceObj + * + * @since 1.1.2 + */ + @JsonProperty("tool_choice") + private Object toolChoice; + + /** + * 使用什么取样温度,0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定。 + *

    + * We generally recommend altering this or but not both.top_p + */ + @Builder.Default + private double temperature = 0.2; + + /** + * 使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币。 + *

    + * 我们通常建议更改此设置,但不要同时更改两者。temperature + */ + @JsonProperty("top_p") + @Builder.Default + private Double topP = 1d; + + + /** + * 为每个提示生成的完成次数。 + */ + @Builder.Default + private Integer n = 1; + + + /** + * 是否流式输出. + * default:false + */ + @Builder.Default + private boolean stream = false; + /** + * 停止输出标识 + */ + private List stop; + /** + * 最大支持4096 + */ + @JsonProperty("max_tokens") + @Builder.Default + private Integer maxTokens = 2048; + + + @JsonProperty("presence_penalty") + @Builder.Default + private double presencePenalty = 0; + + /** + * -2.0 ~~ 2.0 + */ + @JsonProperty("frequency_penalty") + @Builder.Default + private double frequencyPenalty = 0; + + @JsonProperty("logit_bias") + private Map logitBias; + /** + * 用户唯一值,确保接口不被重复调用 + */ + private String user; + + /** + * @since 1.1.2 + */ + private Integer seed; + + + /** + * 最新模型参考官方文档: + * 官方稳定模型列表 + */ + @Getter + @AllArgsConstructor + public enum Model { + /** + * gpt-3.5-turbo + */ + GPT_3_5_TURBO("gpt-3.5-turbo"), + /** + * 临时模型,不建议使用,2023年9 月 13 日将被弃用 + */ + @Deprecated + GPT_3_5_TURBO_0301("gpt-3.5-turbo-0301"), + /** + * gpt-3.5-turbo-0613 支持函数 + */ + GPT_3_5_TURBO_1106("gpt-3.5-turbo-1106"), + + GPT_3_5_TURBO_0613("gpt-3.5-turbo-0613"), + /** + * gpt-3.5-turbo-16k 超长上下文 + */ + GPT_3_5_TURBO_16K("gpt-3.5-turbo-16k"), + /** + * gpt-3.5-turbo-16k-0613 超长上下文 支持函数 + */ + GPT_3_5_TURBO_16K_0613("gpt-3.5-turbo-16k-0613"), + /** + * gpt-3.5-turbo-0125 超长上下文 支持函数 + */ + GPT_3_5_TURBO_0125("gpt-3.5-turbo-0125"), + /** + * GPT4.0 + */ + GPT_4("gpt-4"), + /** + * 临时模型,不建议使用,2023年9 月 13 日将被弃用 + */ + @Deprecated + GPT_4_0314("gpt-4-0314"), + /** + * GPT4.0 超长上下文 + */ + GPT_4_32K("gpt-4-32k"), + /** + * 临时模型,不建议使用,2023年9 月 13 日将被弃用 + */ + @Deprecated + GPT_4_32K_0314("gpt-4-32k-0314"), + + /** + * gpt-4-0613,支持函数 + */ + GPT_4_0613("gpt-4-0613"), + /** + * gpt-4-0613,支持函数 + */ + GPT_4_32K_0613("gpt-4-32k-0613"), + /** + * 支持数组模式,支持function call,支持可重复输出 + */ + GPT_4_1106_PREVIEW("gpt-4-1106-preview"), + /** + * 支持图片 + */ + GPT_4_VISION_PREVIEW("gpt-4-vision-preview"), + /** + * gpt-4-0613,支持函数 + */ + GPT_4_0125_PREVIEW("gpt-4-0125-preview"), + + /** + * GPT_4_ALL + */ + GPT_4_ALL("gpt-4-all"), + + GPT_4_GIZMO("gpt-4-gizmo"), + + NET("net"), + + CLAUDE_3_SONNET("claude-3-sonnet-20240229"), + + GEMINI_PRO("gemini-pro"), + + STABLE_DIFFUSION("stable-diffusion"), + + SUNO_V3("suno-v3"), + ; + private final String name; + } + + @Getter + @AllArgsConstructor + public enum ChatType { + /** + * 对话类型 - 输入 + */ + CHAT_IN("in"), + /** + * 对话类型 - 输出 + */ + CHAT_OUT("out"), + + ; + private final String name; + } + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseMessage.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseMessage.java new file mode 100644 index 00000000..8759aa82 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/BaseMessage.java @@ -0,0 +1,84 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import org.ruoyi.common.chat.entity.chat.tool.ToolCalls; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 1.1.2 + * 2023-03-02 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +public class BaseMessage implements Serializable { + + /** + * 目前支持四个中角色参考官网,进行情景输入: + * https://platform.openai.com/docs/guides/chat/introduction + */ + private String role; + + + private String name; + + /** + * The tool calls generated by the model, such as function calls. + * @since 1.1.2 + */ + @JsonProperty("tool_calls") + private List toolCalls; + + /** + * @since 1.1.2 + */ + @JsonProperty("tool_call_id") + private String toolCallId; + + @Deprecated + @JsonProperty("function_call") + private FunctionCall functionCall; + + + /** + * 构造函数 + * + * @param role 角色 + * @param name name + * @param functionCall functionCall + */ + public BaseMessage(String role, String name, FunctionCall functionCall) { + this.role = role; + this.name = name; + this.functionCall = functionCall; + } + + public BaseMessage() { + } + + + @Getter + @AllArgsConstructor + public enum Role { + + SYSTEM("system"), + USER("user"), + ASSISTANT("assistant"), + FUNCTION("function"), + TOOL("tool"), + ; + private final String name; + } + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatChoice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatChoice.java new file mode 100644 index 00000000..54bdf6fa --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatChoice.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-02 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ChatChoice implements Serializable { + private long index; + /** + * 请求参数stream为true返回是delta + */ + @JsonProperty("delta") + private Message delta; + /** + * 请求参数stream为false返回是message + */ + @JsonProperty("message") + private Message message; + @JsonProperty("finish_reason") + private String finishReason; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletion.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletion.java new file mode 100644 index 00000000..003013b2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletion.java @@ -0,0 +1,34 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: chat模型参数 + * + * @author https:www.unfbx.com + * 2023-03-02 + */ +@Data +@SuperBuilder +@Slf4j +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class ChatCompletion extends BaseChatCompletion implements Serializable { + + /** + * 问题描述 + */ + @NonNull + private List messages; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionResponse.java new file mode 100644 index 00000000..5f02aa38 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionResponse.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.ruoyi.common.chat.entity.common.Usage; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: chat答案类 + * + * @author https:www.unfbx.com + * 2023-03-02 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ChatCompletionResponse implements Serializable { + private String id; + private String object; + private long created; + private String model; + private List choices; + private Usage usage; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionWithPicture.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionWithPicture.java new file mode 100644 index 00000000..b7a060df --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ChatCompletionWithPicture.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: chat模型附带图片的参数 + * + * @author https:www.unfbx.com + * @since 1.1.2 + * 2023-11-10 + */ +@Data +@SuperBuilder +@Slf4j +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class ChatCompletionWithPicture extends BaseChatCompletion implements Serializable { + /** + * 问题描述 + */ + private List messages; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Content.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Content.java new file mode 100644 index 00000000..02fdcbb0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Content.java @@ -0,0 +1,43 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +/** + * 描述: + * + * @author https://www.unfbx.com + * @since 1.1.2 + * 2023-11-10 + */ +@Data +@Builder +@Slf4j +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class Content { + /** + * 输入类型:text、image_url + * + * @see Type + */ + private String type; + private String text; + @JsonProperty("image_url") + private ImageUrl imageUrl; + + /** + * 生成图片风格 + */ + @Getter + @AllArgsConstructor + public enum Type { + TEXT("text"), + IMAGE_URL("image_url"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FunctionCall.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FunctionCall.java new file mode 100644 index 00000000..66910789 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FunctionCall.java @@ -0,0 +1,27 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 描述:函数调用返回值 + * + * @author https://www.unfbx.com + * @since 2023-06-14 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FunctionCall { + /** + * 方法名 + */ + private String name; + /** + * 方法参数 + */ + private String arguments; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Functions.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Functions.java new file mode 100644 index 00000000..d245dd2e --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Functions.java @@ -0,0 +1,46 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述:方法参数实体类,实例数据如下 + *

    + *     {
    + *          "name": "get_current_weather",
    + *          "description": "Get the current weather in a given location",
    + *          "parameters": {
    + *              "type": "object",
    + *              "properties": {
    + *                  "location": {
    + *                      "type": "string",
    + *                      "description": "The city and state, e.g. San Francisco, CA"
    + *                  },
    + *                  "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    + *              },
    + *              "required": ["location"]
    + *          },
    + *     }
    + * 
    + * @author https:www.unfbx.com + * @since 2023-06-14 + */ +@Data +@Builder +public class Functions implements Serializable { + /** + * 方法名称 + */ + private String name; + /** + * 方法描述 + */ + private String description; + /** + * 方法参数 + * 扩展参数可以继承Parameters自己实现,json格式的数据 + */ + private Parameters parameters; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ImageUrl.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ImageUrl.java new file mode 100644 index 00000000..47a0c725 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ImageUrl.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 描述: + * + * @author https://www.unfbx.com + * 2023-11-10 + */ +@Data +@Builder +@Slf4j +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class ImageUrl { + /** + * 图片地址,支持base64. eg: data:image/jpeg;base64,{base64_image} + * https://platform.openai.com/docs/guides/vision + */ + private String url; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java new file mode 100644 index 00000000..9637569c --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java @@ -0,0 +1,116 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-02 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Message implements Serializable { + + /** + * 目前支持四个中角色参考官网,进行情景输入: + * https://platform.openai.com/docs/guides/chat/introduction + */ + private String role; + + private Object content; + + private String name; + + @JsonProperty("function_call") + private FunctionCall functionCall; + + public static Builder builder() { + return new Builder(); + } + + /** + * 构造函数 + * + * @param role 角色 + * @param content 描述主题信息 + * @param name name + * @param functionCall functionCall + */ + public Message(String role, String content, String name, FunctionCall functionCall) { + this.role = role; + this.content = content; + this.name = name; + this.functionCall = functionCall; + } + + public Message() { + } + + private Message(Builder builder) { + setRole(builder.role); + setContent(builder.content); + setName(builder.name); + setFunctionCall(builder.functionCall); + } + + + @Getter + @AllArgsConstructor + public enum Role { + + SYSTEM("system"), + USER("user"), + ASSISTANT("assistant"), + FUNCTION("function"), + ; + private String name; + } + + public static final class Builder { + private String role; + private String content; + private String name; + private FunctionCall functionCall; + + public Builder() { + } + + public Builder role(Role role) { + this.role = role.getName(); + return this; + } + + public Builder role(String role) { + this.role = role; + return this; + } + + public Builder content(String content) { + this.content = content; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder functionCall(FunctionCall functionCall) { + this.functionCall = functionCall; + return this; + } + + public Message build() { + return new Message(this); + } + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/MessagePicture.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/MessagePicture.java new file mode 100644 index 00000000..18a4ee88 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/MessagePicture.java @@ -0,0 +1,114 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.ruoyi.common.chat.entity.chat.tool.ToolCalls; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-02 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@AllArgsConstructor +public class MessagePicture extends BaseMessage implements Serializable { + /** + * Content数组支持多图片输入 + * https://platform.openai.com/docs/guides/vision + */ + private List content; + + + public static Builder builder() { + return new Builder(); + } + + /** + * 构造函数 + * + * @param role 角色 + * @param name name + * @param content content + * @param functionCall functionCall + */ + public MessagePicture(String role, String name, List content, List toolCalls, String toolCallId, FunctionCall functionCall) { + this.content = content; + super.setRole(role); + super.setName(name); + super.setToolCalls(toolCalls); + super.setToolCallId(toolCallId); + super.setFunctionCall(functionCall); + } + + public MessagePicture() { + } + + private MessagePicture(Builder builder) { + setContent(builder.content); + super.setRole(builder.role); + super.setName(builder.name); + super.setFunctionCall(builder.functionCall); + super.setToolCalls(builder.toolCalls); + super.setToolCallId(builder.toolCallId); + } + + public static final class Builder { + private String role; + private List content; + private String name; + private String toolCallId; + private List toolCalls; + private FunctionCall functionCall; + + public Builder() { + } + + public Builder role(Role role) { + this.role = role.getName(); + return this; + } + + public Builder role(String role) { + this.role = role; + return this; + } + + public Builder content(List content) { + this.content = content; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder functionCall(FunctionCall functionCall) { + this.functionCall = functionCall; + return this; + } + + public Builder toolCalls(List toolCalls) { + this.toolCalls = toolCalls; + return this; + } + + public Builder toolCallId(String toolCallId) { + this.toolCallId = toolCallId; + return this; + } + + public MessagePicture build() { + return new MessagePicture(this); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Parameters.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Parameters.java new file mode 100644 index 00000000..48a9036b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Parameters.java @@ -0,0 +1,42 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; +/** + * 描述:方法参数类,扩展参数可以继承Parameters自己实现 + * 参考: + *
    + * {
    + *     "type": "object",
    + *     "properties": {
    + *         "location": {
    + *             "type": "string",
    + *             "description": "The city and state, e.g. San Francisco, CA"
    + *         },
    + *         "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    + *     },
    + *     "required": ["location"]
    + * }
    + * 
    + * @author https:www.unfbx.com + * @since 2023-06-14 + */ +@Data +@Builder +public class Parameters implements Serializable { + /** + * 参数类型 + */ + private String type; + /** + * 参数属性、描述 + */ + private Object properties; + /** + * 方法必输字段 + */ + private List required; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java new file mode 100644 index 00000000..5c154844 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.*; + +/** + * 指定模型必须输出的格式的对象。 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResponseFormat { + /** + * 默认:text + * + * @see Type + */ + private String type; + + @Getter + @AllArgsConstructor + public enum Type { + JSON_OBJECT("json_object"), + TEXT("text"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCallFunction.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCallFunction.java new file mode 100644 index 00000000..da4a7934 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCallFunction.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.chat.entity.chat.tool; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * ToolCall 的 Function参数 + * The function that the model called. + * + * @author https:www.unfbx.com + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolCallFunction implements Serializable { + /** + * 方法名 + */ + private String name; + /** + * 方法参数 + */ + private String arguments; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCalls.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCalls.java new file mode 100644 index 00000000..6feb70ae --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolCalls.java @@ -0,0 +1,37 @@ +package org.ruoyi.common.chat.entity.chat.tool; + +import lombok.*; + +import java.io.Serializable; + +/** + * The tool calls generated by the model, such as function calls. + * + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolCalls implements Serializable { + /** + * The ID of the tool call. + */ + private String id; + /** + * The type of the tool. Currently, only function is supported. + */ + private String type; + + private ToolCallFunction function; + + @Getter + @AllArgsConstructor + public enum Type { + FUNCTION("function"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoice.java new file mode 100644 index 00000000..8eda621f --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoice.java @@ -0,0 +1,27 @@ +package org.ruoyi.common.chat.entity.chat.tool; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +import java.io.Serializable; + +/** + * choice和object同时存在是以object为准 + * + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +public class ToolChoice implements Serializable { + + @Getter + @AllArgsConstructor + public enum Choice { + NONE("none"), + AUTO("auto"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObj.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObj.java new file mode 100644 index 00000000..ca9708c6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObj.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.chat.entity.chat.tool; + +import lombok.*; + +/** + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolChoiceObj { + /** + * 需要调用的方法名称 + */ + private ToolChoiceObjFunction function; + /** + * 工具的类型。目前仅支持函数。 + * + * @see Type + */ + private String type; + + @Getter + @AllArgsConstructor + public enum Type { + FUNCTION("function"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObjFunction.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObjFunction.java new file mode 100644 index 00000000..ac01c744 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolChoiceObjFunction.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.chat.entity.chat.tool; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolChoiceObjFunction { + + private String name; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/Tools.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/Tools.java new file mode 100644 index 00000000..443b4b28 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/Tools.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.chat.entity.chat.tool; + + +import lombok.*; + +import java.io.Serializable; + +/** + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Tools implements Serializable { + + /** + * 目前只支持:function + * + * @see Type + */ + private String type; + + private ToolsFunction function; + + @Getter + @AllArgsConstructor + public enum Type { + FUNCTION("function"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolsFunction.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolsFunction.java new file mode 100644 index 00000000..173d2641 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/tool/ToolsFunction.java @@ -0,0 +1,36 @@ +package org.ruoyi.common.chat.entity.chat.tool; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ruoyi.common.chat.entity.chat.Parameters; + +import java.io.Serializable; + +/** + * @author unfbx + * @since 1.1.2 + * 2023-11-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ToolsFunction implements Serializable { + + /** + * 要调用的函数的名称。必须是 a-z、A-Z、0-9,或包含下划线和破折号,最大长度为 64 + */ + private String name; + /** + * 对函数功能的描述,模型使用它来选择何时以及如何调用该函数。 + */ + private String description; + /** + * 函数接受的参数,描述为 JSON Schema 对象 + * 扩展参数可以继承Parameters自己实现,json格式的数据 + */ + private Parameters parameters; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Choice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Choice.java new file mode 100644 index 00000000..59debb53 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Choice.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.chat.entity.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Choice implements Serializable { + private String text; + private long index; + private Object logprobs; + @JsonProperty("finish_reason") + private String finishReason; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/DeleteResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/DeleteResponse.java new file mode 100644 index 00000000..eedd8eaa --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/DeleteResponse.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.entity.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DeleteResponse implements Serializable { + private String id; + private String object; + private boolean deleted; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/OpenAiResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/OpenAiResponse.java new file mode 100644 index 00000000..5ce90787 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/OpenAiResponse.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.chat.entity.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class OpenAiResponse implements Serializable { + private String object; + private List data; + private Error error; + + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public class Error { + private String message; + private String type; + private String param; + private String code; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Usage.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Usage.java new file mode 100644 index 00000000..09a92076 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/common/Usage.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.chat.entity.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Usage implements Serializable { + @JsonProperty("prompt_tokens") + private long promptTokens; + @JsonProperty("completion_tokens") + private long completionTokens; + @JsonProperty("total_tokens") + private long totalTokens; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/Completion.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/Completion.java new file mode 100644 index 00000000..42fe86de --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/Completion.java @@ -0,0 +1,125 @@ +package org.ruoyi.common.chat.entity.completions; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * 描述: 问题类 + * + * @author https:www.unfbx.com + * 2023-02-11 + */ +@Data +@Builder +@Slf4j +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class Completion implements Serializable { + + @NonNull + @Builder.Default + private String model = Model.DAVINCI_003.getName(); + /** + * 问题描述 + */ + @NonNull + private String prompt; + /** + * 完成输出后的后缀,用于格式化输出结果 + */ + private String suffix; + + /** + * 最大支持4096 + */ + @JsonProperty("max_tokens") + @Builder.Default + private Integer maxTokens = 2048; + /** + * 使用什么取样温度,0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定。 + *

    + * We generally recommend altering this or but not both.top_p + */ + @Builder.Default + private double temperature = 0; + + /** + * 使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币。 + *

    + * 我们通常建议更改此设置,但不要同时更改两者。temperature + */ + @JsonProperty("top_p") + @Builder.Default + private Double topP = 1d; + + /** + * 为每个提示生成的完成次数。 + */ + @Builder.Default + private Integer n = 1; + + @Builder.Default + private boolean stream = false; + /** + * 最大值:5 + */ + private Integer logprobs; + + @Builder.Default + private boolean echo = false; + + private List stop; + + @JsonProperty("presence_penalty") + @Builder.Default + private double presencePenalty = 0; + + /** + * -2.0 ~~ 2.0 + */ + @JsonProperty("frequency_penalty") + @Builder.Default + private double frequencyPenalty = 0; + + @JsonProperty("best_of") + @Builder.Default + private Integer bestOf = 1; + + @JsonProperty("logit_bias") + private Map logitBias; + /** + * 用户唯一值,确保接口不被重复调用 + */ + private String user; + + /** + * 获取当前参数的tokens数 + * @return token数量 + */ +// public long tokens() { +// if (StrUtil.isBlank(this.prompt) || StrUtil.isBlank(this.model)) { +// log.warn("参数异常model:{},prompt:{}", this.model, this.prompt); +// return 0; +// } +// return TikTokensUtil.tokens(this.model, this.prompt); +// } + + @Getter + @AllArgsConstructor + public enum Model { + DAVINCI_003("text-davinci-003"), + DAVINCI_002("text-davinci-002"), + DAVINCI("davinci"), + ; + private String name; + } +} + + diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/CompletionResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/CompletionResponse.java new file mode 100644 index 00000000..31593746 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/completions/CompletionResponse.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.chat.entity.completions; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.ruoyi.common.chat.entity.common.Choice; +import org.ruoyi.common.chat.entity.common.OpenAiResponse; +import org.ruoyi.common.chat.entity.common.Usage; + +import java.io.Serializable; + +/** + * 描述: 答案类 + * + * @author https:www.unfbx.com + * 2023-02-11 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CompletionResponse extends OpenAiResponse implements Serializable { + private String id; + private String object; + private long created; + private String model; + private Choice[] choices; + private Usage usage; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/dto/WebSocketMessageDto.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/dto/WebSocketMessageDto.java new file mode 100644 index 00000000..102be598 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/dto/WebSocketMessageDto.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.chat.entity.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 消息的dto + * + * @author zendwang + */ +@Data +public class WebSocketMessageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 需要推送到的session key 列表 + */ + private List sessionKeys; + + /** + * 需要发送的消息 + */ + private String message; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/Edit.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/Edit.java new file mode 100644 index 00000000..4a2da137 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/Edit.java @@ -0,0 +1,104 @@ +package org.ruoyi.common.chat.entity.edits; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Builder +@Slf4j +@NoArgsConstructor +@AllArgsConstructor +public class Edit implements Serializable { + /** + * 编辑模型,目前支持两种 + */ + @NonNull + private String model; + + @NonNull + private String input; + /** + * 提示说明。告知模型如何修改。 + */ + @NonNull + private String instruction; + + + /** + * 使用什么取样温度,0到2之间。较高的值(如0.8)将使输出更加随机,而较低的值(如0.2)将使输出更加集中和确定。 + * + * We generally recommend altering this or but not both.top_p + */ + @Builder.Default + private double temperature = 0; + + /** + * 使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币。 + * + * 我们通常建议更改此设置,但不要同时更改两者。temperature + */ + @JsonProperty("top_p") + @Builder.Default + private Double topP = 1d; + + /** + * 为每个提示生成的完成次数。 + */ + @Builder.Default + private Integer n = 1; + + public void setModel(Model model) { + this.model = model.getName(); + } + + public void setTemperature(double temperature) { + if (temperature > 2 || temperature < 0) { + log.error("temperature参数异常,temperature属于[0,2]"); + this.temperature = 2; + return; + } + if (temperature < 0) { + log.error("temperature参数异常,temperature属于[0,2]"); + this.temperature = 0; + return; + } + this.temperature = temperature; + } + + + public void setTopP(Double topP) { + this.topP = topP; + } + + public void setN(Integer n) { + this.n = n; + } + + public void setInput(String input) { + this.input = input; + } + + public void setInstruction(String instruction) { + this.instruction = instruction; + } + @Getter + @AllArgsConstructor + public enum Model { + TEXT_DAVINCI_EDIT_001("text-davinci-edit-001"), + CODE_DAVINCI_EDIT_001("code-davinci-edit-001"), + ; + private String name; + } +} + + + diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/EditResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/EditResponse.java new file mode 100644 index 00000000..803b742e --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/edits/EditResponse.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.chat.entity.edits; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.ruoyi.common.chat.entity.common.Choice; +import org.ruoyi.common.chat.entity.common.Usage; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class EditResponse implements Serializable { + private String id; + private String object; + private long created; + private String model; + private Choice[] choices; + private Usage usage; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Embedding.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Embedding.java new file mode 100644 index 00000000..e7789236 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Embedding.java @@ -0,0 +1,54 @@ +package org.ruoyi.common.chat.entity.embeddings; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class Embedding implements Serializable { + @NonNull + @Builder.Default + private String model = Model.TEXT_EMBEDDING_ADA_002.getName(); + /** + * 必选项:长度不能超过:8192 + */ + @NonNull + private List input; + + private String user; + + public void setModel(Model model) { + if (Objects.isNull(model)) { + model = Model.TEXT_EMBEDDING_ADA_002; + } + this.model = model.getName(); + } + + + public void setUser(String user) { + this.user = user; + } + + @Getter + @AllArgsConstructor + public enum Model { + TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), + ; + private String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/EmbeddingResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/EmbeddingResponse.java new file mode 100644 index 00000000..00868ef7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/EmbeddingResponse.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.chat.entity.embeddings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.ruoyi.common.chat.entity.common.Usage; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class EmbeddingResponse implements Serializable { + + private String object; + private List data; + private String model; + private Usage usage; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Item.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Item.java new file mode 100644 index 00000000..ba9deba9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/embeddings/Item.java @@ -0,0 +1,16 @@ +package org.ruoyi.common.chat.entity.embeddings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Item implements Serializable { + private String object; + private List embedding; + private Integer index; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/engines/Engine.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/engines/Engine.java new file mode 100644 index 00000000..ce375a46 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/engines/Engine.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.chat.entity.engines; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Engine implements Serializable { + + private String id; + private String object; + private String owner; + private boolean ready; + private Object permissions; + private long created; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/File.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/File.java new file mode 100644 index 00000000..b696962d --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/File.java @@ -0,0 +1,34 @@ +package org.ruoyi.common.chat.entity.files; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class File implements Serializable { + +// private String id; +// private String object; +// private long bytes; +// private long created_at; +// private String filename; +// private String purpose; +// private String status; +// @JsonProperty("status_details") +// private String statusDetails; + + private long bytes; + private long created_at; + private String filename; + private String id; + private String object; + private String url; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/UploadFileResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/UploadFileResponse.java new file mode 100644 index 00000000..6d1bcda7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/files/UploadFileResponse.java @@ -0,0 +1,17 @@ +package org.ruoyi.common.chat.entity.files; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class UploadFileResponse extends File implements Serializable { +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/Event.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/Event.java new file mode 100644 index 00000000..f8b68492 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/Event.java @@ -0,0 +1,17 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Event implements Serializable { + private String object; + @JsonProperty("created_at") + private long createdAt; + private String level; + private String message; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTune.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTune.java new file mode 100644 index 00000000..26ad2f1d --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTune.java @@ -0,0 +1,122 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class FineTune implements Serializable { + + /** + * 上传的文件ID + */ + @NonNull + @JsonProperty("training_file") + private String trainingFile; + + @JsonProperty("validation_file") + private String validationFile; + /** + * 参考 + * @see Model + */ + private String model; + + @JsonProperty("n_epochs") + @Builder.Default + private Integer n_epochs = 4; + + @JsonProperty("batch_size") + private Integer batchSize; + + @JsonProperty("learning_rate_multiplier") + private Double learningRateMultiplier; + + @JsonProperty("prompt_loss_weight") + @Builder.Default + private Double promptLossWeight = 0.01; + + @JsonProperty("compute_classification_metrics") + @Builder.Default + private boolean computeClassificationMetrics = false; + + @JsonProperty("classification_n_classes") + private Integer classificationNClasses; + + @JsonProperty("classification_betas") + private List classificationBetas; + + private String suffix; + + public void setTrainingFile(String trainingFile) { + this.trainingFile = trainingFile; + } + + public void setValidationFile(String validationFile) { + this.validationFile = validationFile; + } + + public void setModel(String model) { + this.model = model; + } + + public void setN_epochs(Integer n_epochs) { + this.n_epochs = n_epochs; + } + + public void setBatchSize(Integer batchSize) { + this.batchSize = batchSize; + } + + public void setLearningRateMultiplier(Double learningRateMultiplier) { + this.learningRateMultiplier = learningRateMultiplier; + } + + public void setPromptLossWeight(Double promptLossWeight) { + this.promptLossWeight = promptLossWeight; + } + + public void setComputeClassificationMetrics(boolean computeClassificationMetrics) { + this.computeClassificationMetrics = computeClassificationMetrics; + } + + public void setClassificationNClasses(Integer classificationNClasses) { + this.classificationNClasses = classificationNClasses; + } + + public void setClassificationBetas(List classificationBetas) { + this.classificationBetas = classificationBetas; + } + + public void setSuffix(String suffix) { + if(Objects.nonNull(suffix) && !"".equals(suffix) && suffix.length() > 40){ + log.error("后缀长度不能大于40"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + this.suffix = suffix; + } + + @Getter + @AllArgsConstructor + public enum Model { + // or a fine-tuned model created after 2022-04-21. + ADA("ada"), + BABBAGE("babbage"), + CURIE("curie"), + DAVINCI("davinci"), + ; + private String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneDeleteResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneDeleteResponse.java new file mode 100644 index 00000000..90a145b4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneDeleteResponse.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FineTuneDeleteResponse implements Serializable { + + private String id; + + private String object; + + private boolean deleted; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneResponse.java new file mode 100644 index 00000000..5b8a3d3b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/FineTuneResponse.java @@ -0,0 +1,49 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FineTuneResponse implements Serializable { + + private String id; + + private String object; + + private String model; + + @JsonProperty("created_at") + private long createdAt; + + private List events; + + @JsonProperty("fine_tuned_model") + private String fineTunedModel; + + @JsonProperty("hyperparams") + private HyperParam hyperParams; + + @JsonProperty("organization_id") + private String organizationId; + + @JsonProperty("result_files") + private List resultFiles; + + private String status; + + @JsonProperty("validation_files") + private List validationFiles; + + @JsonProperty("training_files") + private List trainingFiles; + + @JsonProperty("updated_at") + private long updatedAt; + + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/HyperParam.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/HyperParam.java new file mode 100644 index 00000000..c685f660 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/HyperParam.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class HyperParam implements Serializable { + + @JsonProperty("batch_size") + private Integer batchSize; + @JsonProperty("learning_rate_multiplier") + private Double learningRateMultiplier; + @JsonProperty("n_epochs") + private Integer nEpochs; + @JsonProperty("prompt_loss_weight") + private Double promptLossWeight; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/TrainingFile.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/TrainingFile.java new file mode 100644 index 00000000..afd371ae --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/fineTune/TrainingFile.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.chat.entity.fineTune; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TrainingFile implements Serializable { + + private String id; + private String object; + private long bytes; + @JsonProperty("created_at") + private long createdAt; + private String filename; + private String purpose; + private String status; + @JsonProperty("status_details") + private String statusDetails; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Image.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Image.java new file mode 100644 index 00000000..08754494 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Image.java @@ -0,0 +1,111 @@ +package org.ruoyi.common.chat.entity.images; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class Image implements Serializable { + + /** + * 提示词:dall-e-2支持1000字符、dall-e-3支持4000字符 + */ + private String prompt; + /** + * 支持dall-e-2、dall-e-3 + * + * @see Model + */ + @Builder.Default + private String model = Model.DALL_E_3.getName(); + + /** + * 此参数仅仅dall-e-3,默认值:standard + * + * @see Quality + */ + private String quality; + + /** + * 为每个提示生成的个数,dall-e-3只能为1。 + */ + private Integer n; + /** + * 图片尺寸,默认值:1024x1024 + * dall-e-2支持:256x256, 512x512, or 1024x1024 + * dall-e-3支持:1024x1024, 1792x1024, or 1024x1792 + * + * @see SizeEnum + */ + private String size; + /** + * 此参数仅仅dall-e-3,取值范围:vivid、natural + * 默认值:vivid + * + * @see Style + */ + private String style; + + /** + * 生成图片格式:url、b64_json + * + * @see ResponseFormat + */ + @JsonProperty("response_format") + private String responseFormat; + + private String user; + + /** + * 图片生成模型 + */ + @Getter + @AllArgsConstructor + public enum Model { + DALL_E_2("dall-e-2"), + DALL_E_3("dall-e-3"), + ; + private final String name; + } + + /** + * 生成图片质量 + */ + @Getter + @AllArgsConstructor + public enum Quality { + STANDARD("standard"), + HD("hd"), + ; + private final String name; + } + + /** + * 生成图片风格 + */ + @Getter + @AllArgsConstructor + public enum Style { + VIVID("vivid"), + NATURAL("natural"), + ; + private final String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageEdit.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageEdit.java new file mode 100644 index 00000000..eac0a61b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageEdit.java @@ -0,0 +1,98 @@ +package org.ruoyi.common.chat.entity.images; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class ImageEdit implements Serializable { + /** + * 必选项:描述文字,最多1000字符 + */ + @NonNull + private String prompt; + /** + * 为每个提示生成的完成次数。 + */ + @Builder.Default + private Integer n = 1; + /** + * 256x256 + * 512x512 + * 1024x1024 + */ + @Builder.Default + private String size = SizeEnum.size_512.getName(); + + @JsonProperty("response_format") + @Builder.Default + private String responseFormat = ResponseFormat.URL.getName(); + + private String user; + + public ImageEdit setN(Integer n) { + if(n < 1){ + log.warn("n最小值1"); + n = 1; + } + if(n > 10){ + log.warn("n最大值10"); + n = 10; + } + this.n = n; + return this; + } + + public ImageEdit setPrompt(String prompt) { + if(Objects.isNull(prompt) || "".equals(prompt)){ + log.error("参数异常"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + if(prompt.length() > 1000){ + log.error("长度超过1000"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + this.prompt = prompt; + return this; + } + + public ImageEdit setSize(SizeEnum size) { + if(Objects.isNull(size)){ + size = SizeEnum.size_512; + } + this.size = size.getName(); + return this; + } + + public ImageEdit setResponseFormat(ResponseFormat responseFormat) { + if(Objects.isNull(responseFormat)){ + responseFormat = ResponseFormat.URL; + } + this.responseFormat = responseFormat.getName(); + return this; + } + + public ImageEdit setUser(String user) { + this.user = user; + return this; + } + + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageResponse.java new file mode 100644 index 00000000..ca61b454 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageResponse.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.entity.images; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ImageResponse implements Serializable { + private long created; + private List data; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageVariations.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageVariations.java new file mode 100644 index 00000000..34b81b9a --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ImageVariations.java @@ -0,0 +1,81 @@ +package org.ruoyi.common.chat.entity.images; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class ImageVariations implements Serializable { + /** + * 为每个提示生成的完成次数。 + */ + @Builder.Default + private Integer n = 1; + /** + * 256x256 + * 512x512 + * 1024x1024 + */ + @Builder.Default + private String size = SizeEnum.size_512.getName(); + + @JsonProperty("response_format") + @Builder.Default + private String responseFormat = ResponseFormat.URL.getName(); + + private String user; + + + public void setN(Integer n) { + if (n < 1) { + log.warn("n最小值1"); + this.n = 1; + return; + } + if (n > 10) { + log.warn("n最大值10"); + this.n = 10; + return; + } + this.n = n; + } + + + public void setSize(SizeEnum size) { + if (Objects.isNull(size)) { + size = SizeEnum.size_512; + } + this.size = size.getName(); + } + + public void setResponseFormat(ResponseFormat responseFormat) { + if (Objects.isNull(responseFormat)) { + responseFormat = ResponseFormat.URL; + } + this.responseFormat = responseFormat.getName(); + } + + public void setUser(String user) { + this.user = user; + } + + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Item.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Item.java new file mode 100644 index 00000000..da7a158b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/Item.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.chat.entity.images; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Item implements Serializable { + private String url; + @JsonProperty("b64_json") + private String b64Json; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ResponseFormat.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ResponseFormat.java new file mode 100644 index 00000000..4c06dcd2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/ResponseFormat.java @@ -0,0 +1,22 @@ +package org.ruoyi.common.chat.entity.images; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@AllArgsConstructor +@Getter +public enum ResponseFormat implements Serializable { + URL("url"), + B64_JSON("b64_json"), + ; + + private String name; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/SizeEnum.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/SizeEnum.java new file mode 100644 index 00000000..d8b02046 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/images/SizeEnum.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.chat.entity.images; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@AllArgsConstructor +public enum SizeEnum implements Serializable { + size_1024_1792("1024x1792"), + size_1792_1024("1792x1024"), + size_1024("1024x1024"), + size_512("512x512"), + size_256("256x256"), + + ; + private String name; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Model.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Model.java new file mode 100644 index 00000000..2848e126 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Model.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.chat.entity.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Model implements Serializable { + + private String id; + private String object; + private long created; + @JsonProperty("owned_by") + private String ownedBy; + @JsonProperty("permission") + private List permission; + private String root; + private Object parent; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/ModelResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/ModelResponse.java new file mode 100644 index 00000000..6aa4cea0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/ModelResponse.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.entity.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ModelResponse implements Serializable { + private String object; + private List data; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Permission.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Permission.java new file mode 100644 index 00000000..fd66a0a6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/models/Permission.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.chat.entity.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Permission implements Serializable { + + private String id; + @JsonProperty("object") + private String object; + @JsonProperty("created") + private long created; + @JsonProperty("allow_create_engine") + private boolean allowCreateEngine; + @JsonProperty("allow_sampling") + private boolean allowSampling; + @JsonProperty("allow_logprobs") + private boolean allowLogprobs; + @JsonProperty("allow_search_indices") + private boolean allowSearchIndices; + @JsonProperty("allow_view") + private boolean allowView; + @JsonProperty("allow_fine_tuning") + private boolean allowFineTuning; + @JsonProperty("organization") + private String organization; + /** + * 不知道是什么类型的数据 + */ + @JsonProperty("group") + private Object group; + @JsonProperty("is_blocking") + private boolean isBlocking; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Categories.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Categories.java new file mode 100644 index 00000000..ebe72e69 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Categories.java @@ -0,0 +1,50 @@ +package org.ruoyi.common.chat.entity.moderations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Categories implements Serializable { + /** + * 表达、煽动或宣扬基于种族、性别、民族、宗教、国籍、性取向、残疾状况或种姓的仇恨的内容。 + */ + private boolean hate; + /** + * 仇恨内容,还包括对目标群体的暴力或严重伤害。 + */ + @JsonProperty("hate/threatening") + private boolean hateThreatening; + /** + * 宣扬、鼓励或描绘自残行为(例如自杀、割伤和饮食失调)的内容。 + */ + @JsonProperty("self-harm") + private boolean selfHarm; + /** + * 旨在引起性兴奋的内容,例如对性活动的描述,或宣传性服务(不包括性教育和健康)的内容。 + */ + private boolean sexual; + /** + * 包含未满 18 周岁的个人的色情内容。 + */ + @JsonProperty("sexual/minors") + private boolean sexualMinors; + /** + * 宣扬或美化暴力或歌颂他人遭受苦难或羞辱的内容。 + */ + private boolean violence; + /** + * 以极端血腥细节描绘死亡、暴力或严重身体伤害的暴力内容。 + */ + @JsonProperty("violence/graphic") + private boolean violenceGraphic; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/CategoryScores.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/CategoryScores.java new file mode 100644 index 00000000..b14e230a --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/CategoryScores.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.chat.entity.moderations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CategoryScores implements Serializable { + private BigDecimal hate; + @JsonProperty("hate/threatening") + private BigDecimal hateThreatening; + @JsonProperty("self-harm") + private BigDecimal selfHarm; + private BigDecimal sexual; + @JsonProperty("sexual/minors") + private BigDecimal sexualMinors; + private BigDecimal violence; + @JsonProperty("violence/graphic") + private BigDecimal violenceGraphic; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Moderation.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Moderation.java new file mode 100644 index 00000000..42dd1bef --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Moderation.java @@ -0,0 +1,54 @@ +package org.ruoyi.common.chat.entity.moderations; + +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * 描述:文本审核,敏感词鉴别 + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Builder +@Slf4j +@NoArgsConstructor +@AllArgsConstructor +public class Moderation implements Serializable { + + @NonNull + private List input; + @Builder.Default + private String model = Model.TEXT_MODERATION_LATEST.getName(); + + public void setInput(List input) { + if (Objects.isNull(input) || input.size() == 0) { + log.error("input不能为空"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + this.input = input; + } + + public void setModel(Model model) { + if (Objects.isNull(model)) { + model = Model.TEXT_MODERATION_LATEST; + } + this.model = model.getName(); + } + + @Getter + @AllArgsConstructor + public enum Model { + TEXT_MODERATION_STABLE("text-moderation-stable"), + TEXT_MODERATION_LATEST("text-moderation-latest"), + ; + + private String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/ModerationResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/ModerationResponse.java new file mode 100644 index 00000000..046cd0b7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/ModerationResponse.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.chat.entity.moderations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ModerationResponse implements Serializable { + private String id; + private String model; + private List results; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Result.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Result.java new file mode 100644 index 00000000..28dee336 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/moderations/Result.java @@ -0,0 +1,22 @@ +package org.ruoyi.common.chat.entity.moderations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Result implements Serializable { + private Categories categories; + @JsonProperty("category_scores") + private CategoryScores categoryScores; + private boolean flagged; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Transcriptions.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Transcriptions.java new file mode 100644 index 00000000..cf8fbdb5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Transcriptions.java @@ -0,0 +1,49 @@ +package org.ruoyi.common.chat.entity.whisper; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +/** + * @author Admin + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Transcriptions extends Whisper { + /** + * 模型目前只支持这一种:WHISPER_1 + */ + @Builder.Default + private String model = Model.WHISPER_1.getName(); + /** + * 提示语,需要与语音语言匹配 + */ + private String prompt; + /** + * 输出的格式,采用以下选项之一:json、text、srt、verbose_json 或 vtt。 + * 默认值:json + */ + @JsonProperty("response_format") + @Builder.Default + private String responseFormat = ResponseFormat.JSON.getName(); + /** + * 温度控制随机效果:0-1,值越大输出更加随机 + * 默认值:0 + */ + @Builder.Default + private Double temperature = 0d; + /** + * 输入音频的语言,以 ISO-639-1 格式提供输入语言将提高准确性和延迟。 + * 参考:ISO-639-1 + */ + private String language; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Translations.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Translations.java new file mode 100644 index 00000000..4ad14129 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Translations.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.chat.entity.whisper; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Translations { + /** + * 模型目前只支持这一种:WHISPER_1 + */ + @Builder.Default + private String model = Whisper.Model.WHISPER_1.getName(); + /** + * 提示语,需要与语音语言匹配 + */ + private String prompt; + /** + * 输出的格式,采用以下选项之一:json、text、srt、verbose_json 或 vtt。 + * 默认值:json + */ + @JsonProperty("response_format") + @Builder.Default + private String responseFormat = Whisper.ResponseFormat.JSON.getName(); + /** + * 温度控制随机效果:0-1,值越大输出更加随机 + * 默认值:0 + */ + @Builder.Default + private double temperature = 0; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Whisper.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Whisper.java new file mode 100644 index 00000000..4ffc5227 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/Whisper.java @@ -0,0 +1,38 @@ +package org.ruoyi.common.chat.entity.whisper; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +import java.io.Serializable; + +/** + * 描述:语音转文字 + * + * @author https:www.unfbx.com + * @since 2023-03-02 + */ +@Data +public class Whisper implements Serializable { + + + @Getter + @AllArgsConstructor + public enum Model { + WHISPER_1("whisper-1"), + ; + private String name; + } + + @Getter + @AllArgsConstructor + public enum ResponseFormat { + JSON("json"), + TEXT("text"), + SRT("srt"), + VERBOSE_JSON("verbose_json"), + VTT("vtt"), + ; + private String name; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/WhisperResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/WhisperResponse.java new file mode 100644 index 00000000..3635dd93 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/whisper/WhisperResponse.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.chat.entity.whisper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @since 2023-03-02 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class WhisperResponse implements Serializable { + + private String text; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java new file mode 100644 index 00000000..1e79f2a8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java @@ -0,0 +1,139 @@ +package org.ruoyi.common.chat.handler; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.config.LocalCache; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.holder.WebSocketSessionHolder; +import org.ruoyi.common.chat.listener.WebSocketEventListener; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.utils.WebSocketUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * WebSocketHandler 实现类 + * + * @author zendwang + */ +@Slf4j +public class PlusWebSocketHandler extends AbstractWebSocketHandler { + + /** + * 连接成功后 + */ + @Override + public void afterConnectionEstablished(WebSocketSession session) { + WebSocketSessionHolder.addSession(session.getId(), session); + } + + /** + * 处理发送来的文本消息 + * + * @param session + * @param message + */ + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + WebSocketEventListener eventSourceListener = new WebSocketEventListener(session); + String messageContext = (String) LocalCache.CACHE.get(session.getId()); + List messages = new ArrayList<>(); + if (StrUtil.isNotBlank(messageContext)) { + messages = JSONUtil.toList(messageContext, Message.class); + // 上下文长度 + int contextSize=10; + if (messages.size() >= contextSize) { + messages = messages.subList(1, contextSize); + } + Message currentMessage = Message.builder().content(message.getPayload()).role(Message.Role.USER).build(); + messages.add(currentMessage); + } else { + Message currentMessage = Message.builder().content(message.getPayload()).role(Message.Role.USER).build(); + messages.add(currentMessage); + } + ChatCompletion chatCompletion = ChatCompletion + .builder() + .model(ChatCompletion.Model.GPT_3_5_TURBO.getName()) + .messages(messages) + .temperature(0.2) + .stream(true) + .build(); + OpenAiStreamClient openAiStreamClient=(OpenAiStreamClient) SpringUtils.context().getBean("openAiStreamClient"); + openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener); + LocalCache.CACHE.put(session.getId(), JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); + } + + /** + * 根据key获取Value值 + * + * @Date 2023/7/27 + * @param jsonObject + * @param key + * @param defaultValue + * @return String + **/ + public String getValue(JSONObject jsonObject,String key,String defaultValue){ + String value = (String)jsonObject.get(key); + if(StrUtil.isEmpty(value)){ + return defaultValue; + } + return value; + } + + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { + super.handleBinaryMessage(session, message); + } + + /** + * 心跳监测的回复 + * + * @param session + * @param message + * @throws Exception + */ + @Override + protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception { + WebSocketUtils.sendPongMessage(session); + } + + /** + * 连接出错时 + * + * @param session + * @param exception + * @throws Exception + */ + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage()); + } + + /** + * 连接关闭后 + * + * @param session + * @param status + */ + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + WebSocketSessionHolder.removeSession(session.getId()); + } + + /** + * 是否支持分片消息 + * + * @return + */ + @Override + public boolean supportsPartialMessages() { + return false; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/holder/WebSocketSessionHolder.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/holder/WebSocketSessionHolder.java new file mode 100644 index 00000000..793a8d8a --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/holder/WebSocketSessionHolder.java @@ -0,0 +1,37 @@ +package org.ruoyi.common.chat.holder; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * WebSocketSession 用于保存当前所有在线的会话信息 + * + * @author zendwang + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebSocketSessionHolder { + + private static final Map USER_SESSION_MAP = new ConcurrentHashMap<>(); + + public static void addSession(String sessionKey, WebSocketSession session) { + USER_SESSION_MAP.put(sessionKey, session); + } + + public static void removeSession(String sessionKey) { + if (USER_SESSION_MAP.containsKey(sessionKey)) { + USER_SESSION_MAP.remove(sessionKey); + } + } + + public static WebSocketSession getSessions(Long sessionKey) { + return USER_SESSION_MAP.get(sessionKey); + } + + public static Boolean existSession(Long sessionKey) { + return USER_SESSION_MAP.containsKey(sessionKey); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/interceptor/PlusWebSocketInterceptor.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/interceptor/PlusWebSocketInterceptor.java new file mode 100644 index 00000000..e5548dd3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/interceptor/PlusWebSocketInterceptor.java @@ -0,0 +1,46 @@ +package org.ruoyi.common.chat.interceptor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +/** + * WebSocket握手请求的拦截器 + * + * @author zendwang + */ +@Slf4j +public class PlusWebSocketInterceptor implements HandshakeInterceptor { + + /** + * 握手前 + * + * @param request request + * @param response response + * @param wsHandler wsHandler + * @param attributes attributes + * @return 是否握手成功 + */ + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { + return true; + } + + + /** + * 握手后 + * + * @param request request + * @param response response + * @param wsHandler wsHandler + * @param exception 异常 + */ + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java new file mode 100644 index 00000000..2cc3529b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java @@ -0,0 +1,93 @@ +package org.ruoyi.common.chat.listener; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.ruoyi.common.chat.constant.OpenAIConst; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Objects; + +/** + * 描述:OpenAI流式输出Socket接收 + * + * @author https:www.unfbx.com + * @date 2023-03-23 + */ +@Slf4j +public class WebSocketEventListener extends EventSourceListener { + + private WebSocketSession session; + + /** + * 消息结束标识 + */ + private final String msgEnd = "[DONE]"; + + public WebSocketEventListener(WebSocketSession session) { + this.session = session; + } + + /** + * {@inheritDoc} + */ + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("OpenAI建立Socket连接..."); + } + + /** + * {@inheritDoc} + */ + @SneakyThrows + @Override + public void onEvent(EventSource eventSource, String id, String type, String data) { + log.info("OpenAI返回数据:{}", data); + if (data.equals(msgEnd)) { + log.info("OpenAI返回数据结束了"); + session.sendMessage(new TextMessage(msgEnd)); + return; + } + ObjectMapper mapper = new ObjectMapper(); + // 读取Json + ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); + String delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta()); + session.sendMessage(new TextMessage(delta)); + } + + + @Override + public void onClosed(EventSource eventSource) { + log.info("OpenAI关闭Socket连接..."); + } + + + @SneakyThrows + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + if (Objects.isNull(response)) { + return; + } + ResponseBody body = response.body(); + if (Objects.nonNull(body)) { + // 返回非流式回复内容 + if(response.code() == OpenAIConst.SUCCEED_CODE){ + ObjectMapper mapper = new ObjectMapper(); + ChatCompletionResponse completionResponse = mapper.readValue(body.string(), ChatCompletionResponse.class); + String delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getMessage().getContent()); + session.sendMessage(new TextMessage(delta)); + }else { + log.error("Socket连接异常data:{},异常:{}", body.string(), t); + } + } else { + log.error("Socket连接异常data:{},异常:{}", response, t); + } + eventSource.cancel(); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketTopicListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketTopicListener.java new file mode 100644 index 00000000..47be53d8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketTopicListener.java @@ -0,0 +1,38 @@ +package org.ruoyi.common.chat.listener; + +import cn.hutool.core.collection.CollUtil; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.holder.WebSocketSessionHolder; +import org.ruoyi.common.chat.utils.WebSocketUtils; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.Ordered; + +/** + * WebSocket 主题订阅监听器 + * + * @author zendwang + */ +@Slf4j +public class WebSocketTopicListener implements ApplicationRunner, Ordered { + + @Override + public void run(ApplicationArguments args) throws Exception { + WebSocketUtils.subscribeMessage((message) -> { + log.info("WebSocket主题订阅收到消息session keys={} message={}!", message.getSessionKeys(), message.getMessage()); + if (CollUtil.isNotEmpty(message.getSessionKeys())) { + message.getSessionKeys().forEach(key -> { + if (WebSocketSessionHolder.existSession(key)) { + WebSocketUtils.sendMessage(key, message.getMessage()); + } + }); + } + }); + log.info("初始化WebSocket主题订阅监听器成功"); + } + + @Override + public int getOrder() { + return -1; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiApi.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiApi.java new file mode 100644 index 00000000..bb0bd124 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiApi.java @@ -0,0 +1,355 @@ +package org.ruoyi.common.chat.openai; + +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import org.ruoyi.common.chat.entity.Tts.TextToSpeech; +import org.ruoyi.common.chat.entity.billing.BillingUsage; +import org.ruoyi.common.chat.entity.billing.CreditGrantsResponse; +import org.ruoyi.common.chat.entity.billing.Subscription; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.ruoyi.common.chat.entity.chat.ChatCompletionWithPicture; +import org.ruoyi.common.chat.entity.common.DeleteResponse; +import org.ruoyi.common.chat.entity.common.OpenAiResponse; +import org.ruoyi.common.chat.entity.completions.Completion; +import org.ruoyi.common.chat.entity.completions.CompletionResponse; +import org.ruoyi.common.chat.entity.edits.Edit; +import org.ruoyi.common.chat.entity.edits.EditResponse; +import org.ruoyi.common.chat.entity.embeddings.Embedding; +import org.ruoyi.common.chat.entity.embeddings.EmbeddingResponse; +import org.ruoyi.common.chat.entity.engines.Engine; +import org.ruoyi.common.chat.entity.files.File; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.fineTune.Event; +import org.ruoyi.common.chat.entity.fineTune.FineTune; +import org.ruoyi.common.chat.entity.fineTune.FineTuneDeleteResponse; +import org.ruoyi.common.chat.entity.fineTune.FineTuneResponse; +import org.ruoyi.common.chat.entity.images.Image; +import org.ruoyi.common.chat.entity.images.ImageResponse; +import org.ruoyi.common.chat.entity.models.Model; +import org.ruoyi.common.chat.entity.models.ModelResponse; +import org.ruoyi.common.chat.entity.moderations.Moderation; +import org.ruoyi.common.chat.entity.moderations.ModerationResponse; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import retrofit2.Call; +import retrofit2.http.*; + +import java.time.LocalDate; +import java.util.Map; + +/** + * 描述: open ai官方api接口 + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +public interface OpenAiApi { + + /** + * 模型列表 + * + * @return Single ModelResponse + */ + @GET("v1/models") + Single models(); + + /** + * models 返回的数据id + * + * @param id 模型主键 + * @return Single Model + */ + @GET("v1/models/{id}") + Single model(@Path("id") String id); + + /** + * 文本问答 + * Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + * + * @param completion 问答参数 + * @return Single CompletionResponse + */ + @POST("v1/completions") + Single completions(@Body Completion completion); + + /** + * Creates a new edit for the provided input, instruction, and parameters. + * 文本修复 + * + * @param edit 编辑参数 + * @return Single EditResponse + */ + @POST("v1/edits") + Single edits(@Body Edit edit); + + /** + * Creates an image given a prompt. + * 根据描述生成图片 + * + * @param image 图片对象 + * @return Single ImageResponse + */ + @POST("v1/images/generations") + Single genImages(@Body Image image); + + /** + * Creates an edited or extended image given an original image and a prompt. + * 根据描述修改图片 + * + * @param image 图片对象 + * @param mask 图片对象 + * @param requestBodyMap 请求参数 + * @return Single ImageResponse + */ + @Multipart + @POST("v1/images/edits") + Single editImages(@Part() MultipartBody.Part image, + @Part() MultipartBody.Part mask, + @PartMap() Map requestBodyMap + ); + + /** + * Creates a variation of a given image. + * + * @param image 图片对象 + * @param requestBodyMap 请求参数 + * @return Single ImageResponse + */ + @Multipart + @POST("v1/images/variations") + Single variationsImages(@Part() MultipartBody.Part image, + @PartMap() Map requestBodyMap + ); + + /** + * 文本向量计算 + * + * @param embedding 向量参数 + * @return Single EmbeddingResponse + */ + @POST("v1/embeddings") + Single embeddings(@Body Embedding embedding); + + + /** + * Returns a list of files that belong to the user's organization. + * + * @return Single OpenAiResponse File + */ + @GET("/v1/files") + Single> files(); + + /** + * 删除文件 + * + * @param fileId 文件id + * @return Single DeleteResponse + */ + @DELETE("v1/files/{file_id}") + Single deleteFile(@Path("file_id") String fileId); + + /** + * 上传文件 + * + * @param purpose purpose + * @param file 文件对象 + * @return Single UploadFileResponse + */ + @Multipart + @POST("v1/files") + Single uploadFile(@Part MultipartBody.Part file, + @Part("purpose") RequestBody purpose); + + + /** + * 检索文件 + * + * @param fileId 文件id + * @return Single File + */ + @GET("v1/files/{file_id}") + Single retrieveFile(@Path("file_id") String fileId); + + /** + * 检索文件内容 + * ###不对免费用户开放### + * ###不对免费用户开放### + * ###不对免费用户开放### + * + * @param fileId 文件id + * @return Single ResponseBody + */ + @Streaming + @GET("v1/files/{file_id}/content") + Single retrieveFileContent(@Path("file_id") String fileId); + + + /** + * 文本审核 + * + * @param moderation 文本审核参数 + * @return Single ModerationResponse + */ + @POST("v1/moderations") + Single moderations(@Body Moderation moderation); + + + /** + * 创建微调作业 + * + * @param fineTune 微调 + * @return Single FineTuneResponse + */ + @POST("v1/fine-tunes") + Single fineTune(@Body FineTune fineTune); + + /** + * 微调作业集合 + * + * @return Single OpenAiResponse FineTuneResponse + */ + @GET("v1/fine-tunes") + Single> fineTunes(); + + + /** + * 检索微调作业 + * + * @return Single FineTuneResponse + */ + @GET("v1/fine-tunes/{fine_tune_id}") + Single retrieveFineTune(@Path("fine_tune_id") String fineTuneId); + + /** + * 取消微调作业 + * + * @return Single FineTuneResponse + */ + @POST("v1/fine-tunes/{fine_tune_id}/cancel") + Single cancelFineTune(@Path("fine_tune_id") String fineTuneId); + + /** + * 微调作业事件列表 + * + * @return Single OpenAiResponse Event + */ + @GET("v1/fine-tunes/{fine_tune_id}/events") + Single> fineTuneEvents(@Path("fine_tune_id") String fineTuneId); + + /** + * 删除微调作业模型 + * Delete a fine-tuned model. You must have the Owner role in your organization. + * + * @return Single DeleteResponse + */ + @DELETE("v1/models/{model}") + Single deleteFineTuneModel(@Path("model") String model); + + + /** + * 引擎列表 + * 官方已废弃此接口 + * + * @return Single OpenAiResponse Engine + */ + @Deprecated + @GET("v1/engines") + Single> engines(); + + /** + * 检索引擎 + * 官方已废弃此接口 + * + * @param engineId 引擎id + * @return Engine + */ + @Deprecated + @GET("v1/engines/{engine_id}") + Single engine(@Path("engine_id") String engineId); + + + /** + * 最新版的GPT-3.5 chat completion 更加贴近官方网站的问答模型 + * + * @param chatCompletion chat completion + * @return 返回答案 + */ + @POST("v1/chat/completions") + Single chatCompletion(@Body ChatCompletion chatCompletion); + + + /** + * 语音转文字 + * + * @param file 语音文件 + * @param requestBodyMap 参数 + * @return 文本 + */ + @Multipart + @POST("v1/audio/transcriptions") + Single speechToTextTranscriptions(@Part MultipartBody.Part file, + @PartMap() Map requestBodyMap); + + /** + * 语音翻译:目前仅支持翻译为英文 + * + * @param file 语音文件 + * @param requestBodyMap 参数 + * @return 文本 + */ + @Multipart + @POST("v1/audio/translations") + Single speechToTextTranslations(@Part MultipartBody.Part file, + @PartMap() Map requestBodyMap); + + /** + * 余额查询 + * 官方禁止访问此接口 + * + * @return 余额结果 + */ + @GET("dashboard/billing/credit_grants") + @Deprecated + Single creditGrants(); + + /** + * 账户信息查询:里面包含总金额(美元)等信息 + * + * @return 账户信息 + */ + @GET("v1/dashboard/billing/subscription") + Single subscription(); + + /** + * 账户调用接口消耗金额信息查询 + * totalUsage = 账户总使用金额(美分) + * + * @param starDate 开始时间 + * @param endDate 结束时间 + * @return 消耗金额信息 + */ + @GET("v1/dashboard/billing/usage") + Single billingUsage(@Query("start_date") LocalDate starDate, @Query("end_date") LocalDate endDate); + + /** + * 最新版的GPT-4 chat completion 支持图片输入 + * + * @param chatCompletion chat completion + * @return 返回答案 + */ + @POST("v1/chat/completions") + Single chatCompletionWithPicture(@Body ChatCompletionWithPicture chatCompletion); + + /** + * 文本转语音 + * + * @param textToSpeech 参数 + * @return ResponseBody body + * @since 1.1.2 + */ + @POST("v1/audio/speech") + @Streaming + Call textToSpeech(@Body TextToSpeech textToSpeech); +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java new file mode 100644 index 00000000..0ee97662 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java @@ -0,0 +1,805 @@ +package org.ruoyi.common.chat.openai; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import io.reactivex.Single; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import org.ruoyi.common.chat.constant.OpenAIConst; +import org.ruoyi.common.chat.entity.billing.BillingUsage; +import org.ruoyi.common.chat.entity.billing.Subscription; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.entity.common.DeleteResponse; +import org.ruoyi.common.chat.entity.common.OpenAiResponse; +import org.ruoyi.common.chat.entity.completions.Completion; +import org.ruoyi.common.chat.entity.completions.CompletionResponse; +import org.ruoyi.common.chat.entity.edits.Edit; +import org.ruoyi.common.chat.entity.edits.EditResponse; +import org.ruoyi.common.chat.entity.embeddings.Embedding; +import org.ruoyi.common.chat.entity.embeddings.EmbeddingResponse; +import org.ruoyi.common.chat.entity.engines.Engine; +import org.ruoyi.common.chat.entity.files.File; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.fineTune.Event; +import org.ruoyi.common.chat.entity.fineTune.FineTune; +import org.ruoyi.common.chat.entity.fineTune.FineTuneDeleteResponse; +import org.ruoyi.common.chat.entity.fineTune.FineTuneResponse; +import org.ruoyi.common.chat.entity.images.*; +import org.ruoyi.common.chat.entity.models.Model; +import org.ruoyi.common.chat.entity.models.ModelResponse; +import org.ruoyi.common.chat.entity.moderations.Moderation; +import org.ruoyi.common.chat.entity.moderations.ModerationResponse; +import org.ruoyi.common.chat.entity.whisper.Translations; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.chat.openai.function.KeyRandomStrategy; +import org.ruoyi.common.chat.openai.function.KeyStrategyFunction; +import org.ruoyi.common.chat.openai.interceptor.DefaultOpenAiAuthInterceptor; +import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor; +import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor; +import org.ruoyi.common.core.exception.base.BaseException; +import org.jetbrains.annotations.NotNull; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.time.LocalDate; +import java.util.*; +import java.util.concurrent.TimeUnit; + + +/** + * 描述: open ai 客户端 + * + * @author https:www.unfbx.com + * @since 2023-02-11 + */ + +@Slf4j +public class OpenAiClient { + /** + * keys + */ + @Getter + @NotNull + private List apiKey; + /** + * 自定义api host使用builder的方式构造client + */ + @Getter + private String apiHost; + @Getter + private OpenAiApi openAiApi; + /** + * 自定义的okHttpClient + * 如果不自定义 ,就是用sdk默认的OkHttpClient实例 + */ + @Getter + private OkHttpClient okHttpClient; + /** + * api key的获取策略 + */ + @Getter + private KeyStrategyFunction, String> keyStrategy; + + /** + * 自定义鉴权处理拦截器
    + * 可以不设置,默认实现:DefaultOpenAiAuthInterceptor
    + * 如需自定义实现参考:DealKeyWithOpenAiAuthInterceptor + * + * @see DynamicKeyOpenAiAuthInterceptor + * @see DefaultOpenAiAuthInterceptor + */ + @Getter + private OpenAiAuthInterceptor authInterceptor; + + /** + * 构造器 + * + * @return OpenAiClient.Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 构造 + * + * @param builder + */ + private OpenAiClient(Builder builder) { + if (CollectionUtil.isEmpty(builder.apiKey)) { + throw new BaseException(CommonError.API_KEYS_NOT_NUL.msg() + ); + } + apiKey = builder.apiKey; + + if (StrUtil.isBlank(builder.apiHost)) { + builder.apiHost = OpenAIConst.OPENAI_HOST; + } + apiHost = builder.apiHost; + + if (Objects.isNull(builder.keyStrategy)) { + builder.keyStrategy = new KeyRandomStrategy(); + } + keyStrategy = builder.keyStrategy; + + if (Objects.isNull(builder.authInterceptor)) { + builder.authInterceptor = new DefaultOpenAiAuthInterceptor(); + } + authInterceptor = builder.authInterceptor; + authInterceptor.setApiKey(this.apiKey); + authInterceptor.setKeyStrategy(this.keyStrategy); + + if (Objects.isNull(builder.okHttpClient)) { + builder.okHttpClient = this.okHttpClient(); + } else { + //自定义的okhttpClient 需要增加api keys + builder.okHttpClient = builder.okHttpClient + .newBuilder() + .addInterceptor(authInterceptor) + .build(); + } + okHttpClient = builder.okHttpClient; + this.openAiApi = new Retrofit.Builder() + .baseUrl(apiHost) + .client(okHttpClient) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + .build().create(OpenAiApi.class); + } + + + /** + * 创建默认OkHttpClient + * + * @return + */ + private OkHttpClient okHttpClient() { + if (Objects.isNull(this.authInterceptor)) { + this.authInterceptor = new DefaultOpenAiAuthInterceptor(); + } + this.authInterceptor.setApiKey(this.apiKey); + this.authInterceptor.setKeyStrategy(this.keyStrategy); + return new OkHttpClient + .Builder() + .addInterceptor(this.authInterceptor) + .connectTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS).build(); + } + + /** + * openAi模型列表 + * + * @return Model list + */ + public List models() { + Single models = this.openAiApi.models(); + return models.blockingGet().getData(); + } + + /** + * openAi模型详细信息 + * + * @param id 模型主键 + * @return Model 模型类 + */ + public Model model(String id) { + if (Objects.isNull(id) || "".equals(id)) { + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + Single model = this.openAiApi.model(id); + return model.blockingGet(); + } + + + /** + * 问答接口 + * + * @param completion 问答参数 + * @return CompletionResponse + */ + public CompletionResponse completions(Completion completion) { + Single completions = this.openAiApi.completions(completion); + return completions.blockingGet(); + } + + /** + * 问答接口-简易版 + * + * @param question 问题描述 + * @return CompletionResponse + */ + public CompletionResponse completions(String question) { + Completion q = Completion.builder() + .prompt(question) + .build(); + Single completions = this.openAiApi.completions(q); + return completions.blockingGet(); + } + + /** + * 文本修改 + * + * @param edit 图片对象 + * @return EditResponse + */ + public EditResponse edit(Edit edit) { + Single edits = this.openAiApi.edits(edit); + return edits.blockingGet(); + } + + /** + * 根据描述生成图片 + * + * @param prompt 描述信息 + * @return ImageResponse + */ + public ImageResponse genImages(String prompt) { + Image image = Image.builder().prompt(prompt).build(); + return this.genImages(image); + } + + /** + * 根据描述生成图片 + * + * @param image 图片参数 + * @return ImageResponse + */ + public ImageResponse genImages(Image image) { + Single edits = this.openAiApi.genImages(image); + return edits.blockingGet(); + } + + /** + * Creates an edited or extended image given an original image and a prompt. + * 根据描述修改图片 + * + * @param image 图片对象 + * @param prompt 描述信息 + * @return Item list + */ + public List editImages(java.io.File image, String prompt) { + ImageEdit imageEdit = ImageEdit.builder().prompt(prompt).build(); + return this.editImages(image, null, imageEdit); + } + + /** + * Creates an edited or extended image given an original image and a prompt. + * 根据描述修改图片 + * + * @param image 图片对象 + * @param imageEdit 图片参数 + * @return Item list + */ + public List editImages(java.io.File image, ImageEdit imageEdit) { + return this.editImages(image, null, imageEdit); + } + + /** + * Creates an edited or extended image given an original image and a prompt. + * 根据描述修改图片 + * + * @param image png格式的图片,最大4MB + * @param mask png格式的图片,最大4MB + * @param imageEdit 图片参数 + * @return Item list + */ + public List editImages(java.io.File image, java.io.File mask, ImageEdit imageEdit) { + checkImage(image); + checkImageFormat(image); + checkImageSize(image); + if (Objects.nonNull(mask)) { + checkImageFormat(image); + checkImageSize(image); + } + // 创建 RequestBody,用于封装构建RequestBody + RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), image); + MultipartBody.Part imageMultipartBody = MultipartBody.Part.createFormData("image", image.getName(), imageBody); + MultipartBody.Part maskMultipartBody = null; + if (Objects.nonNull(mask)) { + RequestBody maskBody = RequestBody.create(MediaType.parse("multipart/form-data"), mask); + maskMultipartBody = MultipartBody.Part.createFormData("mask", image.getName(), maskBody); + } + Map requestBodyMap = new HashMap<>(); + requestBodyMap.put("prompt", RequestBody.create(MediaType.parse("multipart/form-data"), imageEdit.getPrompt())); + requestBodyMap.put("n", RequestBody.create(MediaType.parse("multipart/form-data"), imageEdit.getN().toString())); + requestBodyMap.put("size", RequestBody.create(MediaType.parse("multipart/form-data"), imageEdit.getSize())); + requestBodyMap.put("response_format", RequestBody.create(MediaType.parse("multipart/form-data"), imageEdit.getResponseFormat())); + if (!(Objects.isNull(imageEdit.getUser()) || "".equals(imageEdit.getUser()))) { + requestBodyMap.put("user", RequestBody.create(MediaType.parse("multipart/form-data"), imageEdit.getUser())); + } + Single imageResponse = this.openAiApi.editImages( + imageMultipartBody, + maskMultipartBody, + requestBodyMap + ); + return imageResponse.blockingGet().getData(); + } + + /** + * Creates a variation of a given image. + *

    + * 变化图片,类似ai重做图片 + * + * @param image 图片对象 + * @param imageVariations 图片参数 + * @return ImageResponse + */ + public ImageResponse variationsImages(java.io.File image, ImageVariations imageVariations) { + checkImage(image); + checkImageFormat(image); + checkImageSize(image); + RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), image); + MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("image", image.getName(), imageBody); + Map requestBodyMap = new HashMap<>(); + requestBodyMap.put("n", RequestBody.create(MediaType.parse("multipart/form-data"), imageVariations.getN().toString())); + requestBodyMap.put("size", RequestBody.create(MediaType.parse("multipart/form-data"), imageVariations.getSize())); + requestBodyMap.put("response_format", RequestBody.create(MediaType.parse("multipart/form-data"), imageVariations.getResponseFormat())); + if (!(Objects.isNull(imageVariations.getUser()) || "".equals(imageVariations.getUser()))) { + requestBodyMap.put("user", RequestBody.create(MediaType.parse("multipart/form-data"), imageVariations.getUser())); + } + Single variationsImages = this.openAiApi.variationsImages( + multipartBody, + requestBodyMap + ); + return variationsImages.blockingGet(); + } + + /** + * Creates a variation of a given image. + * + * @param image 图片对象 + * @return ImageResponse + */ + public ImageResponse variationsImages(java.io.File image) { + checkImage(image); + checkImageFormat(image); + checkImageSize(image); + ImageVariations imageVariations = ImageVariations.builder().build(); + return this.variationsImages(image, imageVariations); + } + + /** + * 校验图片不能为空 + * + * @param image + */ + private void checkImage(java.io.File image) { + if (Objects.isNull(image)) { + log.error("image不能为空"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + } + + /** + * 校验图片格式 + * + * @param image + */ + private void checkImageFormat(java.io.File image) { + if (!(image.getName().endsWith("png") || image.getName().endsWith("PNG"))) { + log.error("image格式错误"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + } + + /** + * 校验图片大小 + * + * @param image + */ + private void checkImageSize(java.io.File image) { + if (image.length() > 4 * 1024 * 1024) { + log.error("image最大支持4MB"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + } + + /** + * 向量计算:单文本 + * + * @param input 单文本 + * @return EmbeddingResponse + */ + public EmbeddingResponse embeddings(String input) { + List inputs = new ArrayList<>(1); + inputs.add(input); + Embedding embedding = Embedding.builder().input(inputs).build(); + return this.embeddings(embedding); + } + + /** + * 向量计算:集合文本 + * + * @param input 文本集合 + * @return EmbeddingResponse + */ + public EmbeddingResponse embeddings(List input) { + Embedding embedding = Embedding.builder().input(input).build(); + return this.embeddings(embedding); + } + + /** + * 文本转换向量 + * + * @param embedding 入参 + * @return EmbeddingResponse + */ + public EmbeddingResponse embeddings(Embedding embedding) { + Single embeddings = this.openAiApi.embeddings(embedding); + return embeddings.blockingGet(); + } + + /** + * 获取文件列表 + * + * @return File list + */ + public List files() { + Single> files = this.openAiApi.files(); + return files.blockingGet().getData(); + } + + /** + * 删除文件 + * + * @param fileId 文件id + * @return DeleteResponse + */ + public DeleteResponse deleteFile(String fileId) { + Single deleteFile = this.openAiApi.deleteFile(fileId); + return deleteFile.blockingGet(); + } + + /** + * 上传文件 + * + * @param purpose purpose + * @param file 文件对象 + * @return UploadFileResponse + */ + public UploadFileResponse uploadFile(String purpose, java.io.File file) { + // 创建 RequestBody,用于封装构建RequestBody + RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); + MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody); + + RequestBody purposeBody = RequestBody.create(MediaType.parse("multipart/form-data"), purpose); + Single uploadFileResponse = this.openAiApi.uploadFile(multipartBody, purposeBody); + return uploadFileResponse.blockingGet(); + } + + /** + * 上传文件 + * + * @param file 文件 + * @return UploadFileResponse + */ + public UploadFileResponse uploadFile(java.io.File file) { + //purpose 官网示例默认是:fine-tune + return this.uploadFile("fine-tune", file); + } + + /** + * 检索文件 + * + * @param fileId 文件id + * @return File + */ + public File retrieveFile(String fileId) { + Single fileContent = this.openAiApi.retrieveFile(fileId); + return fileContent.blockingGet(); + } + + /** + * 检索文件内容 + * 免费用户无法使用此接口 #未经过测试 + * + * @param fileId + * @return ResponseBody + */ +// public ResponseBody retrieveFileContent(String fileId) { +// Single fileContent = this.openAiApi.retrieveFileContent(fileId); +// return fileContent.blockingGet(); +// } + + /** + * 文本审核 + * + * @param input 待检测数据 + * @return ModerationResponse + */ + public ModerationResponse moderations(String input) { + List content = new ArrayList<>(1); + content.add(input); + Moderation moderation = Moderation.builder().input(content).build(); + return this.moderations(moderation); + } + + /** + * 文本审核 + * + * @param input 待检测数据集合 + * @return ModerationResponse + */ + public ModerationResponse moderations(List input) { + Moderation moderation = Moderation.builder().input(input).build(); + return this.moderations(moderation); + } + + /** + * 文本审核 + * + * @param moderation 审核参数 + * @return ModerationResponse + */ + public ModerationResponse moderations(Moderation moderation) { + Single moderations = this.openAiApi.moderations(moderation); + return moderations.blockingGet(); + } + + /** + * 创建微调模型 + * + * @param fineTune 微调作业id + * @return FineTuneResponse + */ + public FineTuneResponse fineTune(FineTune fineTune) { + Single fineTuneResponse = this.openAiApi.fineTune(fineTune); + return fineTuneResponse.blockingGet(); + } + + /** + * 创建微调模型 + * + * @param trainingFileId 文件id,文件上传返回的id + * @return FineTuneResponse + */ + public FineTuneResponse fineTune(String trainingFileId) { + FineTune fineTune = FineTune.builder().trainingFile(trainingFileId).build(); + return this.fineTune(fineTune); + } + + /** + * 微调模型列表 + * + * @return FineTuneResponse list + */ + public List fineTunes() { + Single> fineTunes = this.openAiApi.fineTunes(); + return fineTunes.blockingGet().getData(); + } + + /** + * 检索微调作业 + * + * @param fineTuneId 微调作业id + * @return FineTuneResponse + */ + public FineTuneResponse retrieveFineTune(String fineTuneId) { + Single fineTune = this.openAiApi.retrieveFineTune(fineTuneId); + return fineTune.blockingGet(); + } + + /** + * 取消微调作业 + * + * @param fineTuneId 主键 + * @return FineTuneResponse + */ + public FineTuneResponse cancelFineTune(String fineTuneId) { + Single fineTune = this.openAiApi.cancelFineTune(fineTuneId); + return fineTune.blockingGet(); + } + + /** + * 微调作业事件列表 + * + * @param fineTuneId 微调作业id + * @return Event List + */ + public List fineTuneEvents(String fineTuneId) { + Single> events = this.openAiApi.fineTuneEvents(fineTuneId); + return events.blockingGet().getData(); + } + + /** + * 删除微调作业模型 + * Delete a fine-tuned model. You must have the Owner role in your organization. + * + * @param model 模型名称 + * @return FineTuneDeleteResponse + */ + public FineTuneDeleteResponse deleteFineTuneModel(String model) { + Single delete = this.openAiApi.deleteFineTuneModel(model); + return delete.blockingGet(); + } + + + /** + * 引擎列表 + * + * @return Engine List + */ + @Deprecated + public List engines() { + Single> engines = this.openAiApi.engines(); + return engines.blockingGet().getData(); + } + + /** + * 引擎详细信息 + * + * @param engineId 引擎id + * @return Engine + */ + @Deprecated + public Engine engine(String engineId) { + Single engine = this.openAiApi.engine(engineId); + return engine.blockingGet(); + } + + /** + * 最新版的GPT-3.5 chat completion 更加贴近官方网站的问答模型 + * + * @param chatCompletion 问答参数 + * @return 答案 + */ + public ChatCompletionResponse chatCompletion(ChatCompletion chatCompletion) { + Single chatCompletionResponse = this.openAiApi.chatCompletion(chatCompletion); + return chatCompletionResponse.blockingGet(); + } + + /** + * 简易版 + * + * @param messages 问答参数 + * @return 答案 + */ + public ChatCompletionResponse chatCompletion(List messages) { + ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).build(); + return this.chatCompletion(chatCompletion); + } + + /** + * 语音翻译:目前仅支持翻译为英文 + * + * @param translations 参数 + * @param file 语音文件 最大支持25MB mp3, mp4, mpeg, mpga, m4a, wav, webm + * @return 翻译后文本 + */ + public WhisperResponse speechToTextTranslations(java.io.File file, Translations translations) { + //文件 + RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); + MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody); + //自定义参数 + Map requestBodyMap = new HashMap<>(5,1L); + + if (StrUtil.isNotBlank(translations.getModel())) { + requestBodyMap.put(Translations.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), translations.getModel())); + } + if (StrUtil.isNotBlank(translations.getPrompt())) { + requestBodyMap.put(Translations.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), translations.getPrompt())); + } + if (StrUtil.isNotBlank(translations.getResponseFormat())) { + requestBodyMap.put(Translations.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), translations.getResponseFormat())); + } + requestBodyMap.put(Translations.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(translations.getTemperature()))); + Single whisperResponse = this.openAiApi.speechToTextTranslations(multipartBody, requestBodyMap); + return whisperResponse.blockingGet(); + } + + /** + * 简易版 语音翻译:目前仅支持翻译为英文 + * + * @param file 语音文件 最大支持25MB mp3, mp4, mpeg, mpga, m4a, wav, webm + * @return 翻译后文本 + */ + public WhisperResponse speechToTextTranslations(java.io.File file) { + Translations translations = Translations.builder().build(); + return this.speechToTextTranslations(file, translations); + } + + /** + * 校验语音文件大小给出提示,目前官方限制25MB,后续可能会改动所以不报错只做提示 + * + * @param file + */ + private void checkSpeechFileSize(java.io.File file) { + if (file.length() > 25 * 1204 * 1024) { + log.warn("2023-03-02官方文档提示:文件不能超出25MB"); + } + } + + /** + * 账户信息查询:里面包含总金额等信息 + * + * @return 账户信息 + */ + public Subscription subscription() { + Single subscription = this.openAiApi.subscription(); + return subscription.blockingGet(); + } + /** + * 账户调用接口消耗金额信息查询 + * 最多查询100天 + * + * @param starDate 开始时间 + * @param endDate 结束时间 + * @return 消耗金额信息 + */ + public BillingUsage billingUsage(@NotNull LocalDate starDate, @NotNull LocalDate endDate) { + Single billingUsage = this.openAiApi.billingUsage(starDate, endDate); + return billingUsage.blockingGet(); + } + + + public static final class Builder { + /** + * api keys + */ + private @NotNull List apiKey; + /** + * api请求地址,结尾处有斜杠 + * + */ + private String apiHost; + /** + * 自定义OkhttpClient + */ + private OkHttpClient okHttpClient; + + /** + * api key的获取策略 + */ + private KeyStrategyFunction keyStrategy; + + /** + * 自定义鉴权拦截器 + */ + private OpenAiAuthInterceptor authInterceptor; + + public Builder() { + } + + /** + * @param val api请求地址,结尾处有斜杠 + * @return Builder对象 + */ + public Builder apiHost(String val) { + apiHost = val; + return this; + } + + public Builder apiKey(@NotNull List val) { + apiKey = val; + return this; + } + + public Builder keyStrategy(KeyStrategyFunction val) { + keyStrategy = val; + return this; + } + + public Builder okHttpClient(OkHttpClient val) { + okHttpClient = val; + return this; + } + + public Builder authInterceptor(OpenAiAuthInterceptor val) { + authInterceptor = val; + return this; + } + + public OpenAiClient build() { + return new OpenAiClient(this); + } + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java new file mode 100644 index 00000000..e293aa41 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java @@ -0,0 +1,480 @@ +package org.ruoyi.common.chat.openai; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.reactivex.Single; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; +import org.ruoyi.common.chat.constant.OpenAIConst; +import org.ruoyi.common.chat.entity.Tts.TextToSpeech; +import org.ruoyi.common.chat.entity.billing.BillingUsage; +import org.ruoyi.common.chat.entity.billing.KeyInfo; +import org.ruoyi.common.chat.entity.billing.Subscription; +import org.ruoyi.common.chat.entity.chat.BaseChatCompletion; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.ruoyi.common.chat.entity.chat.ChatCompletionWithPicture; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.images.Image; +import org.ruoyi.common.chat.entity.images.ImageResponse; +import org.ruoyi.common.chat.entity.models.Model; +import org.ruoyi.common.chat.entity.models.ModelResponse; +import org.ruoyi.common.chat.entity.whisper.Transcriptions; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.chat.openai.function.KeyRandomStrategy; +import org.ruoyi.common.chat.openai.function.KeyStrategyFunction; +import org.ruoyi.common.chat.openai.interceptor.DefaultOpenAiAuthInterceptor; +import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor; +import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor; +import org.ruoyi.common.core.exception.base.BaseException; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * 描述: open ai 客户端 + * + * @author https:www.unfbx.com + * 2023-02-28 + */ + +@Getter +@Slf4j +@Setter +public class OpenAiStreamClient { + + @NotNull + private List apiKey; + /** + * 自定义api host使用builder的方式构造client + */ + private String apiHost; + + /** + * 自定义的okHttpClient + * 如果不自定义 ,就是用sdk默认的OkHttpClient实例 + */ + private OkHttpClient okHttpClient; + + /** + * api key的获取策略 + */ + private KeyStrategyFunction, String> keyStrategy; + + private OpenAiApi openAiApi; + + /** + * 自定义鉴权处理拦截器
    + * 可以不设置,默认实现:DefaultOpenAiAuthInterceptor
    + * 如需自定义实现参考:DealKeyWithOpenAiAuthInterceptor + * + * @see DynamicKeyOpenAiAuthInterceptor + * @see DefaultOpenAiAuthInterceptor + */ + private OpenAiAuthInterceptor authInterceptor; + + private static final String DONE_SIGNAL = "[DONE]"; + + /** + * 构造实例对象 + * + * @param builder + */ + private OpenAiStreamClient(Builder builder) { + if (CollectionUtil.isEmpty(builder.apiKey)) { + throw new BaseException(CommonError.API_KEYS_NOT_NUL.msg()); + } + apiKey = builder.apiKey; + + if (StrUtil.isBlank(builder.apiHost)) { + builder.apiHost = OpenAIConst.OPENAI_HOST; + } + apiHost = builder.apiHost; + + if (Objects.isNull(builder.keyStrategy)) { + builder.keyStrategy = new KeyRandomStrategy(); + } + keyStrategy = builder.keyStrategy; + + if (Objects.isNull(builder.authInterceptor)) { + builder.authInterceptor = new DefaultOpenAiAuthInterceptor(); + } + authInterceptor = builder.authInterceptor; + //设置apiKeys和key的获取策略 + authInterceptor.setApiKey(this.apiKey); + authInterceptor.setKeyStrategy(this.keyStrategy); + + if (Objects.isNull(builder.okHttpClient)) { + builder.okHttpClient = this.okHttpClient(); + } else { + //自定义的okhttpClient 需要增加api keys + builder.okHttpClient = builder.okHttpClient + .newBuilder() + .addInterceptor(authInterceptor) + .build(); + } + okHttpClient = builder.okHttpClient; + + this.openAiApi = new Retrofit.Builder() + .baseUrl(apiHost) + .client(okHttpClient) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + .build().create(OpenAiApi.class); + } + + /** + * 创建默认的OkHttpClient + */ + private OkHttpClient okHttpClient() { + if (Objects.isNull(this.authInterceptor)) { + this.authInterceptor = new DefaultOpenAiAuthInterceptor(); + } + this.authInterceptor.setApiKey(this.apiKey); + this.authInterceptor.setKeyStrategy(this.keyStrategy); + OkHttpClient okHttpClient = new OkHttpClient + .Builder() + .addInterceptor(this.authInterceptor) + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(50, TimeUnit.SECONDS) + .readTimeout(50, TimeUnit.SECONDS) + .build(); + return okHttpClient; + } + + + /** + * 流式输出,最新版的GPT-3.5 chat completion 更加贴近官方网站的问答模型 + * + * @param chatCompletion 问答参数 + * @param eventSourceListener 监听器 + */ + public void streamChatCompletion(T chatCompletion, EventSourceListener eventSourceListener) { + if (Objects.isNull(eventSourceListener)) { + log.error("参数异常:EventSourceListener不能为空!"); + throw new BaseException(CommonError.PARAM_ERROR.msg()); + } + try { + EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); + ObjectMapper mapper = new ObjectMapper(); + String requestBody = mapper.writeValueAsString(chatCompletion); + Request request = new Request.Builder() + .url(this.apiHost + "v1/chat/completions") + .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) + .build(); + factory.newEventSource(request, eventSourceListener); + } catch (Exception e) { + log.error("请求参数解析异常:{}", e.getMessage()); + } + } + + + /** + * 根据描述生成图片 + * + * @param image 图片参数 + * @return ImageResponse + */ + public ImageResponse genImages(Image image) { + Single edits = this.openAiApi.genImages(image); + return edits.blockingGet(); + } + + /** + * 最新版的GPT-3.5 chat completion 更加贴近官方网站的问答模型 + * + * @param chatCompletion 问答参数 + * @return 答案 + */ + public ChatCompletionResponse chatCompletion(T chatCompletion) { + if (chatCompletion instanceof ChatCompletion) { + Single chatCompletionResponse = this.openAiApi.chatCompletion((ChatCompletion) chatCompletion); + return chatCompletionResponse.blockingGet(); + } + Single chatCompletionResponse = this.openAiApi.chatCompletionWithPicture((ChatCompletionWithPicture) chatCompletion); + return chatCompletionResponse.blockingGet(); + } + + /** + * 上传文件 + * + * @param purpose purpose + * @param file 文件对象 + * @return UploadFileResponse + */ + public UploadFileResponse uploadFile(String purpose, java.io.File file) { + // 创建 RequestBody,用于封装构建RequestBody + RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); + MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody); + + RequestBody purposeBody = RequestBody.create(MediaType.parse("multipart/form-data"), purpose); + Single uploadFileResponse = this.openAiApi.uploadFile(multipartBody, purposeBody); + return uploadFileResponse.blockingGet(); + } + + /** + * 获取openKey账户信息(近90天) + * + * @param key + * @return KeyInfo + * @Date 2023/7/6 + **/ + public KeyInfo getKeyInfo(String key) { + Date now = new Date(); + Date start = new Date(now.getTime() - (long) 90 * 24 * 60 * 60 * 1000); + Date end = new Date(now.getTime() + (long) 24 * 60 * 60 * 1000); + + BillingUsage billingUsage = billingUsage(start.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), end.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); + double totalUsage = billingUsage.getTotalUsage().doubleValue() / 100; + System.out.println(totalUsage); + Subscription subscription = subscription(); + KeyInfo keyInfo = new KeyInfo(); + String start_key = key.substring(0, 6); + String end_key = key.substring(key.length() - 6); + String mid_key = key.substring(6, key.length() - 6); + mid_key = mid_key.replaceAll(".", "*"); + + keyInfo.setKeyValue(start_key + mid_key + end_key); + keyInfo.setTotalAmount(subscription.getHardLimitUsd()); + keyInfo.setRemaining(subscription.getHardLimitUsd() - totalUsage); + keyInfo.setTotalUsage(totalUsage); + keyInfo.setLimitDate(new Date(subscription.getAccessUntil() * 1000).toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); + keyInfo.setPlanTitle(subscription.getPlan() != null ? subscription.getPlan().getTitle() : "null"); + keyInfo.setIsHasPaymentMethod(subscription.isHasPaymentMethod()); + keyInfo.setModel(getModelName()); + return keyInfo; + } + + /** + * 获取可用模型 + * + * @param + * @return String + * @Date 2023/7/6 + **/ + public String getModelName() { + Single models = this.openAiApi.models(); + List modelList = models.blockingGet().getData(); + for (Model model : modelList) { + if (Objects.equals(model.getId(), "gpt-4")) { + return "GPT-4.0"; + } + } + return "GPT-3.5"; + } + + /** + * 账户调用接口消耗金额信息查询 + * 最多查询100天 + * + * @param starDate 开始时间 + * @param endDate 结束时间 + * @return 消耗金额信息 + */ + public BillingUsage billingUsage(@NotNull LocalDate starDate, @NotNull LocalDate endDate) { + Single billingUsage = this.openAiApi.billingUsage(starDate, endDate); + return billingUsage.blockingGet(); + } + + /** + * 账户信息查询:里面包含总金额等信息 + * + * @return 账户信息 + */ + public Subscription subscription() { + Single subscription = this.openAiApi.subscription(); + return subscription.blockingGet(); + } + + /** + * 语音转文字 + * + * @param transcriptions 参数 + * @param file 语音文件 最大支持25MB mp3, mp4, mpeg, mpga, m4a, wav, webm + * @return 语音文本 + */ + public WhisperResponse speechToTextTranscriptions(java.io.File file, Transcriptions transcriptions) { + //文件 + RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); + MultipartBody.Part multipartBody = MultipartBody.Part.createFormData("file", file.getName(), fileBody); + //自定义参数 + Map requestBodyMap = new HashMap<>(10); + if (StrUtil.isNotBlank(transcriptions.getLanguage())) { + requestBodyMap.put(Transcriptions.Fields.language, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptions.getLanguage())); + } + if (StrUtil.isNotBlank(transcriptions.getModel())) { + requestBodyMap.put(Transcriptions.Fields.model, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptions.getModel())); + } + if (StrUtil.isNotBlank(transcriptions.getPrompt())) { + requestBodyMap.put(Transcriptions.Fields.prompt, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptions.getPrompt())); + } + if (StrUtil.isNotBlank(transcriptions.getResponseFormat())) { + requestBodyMap.put(Transcriptions.Fields.responseFormat, RequestBody.create(MediaType.parse("multipart/form-data"), transcriptions.getResponseFormat())); + } + if (Objects.nonNull(transcriptions.getTemperature())) { + requestBodyMap.put(Transcriptions.Fields.temperature, RequestBody.create(MediaType.parse("multipart/form-data"), String.valueOf(transcriptions.getTemperature()))); + } + Single whisperResponse = this.openAiApi.speechToTextTranscriptions(multipartBody, requestBodyMap); + return whisperResponse.blockingGet(); + } + + /** + * 简易版 语音转文字 + * + * @param file 语音文件 最大支持25MB mp3, mp4, mpeg, mpga, m4a, wav, webm + * @return 语音文本 + */ + public WhisperResponse speechToTextTranscriptions(java.io.File file) { + Transcriptions transcriptions = Transcriptions.builder().build(); + return this.speechToTextTranscriptions(file, transcriptions); + } + /** + * 文本转语音(异步) + * + * @param textToSpeech 参数 + * @param callback 返回值接收 + * @since 1.1.2 + */ + public void textToSpeech(TextToSpeech textToSpeech, retrofit2.Callback callback) { + Call responseBody = this.openAiApi.textToSpeech(textToSpeech); + responseBody.enqueue(callback); + } + + /** + * 文本转语音(同步) + * + * @param textToSpeech 参数 + * @since 1.1.3 + */ + public ResponseBody textToSpeech(TextToSpeech textToSpeech){ + Call responseBody = this.openAiApi.textToSpeech(textToSpeech); + try { + return responseBody.execute().body(); + } catch (IOException e) { + throw new BaseException("文本转语音(同步)失败: "+e.getMessage()); + } + } + + /** + * 文本转语音(克隆) + * + * @param textToSpeech + * @return + */ + public ResponseBody textToSpeechClone(TextToSpeech textToSpeech) { + String baseUrl = "http://localhost:8081"; + String spk = "三月七"; + String text = textToSpeech.getInput(); + String lang = "zh"; + + // 创建OkHttpClient实例 + OkHttpClient client = new OkHttpClient(); + + // 构建请求URL + HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder(); + urlBuilder.addQueryParameter("spk", spk); + urlBuilder.addQueryParameter("text", text); + urlBuilder.addQueryParameter("lang", lang); + String url = urlBuilder.build().toString(); + + // 创建请求对象 + Request request = new Request.Builder() + .url(url) + .build(); + // 发送请求并处理响应 + try { + return client.newCall(request).execute().body(); + } catch (IOException e) { + throw new BaseException("语音克隆失败!{}",e.getMessage()); + } + } + + + /** + * 构造 + * + * @return Builder + */ + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private @NotNull List apiKey; + /** + * api请求地址,结尾处有斜杠 + * + * @see OpenAIConst + */ + private String apiHost; + + /** + * 自定义OkhttpClient + */ + private OkHttpClient okHttpClient; + + + /** + * api key的获取策略 + */ + private KeyStrategyFunction keyStrategy; + + /** + * 自定义鉴权拦截器 + */ + private OpenAiAuthInterceptor authInterceptor; + + public Builder() { + } + + public Builder apiKey(@NotNull List val) { + apiKey = val; + return this; + } + + /** + * @param val api请求地址,结尾处有斜杠 + * @return Builder + * @see OpenAIConst + */ + public Builder apiHost(String val) { + apiHost = val; + return this; + } + + public Builder keyStrategy(KeyStrategyFunction val) { + keyStrategy = val; + return this; + } + + public Builder okHttpClient(OkHttpClient val) { + okHttpClient = val; + return this; + } + + public Builder authInterceptor(OpenAiAuthInterceptor val) { + authInterceptor = val; + return this; + } + + public OpenAiStreamClient build() { + return new OpenAiStreamClient(this); + } + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/TestOpenAIAPI.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/TestOpenAIAPI.java new file mode 100644 index 00000000..349f5c14 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/TestOpenAIAPI.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.chat.openai; + +import okhttp3.*; + +import java.io.IOException; + +public class TestOpenAIAPI { + + private final OkHttpClient client = new OkHttpClient(); + private static final String API_KEY = "sk-Waea254YSRYVg4FZVCz2CDz73B22xRpmKpJ41kbczVgpPxvg"; + private static final String URL = "https://api.gptgod.online/v1/chat/completions"; + + public void getChatGptResponse(String prompt) throws IOException { + RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), + "{\"model\": \"gpt-3.5-turbo\", \"messages\": [{\"role\": \"system\", \"content\": \"You are a helpful assistant.\"}, {\"role\": \"user\", \"content\": \"" + prompt + "\"}]}"); + + Request request = new Request.Builder() + .url(URL) + .post(body) + .addHeader("Authorization", "Bearer " + API_KEY) + .build(); + + try (Response response = client.newCall(request).execute()) { + System.out.println(response.body().string()); + } + } + + public static void main(String[] args) throws IOException { + TestOpenAIAPI api = new TestOpenAIAPI(); + api.getChatGptResponse("Hello, how are you?"); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java new file mode 100644 index 00000000..9bd65b82 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java @@ -0,0 +1,39 @@ +package org.ruoyi.common.chat.openai.exception; + +/** + * 描述: 错误 + * + * @author https:www.unfbx.com + * 2023-02-11 + */ +public enum CommonError implements IError { + API_KEYS_NOT_NUL(500, "API KEYS 不能为空"), + NO_ACTIVE_API_KEYS(500, "没有可用的API KEYS"), + SYS_ERROR(500, "系统繁忙"), + PARAM_ERROR(501, "参数异常"), + RETRY_ERROR(502, "请求异常,请重试~"), + //官方的错误码列表:https://platform.openai.com/docs/guides/error-codes/api-errors + OPENAI_AUTHENTICATION_ERROR(401, "身份验证无效/提供的 API 密钥不正确/您必须是组织的成员才能使用 API"), + OPENAI_LIMIT_ERROR(429 , "达到请求的速率限制/您超出了当前配额,请检查您的计划和帐单详细信息/发动机当前过载,请稍后重试"), + OPENAI_SERVER_ERROR(500, "服务器在处理您的请求时出错"), + ; + + + private int code; + private String msg; + + CommonError(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public String msg() { + return this.msg; + } + + @Override + public int code() { + return this.code; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/IError.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/IError.java new file mode 100644 index 00000000..84af81c7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/IError.java @@ -0,0 +1,12 @@ +package org.ruoyi.common.chat.openai.exception; +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-11 + */ +public interface IError { + String msg(); + + int code(); +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyRandomStrategy.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyRandomStrategy.java new file mode 100644 index 00000000..5dc10a0b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyRandomStrategy.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.chat.openai.function; + +import cn.hutool.core.util.RandomUtil; + +import java.util.List; + +/** + * 描述:随机策略 + * + * @author https:www.unfbx.com + * @since 2023-04-03 + */ +public class KeyRandomStrategy implements KeyStrategyFunction, String> { + + @Override + public String apply(List apiKeys) { + return RandomUtil.randomEle(apiKeys); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyStrategyFunction.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyStrategyFunction.java new file mode 100644 index 00000000..5ce5af05 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/function/KeyStrategyFunction.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.chat.openai.function; + +import java.util.function.Function; + +/** + * 描述:key 的获取策略 + * jdk默认实现 + * @see Function + * + * @author https:www.unfbx.com + * @since 2023-04-03 + */ +@FunctionalInterface +public interface KeyStrategyFunction { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DefaultOpenAiAuthInterceptor.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DefaultOpenAiAuthInterceptor.java new file mode 100644 index 00000000..97154704 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DefaultOpenAiAuthInterceptor.java @@ -0,0 +1,65 @@ +package org.ruoyi.common.chat.openai.interceptor; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 描述:请求增加header apikey + * + * @author https:www.unfbx.com + * @since 2023-03-23 + */ +@Slf4j +public class DefaultOpenAiAuthInterceptor extends OpenAiAuthInterceptor { + /** + * 请求头处理 + */ + public DefaultOpenAiAuthInterceptor() { + super.setWarringConfig(null); + } + + /** + * 构造方法 + * + * @param warringConfig 所有的key都失效后的告警参数配置 + */ + public DefaultOpenAiAuthInterceptor(Map warringConfig) { + super.setWarringConfig(warringConfig); + } + + /** + * 拦截器鉴权 + * + * @param chain Chain + * @return Response对象 + * @throws IOException io异常 + */ + @Override + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + return chain.proceed(auth(super.getKey(), original)); + } + + /** + * key失效或者禁用后的处理逻辑 + * 默认不处理 + * + * @param apiKey 返回新的api keys集合 + * @return 新的apiKey集合 + */ + @Override + protected List onErrorDealApiKeys(String apiKey) { + return super.getApiKey(); + } + + @Override + protected void noHaveActiveKeyWarring() { + log.error("--------> [告警] 没有可用的key!!!"); + return; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DynamicKeyOpenAiAuthInterceptor.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DynamicKeyOpenAiAuthInterceptor.java new file mode 100644 index 00000000..1f8c7588 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/DynamicKeyOpenAiAuthInterceptor.java @@ -0,0 +1,108 @@ +package org.ruoyi.common.chat.openai.interceptor; + +import cn.hutool.json.JSONUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import okhttp3.Response; +import org.ruoyi.common.chat.entity.common.OpenAiResponse; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 描述:动态处理key的鉴权拦截器 + * + * @author https:www.unfbx.com + * @since 2023-04-25 + */ +@Getter +@Slf4j +public class DynamicKeyOpenAiAuthInterceptor extends OpenAiAuthInterceptor { + /** + * 账号被封了 + */ + private static final String ACCOUNT_DEACTIVATED = "account_deactivated"; + /** + * key不正确 + */ + private static final String INVALID_API_KEY = "invalid_api_key"; + + /** + * 请求头处理 + * + */ + public DynamicKeyOpenAiAuthInterceptor() { + this.setWarringConfig(null); + } + + /** + * 构造方法 + * + * @param warringConfig 所有的key都失效后的告警参数配置 + */ + public DynamicKeyOpenAiAuthInterceptor(Map warringConfig) { + this.setWarringConfig(warringConfig); + } + + @Override + public Response intercept(Chain chain) throws IOException { + String key = getKey(); + Request original = chain.request(); + Request request = this.auth(key, original); + Response response = chain.proceed(request); + if (!response.isSuccessful()) { + String errorMsg = response.body().string(); + if (response.code() == CommonError.OPENAI_AUTHENTICATION_ERROR.code() + || response.code() == CommonError.OPENAI_LIMIT_ERROR.code() + || response.code() == CommonError.OPENAI_SERVER_ERROR.code()) { + OpenAiResponse openAiResponse = JSONUtil.toBean(errorMsg, OpenAiResponse.class); + String errorCode = openAiResponse.getError().getCode(); + log.error("--------> 请求openai异常,错误code:{}", errorCode); + log.error("--------> 请求异常:{}", errorMsg); + //账号被封或者key不正确就移除掉 + if (ACCOUNT_DEACTIVATED.equals(errorCode) || INVALID_API_KEY.equals(errorCode)) { + super.setApiKey(this.onErrorDealApiKeys(key)); + } + throw new BaseException(openAiResponse.getError().getMessage()); + } + //非官方定义的错误code + log.error("--------> 请求异常:{}", errorMsg); + OpenAiResponse openAiResponse = JSONUtil.toBean(errorMsg, OpenAiResponse.class); + if (Objects.nonNull(openAiResponse.getError())) { + log.error(openAiResponse.getError().getMessage()); + throw new BaseException(openAiResponse.getError().getMessage()); + } + throw new BaseException(CommonError.RETRY_ERROR.msg()); + } + return response; + } + + + @Override + protected List onErrorDealApiKeys(String errorKey) { + List apiKey = super.getApiKey().stream().filter(e -> !errorKey.equals(e)).collect(Collectors.toList()); + log.error("--------> 当前ApiKey:[{}] 失效了,移除!", errorKey); + return apiKey; + } + + /** + * 所有的key都失效后,自定义预警配置 + * 不配置直接return + */ + @Override + protected void noHaveActiveKeyWarring() { + log.error("--------> [告警] 没有可用的key!!!"); + return; + } + + @Override + public Request auth(String key, Request original) { + return super.auth(key, original); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAILogger.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAILogger.java new file mode 100644 index 00000000..c0801e2f --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAILogger.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.chat.openai.interceptor; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.logging.HttpLoggingInterceptor; + +/** + * 描述: 日志 + * + * @author https:www.unfbx.com + * 2023-02-28 + */ +@Slf4j +public class OpenAILogger implements HttpLoggingInterceptor.Logger { + @Override + public void log(String message) { + log.info("OkHttp-------->:{}", message); + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiAuthInterceptor.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiAuthInterceptor.java new file mode 100644 index 00000000..68365d62 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiAuthInterceptor.java @@ -0,0 +1,84 @@ +package org.ruoyi.common.chat.openai.interceptor; + + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import lombok.Getter; +import lombok.Setter; +import okhttp3.Interceptor; +import okhttp3.Request; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.chat.openai.function.KeyStrategyFunction; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.util.List; +import java.util.Map; + +public abstract class OpenAiAuthInterceptor implements Interceptor { + + + /** + * key 集合 + */ + @Getter + @Setter + private List apiKey; + /** + * 自定义的key的使用策略 + */ + @Getter + @Setter + private KeyStrategyFunction, String> keyStrategy; + + /** + * 预警触发参数配置,配置参数实现飞书、钉钉、企业微信、邮箱预警等功能 + */ + @Getter + @Setter + private Map warringConfig; + + /** + * 自定义apiKeys的处理逻辑 + * + * @param errorKey 错误的key + * @return 返回值是新的apiKeys + */ + protected abstract List onErrorDealApiKeys(String errorKey); + + /** + * 所有的key都失效后,自定义预警配置 + * 可以通过warringConfig配置参数实现飞书、钉钉、企业微信、邮箱预警等 + */ + protected abstract void noHaveActiveKeyWarring(); + + + /** + * 获取请求key + * + * @return key + */ + public final String getKey() { + if (CollectionUtil.isEmpty(apiKey)) { + this.noHaveActiveKeyWarring(); + throw new BaseException(CommonError.NO_ACTIVE_API_KEYS.msg()); + } + return keyStrategy.apply(apiKey); + } + + /** + * 默认的鉴权处理方法 + * + * @param key api key + * @param original 源请求体 + * @return 请求体 + */ + public Request auth(String key, Request original) { + Request request = original.newBuilder() + .header(Header.AUTHORIZATION.getValue(), "Bearer " + key) + .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) + .method(original.method(), original.body()) + .build(); + return request; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiResponseInterceptor.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiResponseInterceptor.java new file mode 100644 index 00000000..6d656a23 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/interceptor/OpenAiResponseInterceptor.java @@ -0,0 +1,47 @@ +package org.ruoyi.common.chat.openai.interceptor; + +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import org.ruoyi.common.chat.entity.common.OpenAiResponse; +import org.ruoyi.common.chat.openai.exception.CommonError; +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.IOException; +import java.util.Objects; + +/** + * 描述:openai 返回值处理Interceptor + * + * @author https:www.unfbx.com + * @since 2023-03-23 + */ +@Slf4j +public class OpenAiResponseInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + + Request original = chain.request(); + Response response = chain.proceed(original); + if (!response.isSuccessful()) { + if (response.code() == CommonError.OPENAI_AUTHENTICATION_ERROR.code() + || response.code() == CommonError.OPENAI_LIMIT_ERROR.code() + || response.code() == CommonError.OPENAI_SERVER_ERROR.code()) { + OpenAiResponse openAiResponse = JSONUtil.toBean(response.body().string(), OpenAiResponse.class); + log.error(openAiResponse.getError().getMessage()); + throw new BaseException(openAiResponse.getError().getMessage()); + } + String errorMsg = response.body().string(); + log.error("--------> 请求异常:{}", errorMsg); + OpenAiResponse openAiResponse = JSONUtil.toBean(errorMsg, OpenAiResponse.class); + if (Objects.nonNull(openAiResponse.getError())) { + log.error(openAiResponse.getError().getMessage()); + throw new BaseException(openAiResponse.getError().getMessage()); + } + throw new BaseException(CommonError.RETRY_ERROR.msg()); + } + return response; + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java new file mode 100644 index 00000000..3b0af00a --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java @@ -0,0 +1,55 @@ +package org.ruoyi.common.chat.request; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.ruoyi.common.chat.entity.chat.Content; +import org.ruoyi.common.chat.entity.chat.Message; + +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @sine 2023-04-08 + */ +@Data +public class ChatRequest { + + @NotEmpty(message = "传入的模型不能为空") + private String model; + + @NotEmpty(message = "对话消息不能为空") + List messages; + + List imageContent; + + private String prompt; + + private String userId; + + /** + * 知识库id + */ + private String kid; + + /** + * gpt的默认设置 + */ + private String systemMessage = ""; + + private double top_p = 1; + + private double temperature = 0.2; + + /** + * 上下文的条数 + */ + private Integer contentNumber = 10; + + /** + * 是否携带上下文 + */ + private Boolean usingContext = Boolean.TRUE; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/Dall3Request.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/Dall3Request.java new file mode 100644 index 00000000..c6625de9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/Dall3Request.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.chat.request; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @sine 2023-04-08 + */ +@Data +public class Dall3Request { + + @NotEmpty(message = "传入的模型不能为空") + private String model; + + @NotEmpty(message = "提示词不能为空") + private String prompt; + + /** 图片大小 */ + @NotEmpty(message = "图片大小不能为空") + private String size ; + + /** 图片质量 */ + @NotEmpty(message = "图片质量不能为空") + private String quality; + + /** 图片风格 */ + @NotEmpty(message = "图片风格不能为空") + private String style; + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/TikTokensUtil.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/TikTokensUtil.java new file mode 100644 index 00000000..2000651d --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/TikTokensUtil.java @@ -0,0 +1,237 @@ +package org.ruoyi.common.chat.utils; + +import cn.hutool.core.util.StrUtil; +import com.knuddels.jtokkit.Encodings; +import com.knuddels.jtokkit.api.Encoding; +import com.knuddels.jtokkit.api.EncodingRegistry; +import com.knuddels.jtokkit.api.EncodingType; +import com.knuddels.jtokkit.api.ModelType; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.entity.chat.FunctionCall; +import org.ruoyi.common.chat.entity.chat.Message; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * 描述:token计算工具类 + * + * @author https:www.unfbx.com + * @since 2023-04-04 + */ +@Slf4j +public class TikTokensUtil { + /** + * 模型名称对应Encoding + */ + private static final Map modelMap = new HashMap<>(); + /** + * registry实例 + */ + private static final EncodingRegistry registry = Encodings.newDefaultEncodingRegistry(); + + static { + for (ModelType modelType : ModelType.values()) { + modelMap.put(modelType.getName(), registry.getEncodingForModel(modelType)); + } + modelMap.put(ChatCompletion.Model.GPT_3_5_TURBO_0613.getName(), registry.getEncodingForModel(ModelType.GPT_3_5_TURBO)); + modelMap.put(ChatCompletion.Model.GPT_3_5_TURBO_16K.getName(), registry.getEncodingForModel(ModelType.GPT_3_5_TURBO)); + modelMap.put(ChatCompletion.Model.GPT_3_5_TURBO_16K_0613.getName(), registry.getEncodingForModel(ModelType.GPT_3_5_TURBO)); + modelMap.put(ChatCompletion.Model.GPT_3_5_TURBO_0125.getName(), registry.getEncodingForModel(ModelType.GPT_3_5_TURBO)); + modelMap.put(ChatCompletion.Model.GPT_4_32K.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ChatCompletion.Model.GPT_4_0613.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ChatCompletion.Model.GPT_4_32K_0613.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + modelMap.put(ChatCompletion.Model.GPT_4_0125_PREVIEW.getName(), registry.getEncodingForModel(ModelType.GPT_4)); + } + + /** + * 通过Encoding和text获取编码数组 + * + * @param enc Encoding类型 + * @param text 文本信息 + * @return 编码数组 + */ + public static List encode(@NotNull Encoding enc, String text) { + return StrUtil.isBlank(text) ? new ArrayList<>() : enc.encode(text); + } + + /** + * 通过Encoding计算text信息的tokens + * + * @param enc Encoding类型 + * @param text 文本信息 + * @return tokens数量 + */ + public static int tokens(@NotNull Encoding enc, String text) { + return encode(enc, text).size(); + } + + + /** + * 通过Encoding和encoded数组反推text信息 + * + * @param enc Encoding + * @param encoded 编码数组 + * @return 编码数组对应的文本信息 + */ + public static String decode(@NotNull Encoding enc, @NotNull List encoded) { + return enc.decode(encoded); + } + + /** + * 获取一个Encoding对象,通过Encoding类型 + * + * @param encodingType encodingType + * @return Encoding + */ + public static Encoding getEncoding(@NotNull EncodingType encodingType) { + return registry.getEncoding(encodingType); + } + + /** + * 获取encode的编码数组 + * + * @param text 文本信息 + * @return 编码数组 + */ + public static List encode(@NotNull EncodingType encodingType, String text) { + if (StrUtil.isBlank(text)) { + return new ArrayList<>(); + } + Encoding enc = getEncoding(encodingType); + return enc.encode(text); + } + + /** + * 计算指定字符串的tokens,通过EncodingType + * + * @param encodingType encodingType + * @param text 文本信息 + * @return tokens数量 + */ + public static int tokens(@NotNull EncodingType encodingType, String text) { + return encode(encodingType, text).size(); + } + + + /** + * 通过EncodingType和encoded编码数组,反推字符串文本 + * + * @param encodingType encodingType + * @param encoded 编码数组 + * @return 编码数组对应的字符串 + */ + public static String decode(@NotNull EncodingType encodingType, @NotNull List encoded) { + Encoding enc = getEncoding(encodingType); + return enc.decode(encoded); + } + + + /** + * 获取一个Encoding对象,通过模型名称 + * + * @param modelName 模型名称 + * @return Encoding + */ + public static Encoding getEncoding(@NotNull String modelName) { + return modelMap.getOrDefault(modelName, modelMap.get(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())); + } + + /** + * 获取encode的编码数组,通过模型名称 + * + * @param text 文本信息 + * @return 编码数组 + */ + public static List encode(@NotNull String modelName, String text) { + if (StrUtil.isBlank(text)) { + return new ArrayList<>(); + } + Encoding enc = getEncoding(modelName); + if (Objects.isNull(enc)) { + log.warn("[{}]模型不存在或者暂不支持计算tokens,直接返回tokens==0",modelName); + return new ArrayList<>(); + } + return enc.encode(text); + } + + /** + * 通过模型名称, 计算指定字符串的tokens + * + * @param modelName 模型名称 + * @param text 文本信息 + * @return tokens数量 + */ + public static int tokens(@NotNull String modelName, String text) { + return encode(modelName, text).size(); + } + + + /** + * 通过模型名称计算messages获取编码数组 + * 参考官方的处理逻辑: + * https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + * + * @param modelName 模型名称 + * @param messages 消息体 + * @return tokens数量 + */ + public static int tokens(@NotNull String modelName, @NotNull List messages) { + Encoding encoding = getEncoding(modelName); + int tokensPerMessage = 0; + int tokensPerName = 0; + if (modelName.equals(ChatCompletion.Model.GPT_3_5_TURBO_0613.getName()) + || modelName.equals(ChatCompletion.Model.GPT_3_5_TURBO_16K_0613.getName()) + || modelName.equals(ChatCompletion.Model.GPT_4_0613.getName()) + || modelName.equals(ChatCompletion.Model.GPT_4_32K_0613.getName()) + || modelName.equals(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName()) + || modelName.equals(ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName()) + ) { + tokensPerMessage = 3; + tokensPerName = 1; + }else if(modelName.contains(ChatCompletion.Model.GPT_3_5_TURBO.getName())){ + //"gpt-3.5-turbo" in model: + log.warn("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613."); + tokensPerMessage = 3; + tokensPerName = 1; + }else if(modelName.contains(ChatCompletion.Model.GPT_4.getName())){ + log.warn("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613."); + tokensPerMessage = 3; + tokensPerName = 1; + }else { + log.warn("不支持的model {} 按gpt4计算tokens",modelName); + tokensPerMessage = 3; + tokensPerName = 1; + } + int sum = 0; + for (Message msg : messages) { + sum += tokensPerMessage; + sum += tokens(encoding, msg.getContent().toString()); + sum += tokens(encoding, msg.getRole()); + sum += tokens(encoding, msg.getName()); + FunctionCall functionCall = msg.getFunctionCall(); + sum += Objects.isNull(functionCall) ? 0 : tokens(encoding, functionCall.toString()); + if (StrUtil.isNotBlank(msg.getName())) { + sum += tokensPerName; + } + } + sum += 3; + return sum; + } + + /** + * 通过模型名称和encoded编码数组,反推字符串文本 + * + * @param modelName 模型名 + * @param encoded 编码数组 + * @return 返回源文本 + */ + public static String decode(@NotNull String modelName, @NotNull List encoded) { + Encoding enc = getEncoding(modelName); + return enc.decode(encoded); + } + +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/WebSocketUtils.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/WebSocketUtils.java new file mode 100644 index 00000000..43cc6e89 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/utils/WebSocketUtils.java @@ -0,0 +1,98 @@ +package org.ruoyi.common.chat.utils; + +import cn.hutool.core.collection.CollUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.entity.dto.WebSocketMessageDto; +import org.ruoyi.common.chat.holder.WebSocketSessionHolder; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.springframework.web.socket.PongMessage; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static org.ruoyi.common.chat.constant.WebSocketConstants.WEB_SOCKET_TOPIC; + +/** + * 工具类 + * + * @author zendwang + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebSocketUtils { + + /** + * 发送消息 + * + * @param sessionKey session主键 一般为用户id + * @param message 消息文本 + */ + public static void sendMessage(Long sessionKey, String message) { + WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey); + sendMessage(session, message); + } + + /** + * 订阅消息 + * + * @param consumer 自定义处理 + */ + public static void subscribeMessage(Consumer consumer) { + RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer); + } + + /** + * 发布订阅的消息 + * + * @param webSocketMessage 消息对象 + */ + public static void publishMessage(WebSocketMessageDto webSocketMessage) { + List unsentSessionKeys = new ArrayList<>(); + // 当前服务内session,直接发送消息 + for (Long sessionKey : webSocketMessage.getSessionKeys()) { + if (WebSocketSessionHolder.existSession(sessionKey)) { + WebSocketUtils.sendMessage(sessionKey, webSocketMessage.getMessage()); + continue; + } + unsentSessionKeys.add(sessionKey); + } + // 不在当前服务内session,发布订阅消息 + if (CollUtil.isNotEmpty(unsentSessionKeys)) { + WebSocketMessageDto broadcastMessage = new WebSocketMessageDto(); + broadcastMessage.setMessage(webSocketMessage.getMessage()); + broadcastMessage.setSessionKeys(unsentSessionKeys); + RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> { + log.info(" WebSocket发送主题订阅消息topic:{} session keys:{} message:{}", + WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage()); + }); + } + } + + public static void sendPongMessage(WebSocketSession session) { + sendMessage(session, new PongMessage()); + } + + public static void sendMessage(WebSocketSession session, String message) { + sendMessage(session, new TextMessage(message)); + } + + private static void sendMessage(WebSocketSession session, WebSocketMessage message) { + if (session == null || !session.isOpen()) { + log.error("[send] session会话已经关闭"); + } else { + try { + // 获取当前会话中的用户 + session.sendMessage(message); + } catch (IOException e) { + log.error("[send] session({}) 发送消息({}) 异常", session, message, e); + } + } + } +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 25060231..6687bc04 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.chat.config.WebSocketConfig +org.ruoyi.common.chat.config.WebSocketConfig diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml index 020c13ea..57d714fe 100644 --- a/ruoyi-common/ruoyi-common-core/pom.xml +++ b/ruoyi-common/ruoyi-common-core/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -103,6 +103,12 @@ ${weixin-java-miniapp.version} + + com.github.binarywang + weixin-java-pay + ${weixin-java-pay.version} + + com.fasterxml.jackson.core diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ApplicationConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ApplicationConfig.java new file mode 100644 index 00000000..bf4a6aa4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ApplicationConfig.java @@ -0,0 +1,16 @@ +package org.ruoyi.common.core.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author Lion Li + */ +@AutoConfiguration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +public class ApplicationConfig { + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/AsyncConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/AsyncConfig.java new file mode 100644 index 00000000..2751d5b6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/AsyncConfig.java @@ -0,0 +1,54 @@ +package org.ruoyi.common.core.config; + +import cn.hutool.core.util.ArrayUtil; +import org.ruoyi.common.core.exception.ServiceException; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; + +/** + * 异步配置 + * + * @author Lion Li + */ +@EnableAsync(proxyTargetClass = true) +@AutoConfiguration +public class AsyncConfig implements AsyncConfigurer { + + @Autowired + @Qualifier("scheduledExecutorService") + private ScheduledExecutorService scheduledExecutorService; + + /** + * 自定义 @Async 注解使用系统线程池 + */ + @Override + public Executor getAsyncExecutor() { + return scheduledExecutorService; + } + + /** + * 异步执行异常处理 + */ + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + throwable.printStackTrace(); + StringBuilder sb = new StringBuilder(); + sb.append("Exception message - ").append(throwable.getMessage()) + .append(", Method name - ").append(method.getName()); + if (ArrayUtil.isNotEmpty(objects)) { + sb.append(", Parameter value - ").append(Arrays.toString(objects)); + } + throw new ServiceException(sb.toString()); + }; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/RuoYiConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/RuoYiConfig.java new file mode 100644 index 00000000..f671848a --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/RuoYiConfig.java @@ -0,0 +1,49 @@ +package org.ruoyi.common.core.config; + +import lombok.Data; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author Lion Li + */ + +@Data +@Component +@ConfigurationProperties(prefix = "ruoyi") +public class RuoYiConfig { + + /** + * 项目名称 + */ + private String name; + + /** + * 版本 + */ + private String version; + + /** + * 版权年份 + */ + private String copyrightYear; + + /** + * 实例演示开关 + */ + private boolean demoEnabled; + + /** + * 获取地址开关 + */ + @Getter + private static boolean addressEnabled; + + public void setAddressEnabled(boolean addressEnabled) { + RuoYiConfig.addressEnabled = addressEnabled; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ThreadPoolConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ThreadPoolConfig.java new file mode 100644 index 00000000..69ab0736 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ThreadPoolConfig.java @@ -0,0 +1,57 @@ +package org.ruoyi.common.core.config; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.ruoyi.common.core.config.properties.ThreadPoolProperties; +import org.ruoyi.common.core.utils.Threads; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @author Lion Li + **/ +@AutoConfiguration +@EnableConfigurationProperties(ThreadPoolProperties.class) +public class ThreadPoolConfig { + + /** + * 核心线程数 = cpu 核心数 + 1 + */ + private final int core = Runtime.getRuntime().availableProcessors() + 1; + + @Bean(name = "threadPoolTaskExecutor") + @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true") + public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(core); + executor.setMaxPoolSize(core * 2); + executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); + executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() { + return new ScheduledThreadPoolExecutor(core, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ValidatorConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ValidatorConfig.java new file mode 100644 index 00000000..9b8a5758 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/ValidatorConfig.java @@ -0,0 +1,39 @@ +package org.ruoyi.common.core.config; + +import jakarta.validation.Validator; +import org.hibernate.validator.HibernateValidator; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +import java.util.Properties; + +/** + * 校验框架配置类 + * + * @author Lion Li + */ +@AutoConfiguration +public class ValidatorConfig { + + /** + * 配置校验框架 快速返回模式 + */ + @Bean + public Validator validator(MessageSource messageSource) { + LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); + // 国际化 + factoryBean.setValidationMessageSource(messageSource); + // 设置使用 HibernateValidator 校验器 + factoryBean.setProviderClass(HibernateValidator.class); + Properties properties = new Properties(); + // 设置 快速异常返回 + properties.setProperty("hibernate.validator.fail_fast", "true"); + factoryBean.setValidationProperties(properties); + // 加载配置 + factoryBean.afterPropertiesSet(); + return factoryBean.getValidator(); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/properties/ThreadPoolProperties.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/properties/ThreadPoolProperties.java new file mode 100644 index 00000000..9c5798a9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/config/properties/ThreadPoolProperties.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.core.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 线程池 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "thread-pool") +public class ThreadPoolProperties { + + /** + * 是否开启线程池 + */ + private boolean enabled; + + /** + * 队列最大长度 + */ + private int queueCapacity; + + /** + * 线程池维护线程所允许的空闲时间 + */ + private int keepAliveSeconds; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheConstants.java new file mode 100644 index 00000000..07f36dc0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheConstants.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.core.constant; + +/** + * 缓存的key 常量 + * + * @author Lion Li + */ +public interface CacheConstants { + + /** + * 在线用户 redis key + */ + String ONLINE_TOKEN_KEY = "online_tokens:"; + + /** + * 参数管理 cache key + */ + String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + String SYS_DICT_KEY = "sys_dict:"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheNames.java new file mode 100644 index 00000000..eee04a51 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/CacheNames.java @@ -0,0 +1,63 @@ +package org.ruoyi.common.core.constant; + +/** + * 缓存组名称常量 + *

    + * key 格式为 cacheNames#ttl#maxIdleTime#maxSize + *

    + * ttl 过期时间 如果设置为0则不过期 默认为0 + * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 + * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0 + *

    + * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500 + * + * @author Lion Li + */ +public interface CacheNames { + + /** + * 演示案例 + */ + String DEMO_CACHE = "demo:cache#60s#10m#20"; + + /** + * 系统配置 + */ + String SYS_CONFIG = "sys_config"; + + /** + * 数据字典 + */ + String SYS_DICT = "sys_dict"; + + /** + * 租户 + */ + String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d"; + + /** + * 用户账户 + */ + String SYS_USER_NAME = "sys_user_name#30d"; + + /** + * 部门 + */ + String SYS_DEPT = "sys_dept#30d"; + + /** + * OSS内容 + */ + String SYS_OSS = "sys_oss#30d"; + + /** + * OSS配置 + */ + String SYS_OSS_CONFIG = "sys_oss_config"; + + /** + * 在线用户 + */ + String ONLINE_TOKEN = "online_tokens"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/Constants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/Constants.java new file mode 100644 index 00000000..41001002 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/Constants.java @@ -0,0 +1,86 @@ +package org.ruoyi.common.core.constant; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public interface Constants { + + /** + * UTF-8 字符集 + */ + String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + String GBK = "GBK"; + + /** + * www主域 + */ + String WWW = "www."; + + /** + * http请求 + */ + String HTTP = "http://"; + + /** + * https请求 + */ + String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + String FAIL = "1"; + + /** + * 登录成功 + */ + String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + String LOGOUT = "Logout"; + + /** + * 注册 + */ + String REGISTER = "Register"; + + /** + * 登录失败 + */ + String LOGIN_FAIL = "Error"; + + /** + * 验证码有效期(分钟) + */ + Integer CAPTCHA_EXPIRATION = 30; + + /** + * 令牌 + */ + String TOKEN = "token"; + + /** + * 顶级部门id + */ + Long TOP_PARENT_ID = 0L; + + /** + * 默认租户ID + **/ + String TENANT_ID = "00000"; + +} + diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/GlobalConstants.java new file mode 100644 index 00000000..3334cb7c --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/GlobalConstants.java @@ -0,0 +1,34 @@ +package org.ruoyi.common.core.constant; + +/** + * 全局的key常量 (业务无关的key) + * + * @author Lion Li + */ +public interface GlobalConstants { + + /** + * 全局 redis key (业务无关的key) + */ + String GLOBAL_REDIS_KEY = "global:"; + + /** + * 验证码 redis key + */ + String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:"; + + /** + * 防重提交 redis key + */ + String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:"; + + /** + * 限流 redis key + */ + String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:"; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/HttpStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/HttpStatus.java new file mode 100644 index 00000000..a38abcbc --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/HttpStatus.java @@ -0,0 +1,93 @@ +package org.ruoyi.common.core.constant; + +/** + * 返回状态码 + * + * @author Lion Li + */ +public interface HttpStatus { + /** + * 操作成功 + */ + int SUCCESS = 200; + + /** + * 对象创建成功 + */ + int CREATED = 201; + + /** + * 请求已经被接受 + */ + int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + int MOVED_PERM = 301; + + /** + * 重定向 + */ + int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + int BAD_REQUEST = 400; + + /** + * 未授权 + */ + int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + int ERROR = 500; + + /** + * 接口未实现 + */ + int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + int WARN = 601; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/TenantConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/TenantConstants.java new file mode 100644 index 00000000..0d2d77e7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/TenantConstants.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.core.constant; + +/** + * 租户常量信息 + * + * @author Lion Li + */ +public interface TenantConstants { + + /** + * 租户正常状态 + */ + String NORMAL = "0"; + + /** + * 租户封禁状态 + */ + String DISABLE = "1"; + + /** + * 超级管理员ID + */ + Long SUPER_ADMIN_ID = 1L; + + /** + * 超级管理员角色 roleKey + */ + String SUPER_ADMIN_ROLE_KEY = "superadmin"; + + /** + * 租户管理员角色 roleKey + */ + String TENANT_ADMIN_ROLE_KEY = "admin"; + + /** + * 租户管理员角色名称 + */ + String TENANT_ADMIN_ROLE_NAME = "管理员"; + + /** + * 默认租户ID + */ + String DEFAULT_TENANT_ID = "000000"; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/UserConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/UserConstants.java new file mode 100644 index 00000000..04dbfd1a --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/UserConstants.java @@ -0,0 +1,132 @@ +package org.ruoyi.common.core.constant; + +/** + * 用户常量信息 + * + * @author ruoyi + */ +public interface UserConstants { + + /** + * 平台内系统用户的唯一标志 + */ + String SYS_USER = "SYS_USER"; + + /** + * 正常状态 + */ + String NORMAL = "0"; + + /** + * 异常状态 + */ + String EXCEPTION = "1"; + + /** + * 用户正常状态 + */ + String USER_NORMAL = "0"; + + /** + * 用户封禁状态 + */ + String USER_DISABLE = "1"; + + /** + * 角色正常状态 + */ + String ROLE_NORMAL = "0"; + + /** + * 角色封禁状态 + */ + String ROLE_DISABLE = "1"; + + /** + * 部门正常状态 + */ + String DEPT_NORMAL = "0"; + + /** + * 部门停用状态 + */ + String DEPT_DISABLE = "1"; + + /** + * 字典正常状态 + */ + String DICT_NORMAL = "0"; + + /** + * 是否为系统默认(是) + */ + String YES = "Y"; + + /** + * 是否菜单外链(是) + */ + String YES_FRAME = "0"; + + /** + * 是否菜单外链(否) + */ + String NO_FRAME = "1"; + + /** + * 菜单正常状态 + */ + String MENU_NORMAL = "0"; + + /** + * 菜单停用状态 + */ + String MENU_DISABLE = "1"; + + /** + * 菜单类型(目录) + */ + String TYPE_DIR = "M"; + + /** + * 菜单类型(菜单) + */ + String TYPE_MENU = "C"; + + /** + * 菜单类型(按钮) + */ + String TYPE_BUTTON = "F"; + + /** + * Layout组件标识 + */ + String LAYOUT = "Layout"; + + /** + * ParentView组件标识 + */ + String PARENT_VIEW = "ParentView"; + + /** + * InnerLink组件标识 + */ + String INNER_LINK = "InnerLink"; + + /** + * 用户名长度限制 + */ + int USERNAME_MIN_LENGTH = 2; + int USERNAME_MAX_LENGTH = 100; + + /** + * 密码长度限制 + */ + int PASSWORD_MIN_LENGTH = 5; + int PASSWORD_MAX_LENGTH = 20; + + /** + * 超级管理员ID + */ + Long SUPER_ADMIN_ID = 1L; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/R.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/R.java new file mode 100644 index 00000000..b3856dbc --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/R.java @@ -0,0 +1,110 @@ +package org.ruoyi.common.core.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.constant.HttpStatus; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author Lion Li + */ +@Data +@NoArgsConstructor +public class R implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 成功 + */ + public static final int SUCCESS = 200; + + /** + * 失败 + */ + public static final int FAIL = 500; + + private int code; + + private String msg; + + private T data; + + public static R ok() { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(String msg) { + return restResult(null, SUCCESS, msg); + } + + public static R ok(String msg, T data) { + return restResult(data, SUCCESS, msg); + } + + public static R fail() { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(String msg, T data) { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) { + return restResult(null, code, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static R warn(String msg) { + return restResult(null, HttpStatus.WARN, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static R warn(String msg, T data) { + return restResult(data, HttpStatus.WARN, msg); + } + + private static R restResult(T data, int code, String msg) { + R r = new R<>(); + r.setCode(code); + r.setData(data); + r.setMsg(msg); + return r; + } + + public static Boolean isError(R ret) { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/RoleDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/RoleDTO.java new file mode 100644 index 00000000..dfd31f4e --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/RoleDTO.java @@ -0,0 +1,38 @@ +package org.ruoyi.common.core.domain.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 角色 + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +public class RoleDTO implements Serializable { + + /** + * 角色ID + */ + private Long roleId; + + /** + * 角色名称 + */ + private String roleName; + + /** + * 角色权限 + */ + private String roleKey; + + /** + * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) + */ + private String dataScope; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/UserOnlineDTO.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/UserOnlineDTO.java new file mode 100644 index 00000000..a74cc2d1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/dto/UserOnlineDTO.java @@ -0,0 +1,62 @@ +package org.ruoyi.common.core.domain.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 当前在线会话 + * + * @author ruoyi + */ + +@Data +@NoArgsConstructor +public class UserOnlineDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 会话编号 + */ + private String tokenId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 用户名称 + */ + private String userName; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地址 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录时间 + */ + private Long loginTime; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/EmailLoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/EmailLoginBody.java new file mode 100644 index 00000000..3188c756 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/EmailLoginBody.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.core.domain.model; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 短信登录对象 + * + * @author Lion Li + */ + +@Data +public class EmailLoginBody { + + /** + * 租户ID + */ + @NotBlank(message = "{tenant.number.not.blank}") + private String tenantId; + + /** + * 邮箱 + */ + @NotBlank(message = "{user.email.not.blank}") + @Email(message = "{user.email.not.valid}") + private String email; + + /** + * 邮箱code + */ + @NotBlank(message = "{email.code.not.blank}") + private String emailCode; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 00000000..9a91d081 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,44 @@ +package org.ruoyi.common.core.domain.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 用户登录对象 + * + * @author Lion Li + */ + +@Data +public class LoginBody { + + /** + * 租户ID + */ + private String tenantId; + + /** + * 用户名 + */ + @NotBlank(message = "{user.username.not.blank}") + // @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}") + private String username; + + /** + * 用户密码 + */ + @NotBlank(message = "{user.password.not.blank}") + // @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginUser.java new file mode 100644 index 00000000..58462035 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/LoginUser.java @@ -0,0 +1,133 @@ +package org.ruoyi.common.core.domain.model; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.domain.dto.RoleDTO; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +public class LoginUser implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门名 + */ + private String deptName; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 用户类型 + */ + private String userType; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 菜单权限 + */ + private Set menuPermission; + + /** + * 角色权限 + */ + private Set rolePermission; + + /** + * 用户名 + */ + private String username; + + /** + * 用户名 + */ + private String nickName; + + /** + * 微信头像 + */ + private String avatar; + + /** + * 角色对象 + */ + private List roles; + + /** + * 数据权限 当前角色ID + */ + private Long roleId; + + /** + * 获取登录id + */ + public String getLoginId() { + if (userType == null) { + throw new IllegalArgumentException("用户类型不能为空"); + } + if (userId == null) { + throw new IllegalArgumentException("用户ID不能为空"); + } + return userType + ":" + userId; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/RegisterBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/RegisterBody.java new file mode 100644 index 00000000..fb2cac7e --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/RegisterBody.java @@ -0,0 +1,22 @@ +package org.ruoyi.common.core.domain.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户注册对象 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RegisterBody extends LoginBody { + + private String userType; + + /** + * 注册域名 + */ + private String domainName; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/SmsLoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/SmsLoginBody.java new file mode 100644 index 00000000..b3efc58d --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/SmsLoginBody.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.core.domain.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 短信登录对象 + * + * @author Lion Li + */ + +@Data +public class SmsLoginBody { + + /** + * 租户ID + */ + @NotBlank(message = "{tenant.number.not.blank}") + private String tenantId; + + /** + * 手机号 + */ + @NotBlank(message = "{user.phonenumber.not.blank}") + private String phonenumber; + + /** + * 短信code + */ + @NotBlank(message = "{sms.code.not.blank}") + private String smsCode; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginBody.java new file mode 100644 index 00000000..7ae8803f --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginBody.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.core.domain.model; + +import lombok.Data; + +import java.io.Serial; + +/** + * 游客登录用户身份权限 + * + * @author Lion Li + */ +@Data +public class VisitorLoginBody { + + @Serial + private static final long serialVersionUID = 1L; + + private String code; + + /** + * 登录类型(1.小程序访客 2.pc访客) + */ + private String type; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginUser.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginUser.java new file mode 100644 index 00000000..61dc4b3d --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/domain/model/VisitorLoginUser.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.core.domain.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.io.Serial; + +/** + * 小程序登录用户身份权限 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class VisitorLoginUser extends LoginUser { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * openid + */ + private String openid; + + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/DeviceType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/DeviceType.java new file mode 100644 index 00000000..5767480f --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/DeviceType.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设备类型 + * 针对一套 用户体系 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum DeviceType { + + /** + * pc端 + */ + PC("pc"), + + /** + * app端 + */ + APP("app"), + + /** + * 小程序端 + */ + XCX("xcx"); + + private final String device; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginType.java new file mode 100644 index 00000000..3e45b4c0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginType.java @@ -0,0 +1,44 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 登录类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum LoginType { + + /** + * 密码登录 + */ + PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"), + + /** + * 短信登录 + */ + SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"), + + /** + * 邮箱登录 + */ + EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"), + + /** + * 小程序登录 + */ + XCX("", ""); + + /** + * 登录重试超出限制提示 + */ + final String retryLimitExceed; + + /** + * 登录重试限制计数提示 + */ + final String retryLimitCount; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginUserType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginUserType.java new file mode 100644 index 00000000..8022e19b --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/LoginUserType.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 游客登录类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum LoginUserType { + + PC("1", "PC端用户"), + + XCX("2", "小程序用户"); + + private final String code; + private final String content; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/TenantStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/TenantStatus.java new file mode 100644 index 00000000..de6ef382 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/TenantStatus.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户状态 + * + * @author LionLi + */ +@Getter +@AllArgsConstructor +public enum TenantStatus { + /** + * 正常 + */ + OK("0", "正常"), + /** + * 停用 + */ + DISABLE("1", "停用"), + /** + * 删除 + */ + DELETED("2", "删除"); + + private final String code; + private final String info; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserStatus.java new file mode 100644 index 00000000..b6eb8c1a --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserStatus.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户状态 + * + * @author ruoyi + */ +@Getter +@AllArgsConstructor +public enum UserStatus { + /** + * 正常 + */ + OK("0", "正常"), + /** + * 停用 + */ + DISABLE("1", "停用"), + /** + * 删除 + */ + DELETED("2", "删除"); + + private final String code; + private final String info; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserType.java new file mode 100644 index 00000000..74dd8301 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/enums/UserType.java @@ -0,0 +1,37 @@ +package org.ruoyi.common.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * 设备类型 + * 针对多套 用户体系 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum UserType { + + /** + * pc端 + */ + SYS_USER("sys_user"), + + /** + * app端 + */ + APP_USER("app_user"); + + private final String userType; + + public static UserType getUserType(String str) { + for (UserType value : values()) { + if (StringUtils.contains(str, value.getUserType())) { + return value; + } + } + throw new RuntimeException("'UserType' not found By " + str); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/event/ConfigChangeEvent.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/event/ConfigChangeEvent.java new file mode 100644 index 00000000..67244af4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/event/ConfigChangeEvent.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.core.event; + +import org.springframework.context.ApplicationEvent; + +/** + * 描述:定义一个事件类,用于通知配置变化 + * + * @author ageerle@163.com + * date 2024/5/19 + */ +public class ConfigChangeEvent extends ApplicationEvent { + public ConfigChangeEvent(Object source) { + super(source); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/AuthException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/AuthException.java new file mode 100644 index 00000000..433c49c0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/AuthException.java @@ -0,0 +1,10 @@ +package org.ruoyi.common.core.exception; + +public class AuthException extends RuntimeException{ + + private static final long serialVersionUID = 1L; + + public AuthException(String message) { + super(message); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/DemoModeException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/DemoModeException.java new file mode 100644 index 00000000..caec6494 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/DemoModeException.java @@ -0,0 +1,17 @@ +package org.ruoyi.common.core.exception; + +import java.io.Serial; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public DemoModeException() { + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/GlobalException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/GlobalException.java new file mode 100644 index 00000000..dbdeb73e --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/GlobalException.java @@ -0,0 +1,53 @@ +package org.ruoyi.common.core.exception; + +import java.io.Serial; + +/** + * 全局异常 + * + * @author ruoyi + */ +public class GlobalException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() { + } + + public GlobalException(String message) { + this.message = message; + } + + public String getDetailMessage() { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public GlobalException setMessage(String message) { + this.message = message; + return this; + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/ServiceException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/ServiceException.java new file mode 100644 index 00000000..ec2a6148 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/ServiceException.java @@ -0,0 +1,67 @@ +package org.ruoyi.common.core.exception; + +import java.io.Serial; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() { + } + + public ServiceException(String message) { + this.message = message; + } + + public ServiceException(String message, Integer code) { + this.message = message; + this.code = code; + } + + public String getDetailMessage() { + return detailMessage; + } + + @Override + public String getMessage() { + return message; + } + + public Integer getCode() { + return code; + } + + public ServiceException setMessage(String message) { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) { + this.detailMessage = detailMessage; + return this; + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/UtilException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/UtilException.java new file mode 100644 index 00000000..3a773210 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/UtilException.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.core.exception; + +import java.io.Serial; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) { + super(e.getMessage(), e); + } + + public UtilException(String message) { + super(message); + } + + public UtilException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/base/BaseException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/base/BaseException.java new file mode 100644 index 00000000..803d9c2b --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/base/BaseException.java @@ -0,0 +1,79 @@ +package org.ruoyi.common.core.exception.base; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.MessageUtils; +import org.ruoyi.common.core.utils.StringUtils; + +import java.io.Serial; + +/** + * 基础异常 + * + * @author ruoyi + */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class BaseException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() { + String message = null; + if (!StringUtils.isEmpty(code)) { + message = MessageUtils.message(code, args); + } + if (message == null) { + message = defaultMessage; + } + return message; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileException.java new file mode 100644 index 00000000..7cccfefa --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileException.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.core.exception.file; + +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 文件信息异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) { + super("file", code, args, null); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 00000000..29758c37 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.exception.file; + +import java.io.Serial; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) { + super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 00000000..ff0c5a4e --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.exception.file; + +import java.io.Serial; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) { + super("upload.exceed.maxSize", new Object[]{defaultMaxSize}); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaException.java new file mode 100644 index 00000000..d3848c7c --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaException.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.exception.user; + +import java.io.Serial; + +/** + * 验证码错误异常类 + * + * @author ruoyi + */ +public class CaptchaException extends UserException { + + @Serial + private static final long serialVersionUID = 1L; + + public CaptchaException() { + super("user.jcaptcha.error"); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaExpireException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaExpireException.java new file mode 100644 index 00000000..dd849c46 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/CaptchaExpireException.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.exception.user; + +import java.io.Serial; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException { + + @Serial + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() { + super("user.jcaptcha.expire"); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserException.java new file mode 100644 index 00000000..ef12650e --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserException.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.core.exception.user; + +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class UserException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public UserException(String code, Object... args) { + super("user", code, args, null); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 00000000..0f1ac66f --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.exception.user; + +import java.io.Serial; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author ruoyi + */ +public class UserPasswordNotMatchException extends UserException { + + @Serial + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() { + super("user.password.not.match"); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 00000000..85fb47f5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.core.exception.user; + +import java.io.Serial; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException { + + @Serial + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) { + super("user.password.retry.limit.exceed", retryLimitCount, lockTime); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/manager/ShutdownManager.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/manager/ShutdownManager.java new file mode 100644 index 00000000..b125e9ec --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/manager/ShutdownManager.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.core.manager; + +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.Threads; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ScheduledExecutorService; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author Lion Li + */ +@Slf4j +@Component +public class ShutdownManager { + + @Autowired + @Qualifier("scheduledExecutorService") + private ScheduledExecutorService scheduledExecutorService; + + @PreDestroy + public void destroy() { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() { + try { + log.info("====关闭后台任务任务线程池===="); + Threads.shutdownAndAwaitTermination(scheduledExecutorService); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/ConfigService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/ConfigService.java new file mode 100644 index 00000000..675b662c --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/ConfigService.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.core.service; + + +/** + * 通用 参数配置服务 + */ +public interface ConfigService { + + /** + * 根据配置类型和配置key获取值 + * + * @param category + * @param configKey + * @return + */ + String getConfigValue(String category,String configKey); + + + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DeptService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DeptService.java new file mode 100644 index 00000000..bbe97569 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DeptService.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.service; + +/** + * 通用 部门服务 + * + * @author Lion Li + */ +public interface DeptService { + + /** + * 通过部门ID查询部门名称 + * + * @param deptIds 部门ID串逗号分隔 + * @return 部门名称串逗号分隔 + */ + String selectDeptNameByIds(String deptIds); + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DictService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DictService.java new file mode 100644 index 00000000..a94524ab --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/DictService.java @@ -0,0 +1,57 @@ +package org.ruoyi.common.core.service; + +/** + * 通用 字典服务 + * + * @author Lion Li + */ +public interface DictService { + + /** + * 分隔符 + */ + String SEPARATOR = ","; + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + default String getDictLabel(String dictType, String dictValue) { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + default String getDictValue(String dictType, String dictLabel) { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + String getDictLabel(String dictType, String dictValue, String separator); + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + String getDictValue(String dictType, String dictLabel, String separator); + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java new file mode 100644 index 00000000..28c9d746 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/OssService.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.service; + +/** + * 通用 OSS服务 + * + * @author Lion Li + */ +public interface OssService { + + /** + * 通过ossId查询对应的url + * + * @param ossIds ossId串逗号分隔 + * @return url串逗号分隔 + */ + String selectUrlByIds(String ossIds); + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java new file mode 100644 index 00000000..5e354b7f --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.core.service; + +/** + * 通用 用户服务 + * + * @author Lion Li + */ +public interface UserService { + + /** + * 通过用户ID查询用户账户 + * + * @param userId 用户ID + * @return 用户账户 + */ + String selectUserNameById(Long userId); + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/DateUtils.java new file mode 100644 index 00000000..75a954c9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/DateUtils.java @@ -0,0 +1,164 @@ +package org.ruoyi.common.core.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Date; + +/** + * 时间工具类 + * + * @author ruoyi + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DateUtils extends org.apache.commons.lang3.time.DateUtils { + + public static final String YYYY = "yyyy"; + + public static final String YYYY_MM = "yyyy-MM"; + + public static final String YYYY_MM_DD = "yyyy-MM-dd"; + + public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static final String[] PARSE_PATTERNS = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() { + return dateTimeNow(YYYY_MM_DD); + } + + public static String getTime() { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static String dateTimeNow() { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static String dateTimeNow(final String format) { + return parseDateToStr(format, new Date()); + } + + public static String dateTime(final Date date) { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static String parseDateToStr(final String format, final Date date) { + return new SimpleDateFormat(format).format(date); + } + + public static Date dateTime(final String format, final String ts) { + try { + return new SimpleDateFormat(format).parse(ts); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static String datePath() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static String dateTime() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) { + if (str == null) { + return null; + } + try { + return parseDate(str.toString(), PARSE_PATTERNS); + } catch (ParseException e) { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算两个时间差 + */ + public static String getDatePoor(Date endDate, Date nowDate) { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/JsonUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/JsonUtils.java new file mode 100644 index 00000000..73033b5a --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/JsonUtils.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.core.utils; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +/** + * @author Binary Wang + */ +public class JsonUtils { + private static final ObjectMapper JSON = new ObjectMapper(); + + static { + JSON.setSerializationInclusion(Include.NON_NULL); + JSON.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE); + } + + public static String toJson(Object obj) { + try { + return JSON.writeValueAsString(obj); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MapstructUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MapstructUtils.java new file mode 100644 index 00000000..713219a1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MapstructUtils.java @@ -0,0 +1,92 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import io.github.linpeilie.Converter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Mapstruct 工具类 + *

    参考文档:mapstruct-plus

    + * + * @author Michelle.Chung + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MapstructUtils { + + private final static Converter CONVERTER = SpringUtils.getBean(Converter.class); + + /** + * 将 T 类型对象,转换为 desc 类型的对象并返回 + * + * @param source 数据来源实体 + * @param desc 描述对象 转换后的对象 + * @return desc + */ + public static V convert(T source, Class desc) { + if (ObjectUtil.isNull(source)) { + return null; + } + if (ObjectUtil.isNull(desc)) { + return null; + } + return CONVERTER.convert(source, desc); + } + + /** + * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象 + * + * @param source 数据来源实体 + * @param desc 转换后的对象 + * @return desc + */ + public static V convert(T source, V desc) { + if (ObjectUtil.isNull(source)) { + return null; + } + if (ObjectUtil.isNull(desc)) { + return null; + } + return CONVERTER.convert(source, desc); + } + + /** + * 将 T 类型的集合,转换为 desc 类型的集合并返回 + * + * @param sourceList 数据来源实体列表 + * @param desc 描述对象 转换后的对象 + * @return desc + */ + public static List convert(List sourceList, Class desc) { + if (ObjectUtil.isNull(sourceList)) { + return null; + } + if (CollUtil.isEmpty(sourceList)) { + return CollUtil.newArrayList(); + } + return CONVERTER.convert(sourceList, desc); + } + + /** + * 将 Map 转换为 beanClass 类型的集合并返回 + * + * @param map 数据来源 + * @param beanClass bean类 + * @return bean对象 + */ + public static T convert(Map map, Class beanClass) { + if (MapUtil.isEmpty(map)) { + return null; + } + if (ObjectUtil.isNull(beanClass)) { + return null; + } + return CONVERTER.convert(map, beanClass); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MessageUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MessageUtils.java new file mode 100644 index 00000000..75233c46 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/MessageUtils.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.core.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * 获取i18n资源文件 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MessageUtils { + + private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class); + + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) { + return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/OkHttpUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/OkHttpUtil.java new file mode 100644 index 00000000..9bc4cfce --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/OkHttpUtil.java @@ -0,0 +1,55 @@ +package org.ruoyi.common.core.utils; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class OkHttpUtil { + + @Setter + private String apiHost; + @Setter + private String apiKey; + + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(3000, TimeUnit.SECONDS) + .writeTimeout(3000, TimeUnit.SECONDS) + .readTimeout(3000, TimeUnit.SECONDS) + .build(); + + public String executeRequest(Request request) { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + log.error("Request failed: {}", response); + throw new IOException("Unexpected code " + response); + } + return response.body() != null ? response.body().string() : null; + } catch (IOException e) { + log.error("Request execution failed: {}", e.getMessage(), e); + return null; + } + } + + public Request createPostRequest(String url, String json) { + MediaType JSON = MediaType.get("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(json, JSON); + return new Request.Builder() + .url(apiHost + url) + .post(body) + .header("Authorization", apiKey) + .build(); + } + + public Request createGetRequest(String url) { + return new Request.Builder() + .url(apiHost + url) + .header("Authorization", apiKey) + .build(); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ServletUtils.java new file mode 100644 index 00000000..0bf0fabf --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ServletUtils.java @@ -0,0 +1,193 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.convert.Convert; +import cn.hutool.extra.servlet.JakartaServletUtil; +import cn.hutool.http.HttpStatus; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ServletUtils extends JakartaServletUtil { + + /** + * 获取String参数 + */ + public static String getParameter(String name) { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR)); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) { + try { + response.setStatus(HttpStatus.HTTP_OK); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + response.getWriter().print(string); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) { + + String accept = request.getHeader("accept"); + if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml"); + } + + public static String getClientIP() { + return getClientIP(getRequest()); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) { + return URLEncoder.encode(str, StandardCharsets.UTF_8); + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) { + return URLDecoder.decode(str, StandardCharsets.UTF_8); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java new file mode 100644 index 00000000..99937c79 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java @@ -0,0 +1,62 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * spring工具类 + * + * @author Lion Li + */ +@Component +public final class SpringUtils extends SpringUtil { + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + */ + public static boolean containsBean(String name) { + return getBeanFactory().containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 + * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().isSingleton(name); + } + + /** + * @return Class 注册对象的类型 + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getAliases(name); + } + + /** + * 获取aop代理对象 + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) { + return (T) AopContext.currentProxy(); + } + + + /** + * 获取spring上下文 + */ + public static ApplicationContext context() { + return getApplicationContext(); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StreamUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StreamUtils.java new file mode 100644 index 00000000..8ed09913 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StreamUtils.java @@ -0,0 +1,254 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * stream 流工具类 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class StreamUtils { + + /** + * 将collection过滤 + * + * @param collection 需要转化的集合 + * @param function 过滤方法 + * @return 过滤后的list + */ + public static List filter(Collection collection, Predicate function) { + if (CollUtil.isEmpty(collection)) { + return CollUtil.newArrayList(); + } + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + return collection.stream().filter(function).collect(Collectors.toList()); + } + + /** + * 将collection拼接 + * + * @param collection 需要转化的集合 + * @param function 拼接方法 + * @return 拼接后的list + */ + public static String join(Collection collection, Function function) { + return join(collection, function, StringUtils.SEPARATOR); + } + + /** + * 将collection拼接 + * + * @param collection 需要转化的集合 + * @param function 拼接方法 + * @param delimiter 拼接符 + * @return 拼接后的list + */ + public static String join(Collection collection, Function function, CharSequence delimiter) { + if (CollUtil.isEmpty(collection)) { + return StringUtils.EMPTY; + } + return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter)); + } + + /** + * 将collection排序 + * + * @param collection 需要转化的集合 + * @param comparing 排序方法 + * @return 排序后的list + */ + public static List sorted(Collection collection, Comparator comparing) { + if (CollUtil.isEmpty(collection)) { + return CollUtil.newArrayList(); + } + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + return collection.stream().sorted(comparing).collect(Collectors.toList()); + } + + /** + * 将collection转化为类型不变的map
    + * {@code Collection ----> Map} + * + * @param collection 需要转化的集合 + * @param key V类型转化为K类型的lambda方法 + * @param collection中的泛型 + * @param map中的key类型 + * @return 转化后的map + */ + public static Map toIdentityMap(Collection collection, Function key) { + if (CollUtil.isEmpty(collection)) { + return MapUtil.newHashMap(); + } + return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); + } + + /** + * 将Collection转化为map(value类型与collection的泛型不同)
    + * {@code Collection -----> Map } + * + * @param collection 需要转化的集合 + * @param key E类型转化为K类型的lambda方法 + * @param value E类型转化为V类型的lambda方法 + * @param collection中的泛型 + * @param map中的key类型 + * @param map中的value类型 + * @return 转化后的map + */ + public static Map toMap(Collection collection, Function key, Function value) { + if (CollUtil.isEmpty(collection)) { + return MapUtil.newHashMap(); + } + return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l)); + } + + /** + * 将collection按照规则(比如有相同的班级id)分类成map
    + * {@code Collection -------> Map> } + * + * @param collection 需要分类的集合 + * @param key 分类的规则 + * @param collection中的泛型 + * @param map中的key类型 + * @return 分类后的map + */ + public static Map> groupByKey(Collection collection, Function key) { + if (CollUtil.isEmpty(collection)) { + return MapUtil.newHashMap(); + } + return collection + .stream() + .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); + } + + /** + * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
    + * {@code Collection ---> Map>> } + * + * @param collection 需要分类的集合 + * @param key1 第一个分类的规则 + * @param key2 第二个分类的规则 + * @param 集合元素类型 + * @param 第一个map中的key类型 + * @param 第二个map中的key类型 + * @return 分类后的map + */ + public static Map>> groupBy2Key(Collection collection, Function key1, Function key2) { + if (CollUtil.isEmpty(collection)) { + return MapUtil.newHashMap(); + } + return collection + .stream() + .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList()))); + } + + /** + * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
    + * {@code Collection ---> Map> } + * + * @param collection 需要分类的集合 + * @param key1 第一个分类的规则 + * @param key2 第二个分类的规则 + * @param 第一个map中的key类型 + * @param 第二个map中的key类型 + * @param collection中的泛型 + * @return 分类后的map + */ + public static Map> group2Map(Collection collection, Function key1, Function key2) { + if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { + return MapUtil.newHashMap(); + } + return collection + .stream() + .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l))); + } + + /** + * 将collection转化为List集合,但是两者的泛型不同
    + * {@code Collection ------> List } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为list泛型的lambda表达式 + * @param collection中的泛型 + * @param List中的泛型 + * @return 转化后的list + */ + public static List toList(Collection collection, Function function) { + if (CollUtil.isEmpty(collection)) { + return CollUtil.newArrayList(); + } + return collection + .stream() + .map(function) + .filter(Objects::nonNull) + // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 + .collect(Collectors.toList()); + } + + /** + * 将collection转化为Set集合,但是两者的泛型不同
    + * {@code Collection ------> Set } + * + * @param collection 需要转化的集合 + * @param function collection中的泛型转化为set泛型的lambda表达式 + * @param collection中的泛型 + * @param Set中的泛型 + * @return 转化后的Set + */ + public static Set toSet(Collection collection, Function function) { + if (CollUtil.isEmpty(collection) || function == null) { + return CollUtil.newHashSet(); + } + return collection + .stream() + .map(function) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + + /** + * 合并两个相同key类型的map + * + * @param map1 第一个需要合并的 map + * @param map2 第二个需要合并的 map + * @param merge 合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况 + * @param map中的key类型 + * @param 第一个 map的value类型 + * @param 第二个 map的value类型 + * @param 最终map的value类型 + * @return 合并后的map + */ + public static Map merge(Map map1, Map map2, BiFunction merge) { + if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) { + return MapUtil.newHashMap(); + } else if (MapUtil.isEmpty(map1)) { + map1 = MapUtil.newHashMap(); + } else if (MapUtil.isEmpty(map2)) { + map2 = MapUtil.newHashMap(); + } + Set key = new HashSet<>(); + key.addAll(map1.keySet()); + key.addAll(map2.keySet()); + Map map = new HashMap<>(); + for (K t : key) { + X x = map1.get(t); + Y y = map2.get(t); + V z = merge.apply(x, y); + if (z != null) { + map.put(t, z); + } + } + return map; + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StringUtils.java new file mode 100644 index 00000000..847dd107 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/StringUtils.java @@ -0,0 +1,321 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.util.AntPathMatcher; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 字符串工具类 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class StringUtils extends org.apache.commons.lang3.StringUtils { + + public static final String SEPARATOR = ","; + + /** + * 获取参数不为空值 + * + * @param str defaultValue 要判断的value + * @return value 返回值 + */ + public static String blankToDefault(String str, String defaultValue) { + return StrUtil.blankToDefault(str, defaultValue); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) { + return StrUtil.isEmpty(str); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * 去空格 + */ + public static String trim(String str) { + return StrUtil.trim(str); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) { + return substring(str, start, str.length()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) { + return StrUtil.sub(str, start, end); + } + + /** + * 格式化文本, {} 表示占位符
    + * 此方法只是简单将占位符 {} 按照顺序替换为参数
    + * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
    + * 例:
    + * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
    + * 转义{}: format("this is \\{} for {}", "a", "b") -> this is {} for a
    + * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
    + * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + return StrUtil.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) { + return Validator.isUrl(link); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static Set str2Set(String str, String sep) { + return new HashSet<>(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static List str2List(String str, String sep, boolean filterBlank, boolean trim) { + List list = new ArrayList<>(); + if (isEmpty(str)) { + return list; + } + + // 过滤空白字符串 + if (filterBlank && isBlank(str)) { + return list; + } + String[] split = str.split(sep); + for (String string : split) { + if (filterBlank && isBlank(string)) { + continue; + } + if (trim) { + string = trim(string); + } + list.add(string); + } + + return list; + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { + return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences); + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + return StrUtil.toUnderlineCase(str); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) { + return StrUtil.equalsAnyIgnoreCase(str, strs); + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + return StrUtil.upperFirst(StrUtil.toCamelCase(name)); + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) { + return StrUtil.toCamelCase(s); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) { + if (isEmpty(str) || CollUtil.isEmpty(strs)) { + return false; + } + for (String pattern : strs) { + if (isMatch(pattern, str)) { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + */ + public static boolean isMatch(String pattern, String url) { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static String padl(final Number num, final int size) { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static String padl(final String s, final int size, final char c) { + final StringBuilder sb = new StringBuilder(size); + if (s != null) { + final int len = s.length(); + if (s.length() <= size) { + sb.append(String.valueOf(c).repeat(size - len)); + sb.append(s); + } else { + return s.substring(len - size, len); + } + } else { + sb.append(String.valueOf(c).repeat(Math.max(0, size))); + } + return sb.toString(); + } + + /** + * 切分字符串(分隔符默认逗号) + * + * @param str 被切分的字符串 + * @return 分割后的数据列表 + */ + public static List splitList(String str) { + return splitTo(str, Convert::toStr); + } + + /** + * 切分字符串 + * + * @param str 被切分的字符串 + * @param separator 分隔符 + * @return 分割后的数据列表 + */ + public static List splitList(String str, String separator) { + return splitTo(str, separator, Convert::toStr); + } + + /** + * 切分字符串自定义转换(分隔符默认逗号) + * + * @param str 被切分的字符串 + * @param mapper 自定义转换 + * @return 分割后的数据列表 + */ + public static List splitTo(String str, Function mapper) { + return splitTo(str, SEPARATOR, mapper); + } + + /** + * 切分字符串自定义转换 + * + * @param str 被切分的字符串 + * @param separator 分隔符 + * @param mapper 自定义转换 + * @return 分割后的数据列表 + */ + public static List splitTo(String str, String separator, Function mapper) { + if (isBlank(str)) { + return new ArrayList<>(0); + } + return StrUtil.split(str, separator) + .stream() + .filter(Objects::nonNull) + .map(mapper) + .collect(Collectors.toList()); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/Threads.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/Threads.java new file mode 100644 index 00000000..1c9bd6cf --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/Threads.java @@ -0,0 +1,75 @@ +package org.ruoyi.common.core.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.*; + +/** + * 线程相关工具类. + * + * @author ruoyi + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Threads { + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) { + if (pool != null && !pool.isShutdown()) { + pool.shutdown(); + try { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { + log.info("Pool did not terminate"); + } + } + } catch (InterruptedException ie) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) { + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) { + future.get(); + } + } catch (CancellationException ce) { + t = ce; + } catch (ExecutionException ee) { + t = ee.getCause(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + if (t != null) { + log.error(t.getMessage(), t); + } + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/TreeBuildUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/TreeBuildUtils.java new file mode 100644 index 00000000..4537fa07 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/TreeBuildUtils.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.core.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.lang.tree.parser.NodeParser; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; + +import java.util.List; + +/** + * 扩展 hutool TreeUtil 封装系统树构建 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TreeBuildUtils extends TreeUtil { + + /** + * 根据前端定制差异化字段 + */ + public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label"); + + public static List> build(List list, NodeParser nodeParser) { + if (CollUtil.isEmpty(list)) { + return null; + } + K k = ReflectUtils.invokeGetter(list.get(0), "parentId"); + return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ValidatorUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ValidatorUtils.java new file mode 100644 index 00000000..4af4fbe5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ValidatorUtils.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.core.utils; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Set; + +/** + * Validator 校验框架工具 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ValidatorUtils { + + private static final Validator VALID = SpringUtils.getBean(Validator.class); + + public static void validate(T object, Class... groups) { + Set> validate = VALID.validate(object, groups); + if (!validate.isEmpty()) { + throw new ConstraintViolationException("参数校验异常", validate); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/FileUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/FileUtils.java new file mode 100644 index 00000000..9c6f2650 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/FileUtils.java @@ -0,0 +1,43 @@ +package org.ruoyi.common.core.utils.file; + +import cn.hutool.core.io.FileUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 文件处理工具类 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FileUtils extends FileUtil { + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) { + String percentEncodedFileName = percentEncode(realFileName); + String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8); + return encode.replaceAll("\\+", "%20"); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/MimeTypeUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/MimeTypeUtils.java new file mode 100644 index 00000000..9e39699b --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/file/MimeTypeUtils.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.core.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils { + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; + + public static final String[] FLASH_EXTENSION = {"swf", "flv"}; + + public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb"}; + + public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf"}; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/AddressUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/AddressUtils.java new file mode 100644 index 00000000..f13630c1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/AddressUtils.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.core.utils.ip; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.http.HtmlUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * 获取地址类 + * + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AddressUtils { + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) { + if (StringUtils.isBlank(ip)) { + return UNKNOWN; + } + // 内网不查询 + ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip); + if (NetUtil.isInnerIP(ip)) { + return "内网IP"; + } + return RegionUtils.getCityInfo(ip); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/RegionUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/RegionUtils.java new file mode 100644 index 00000000..aed1efc1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ip/RegionUtils.java @@ -0,0 +1,67 @@ +package org.ruoyi.common.core.utils.ip; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ClassPathResource; +import cn.hutool.core.util.ObjectUtil; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.file.FileUtils; +import org.lionsoul.ip2region.xdb.Searcher; + +import java.io.File; + +/** + * 根据ip地址定位工具类,离线方式 + * 参考地址:集成 ip2region 实现离线IP地址定位库 + * + * @author lishuyan + */ +@Slf4j +public class RegionUtils { + + private static final Searcher SEARCHER; + + static { + String fileName = "/ip2region.xdb"; + File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName); + if (!FileUtils.exist(existFile)) { + ClassPathResource fileStream = new ClassPathResource(fileName); + if (ObjectUtil.isEmpty(fileStream.getStream())) { + throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!"); + } + FileUtils.writeFromStream(fileStream.getStream(), existFile); + } + + String dbPath = existFile.getPath(); + + // 1、从 dbPath 加载整个 xdb 到内存。 + byte[] cBuff; + try { + cBuff = Searcher.loadContentFromFile(dbPath); + } catch (Exception e) { + throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage()); + } + // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。 + try { + SEARCHER = Searcher.newWithBuffer(cBuff); + } catch (Exception e) { + throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage()); + } + } + + /** + * 根据IP地址离线获取城市 + */ + public static String getCityInfo(String ip) { + try { + ip = ip.trim(); + // 3、执行查询 + String region = SEARCHER.search(ip); + return region.replace("0|", "").replace("|0", ""); + } catch (Exception e) { + log.error("IP地址离线获取城市异常 {}", ip); + return "未知"; + } + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/reflect/ReflectUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/reflect/ReflectUtils.java new file mode 100644 index 00000000..b2297b02 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/reflect/ReflectUtils.java @@ -0,0 +1,56 @@ +package org.ruoyi.common.core.utils.reflect; + +import cn.hutool.core.util.ReflectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.StringUtils; + +import java.lang.reflect.Method; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author Lion Li + */ +@SuppressWarnings("rawtypes") +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ReflectUtils extends ReflectUtil { + + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invoke(object, getterMethodName); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) { + if (i < names.length - 1) { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invoke(object, getterMethodName); + } else { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + Method method = getMethodByName(object.getClass(), setterMethodName); + invoke(object, method, value); + } + } + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/sql/SqlUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/sql/SqlUtil.java new file mode 100644 index 00000000..50a8cb89 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/sql/SqlUtil.java @@ -0,0 +1,57 @@ +package org.ruoyi.common.core.utils.sql; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.exception.UtilException; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class SqlUtil { + + /** + * 定义常用的 sql关键字 + */ + public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) { + if (StringUtils.isEmpty(value)) { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { + throw new UtilException("参数存在SQL注入风险"); + } + } + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/AddGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/AddGroup.java new file mode 100644 index 00000000..12998e5d --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/AddGroup.java @@ -0,0 +1,9 @@ +package org.ruoyi.common.core.validate; + +/** + * 校验分组 add + * + * @author Lion Li + */ +public interface AddGroup { +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/EditGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/EditGroup.java new file mode 100644 index 00000000..0c01b3de --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/EditGroup.java @@ -0,0 +1,9 @@ +package org.ruoyi.common.core.validate; + +/** + * 校验分组 edit + * + * @author Lion Li + */ +public interface EditGroup { +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/QueryGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/QueryGroup.java new file mode 100644 index 00000000..11702361 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/validate/QueryGroup.java @@ -0,0 +1,9 @@ +package org.ruoyi.common.core.validate; + +/** + * 校验分组 query + * + * @author Lion Li + */ +public interface QueryGroup { +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/Xss.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/Xss.java new file mode 100644 index 00000000..483b31d6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/Xss.java @@ -0,0 +1,27 @@ +package org.ruoyi.common.core.xss; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author Lion Li + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) +@Constraint(validatedBy = {XssValidator.class}) +public @interface Xss { + + String message() default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/XssValidator.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/XssValidator.java new file mode 100644 index 00000000..a42f47b7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/xss/XssValidator.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.core.xss; + +import cn.hutool.core.util.ReUtil; +import cn.hutool.http.HtmlUtil; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +/** + * 自定义xss校验注解实现 + * + * @author Lion Li + */ +public class XssValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { + return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value); + } + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 9e63be5b..4a7ec568 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,6 +1,6 @@ -com.xmzs.common.core.config.ApplicationConfig -com.xmzs.common.core.config.AsyncConfig -com.xmzs.common.core.config.RuoYiConfig -com.xmzs.common.core.config.ThreadPoolConfig -com.xmzs.common.core.config.ValidatorConfig -com.xmzs.common.core.utils.SpringUtils +org.ruoyi.common.core.config.ApplicationConfig +org.ruoyi.common.core.config.AsyncConfig +org.ruoyi.common.core.config.RuoYiConfig +org.ruoyi.common.core.config.ThreadPoolConfig +org.ruoyi.common.core.config.ValidatorConfig +org.ruoyi.common.core.utils.SpringUtils diff --git a/ruoyi-common/ruoyi-common-doc/pom.xml b/ruoyi-common/ruoyi-common-doc/pom.xml index f2904cb8..0d227fbd 100644 --- a/ruoyi-common/ruoyi-common-doc/pom.xml +++ b/ruoyi-common/ruoyi-common-doc/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-core diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/SwaggerConfig.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/SwaggerConfig.java new file mode 100644 index 00000000..fe95cbc2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/SwaggerConfig.java @@ -0,0 +1,126 @@ +package org.ruoyi.common.doc.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.doc.config.properties.SwaggerProperties; +import org.ruoyi.common.doc.handler.OpenApiHandler; +import org.springdoc.core.configuration.SpringDocConfiguration; +import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.customizers.ServerBaseUrlCustomizer; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.providers.JavadocProvider; +import org.springdoc.core.service.OpenAPIService; +import org.springdoc.core.service.SecurityService; +import org.springdoc.core.utils.PropertyResolverUtils; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Swagger 文档配置 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@AutoConfiguration(before = SpringDocConfiguration.class) +@EnableConfigurationProperties(SwaggerProperties.class) +@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true) +public class SwaggerConfig { + + private final ServerProperties serverProperties; + + @Bean + @ConditionalOnMissingBean(OpenAPI.class) + public OpenAPI openApi(SwaggerProperties swaggerProperties) { + OpenAPI openApi = new OpenAPI(); + // 文档基本信息 + SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo(); + Info info = convertInfo(infoProperties); + openApi.info(info); + // 扩展文档信息 + openApi.externalDocs(swaggerProperties.getExternalDocs()); + openApi.tags(swaggerProperties.getTags()); + openApi.paths(swaggerProperties.getPaths()); + openApi.components(swaggerProperties.getComponents()); + Set keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet(); + List list = new ArrayList<>(); + SecurityRequirement securityRequirement = new SecurityRequirement(); + keySet.forEach(securityRequirement::addList); + list.add(securityRequirement); + openApi.security(list); + + return openApi; + } + + private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) { + Info info = new Info(); + info.setTitle(infoProperties.getTitle()); + info.setDescription(infoProperties.getDescription()); + info.setContact(infoProperties.getContact()); + info.setLicense(infoProperties.getLicense()); + info.setVersion(infoProperties.getVersion()); + return info; + } + + /** + * 自定义 openapi 处理器 + */ + @Bean + public OpenAPIService openApiBuilder(Optional openAPI, + SecurityService securityParser, + SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, + Optional> openApiBuilderCustomisers, + Optional> serverBaseUrlCustomisers, Optional javadocProvider) { + return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider); + } + + /** + * 对已经生成好的 OpenApi 进行自定义操作 + */ + @Bean + public OpenApiCustomizer openApiCustomizer() { + String contextPath = serverProperties.getServlet().getContextPath(); + String finalContextPath; + if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) { + finalContextPath = ""; + } else { + finalContextPath = contextPath; + } + // 对所有路径增加前置上下文路径 + return openApi -> { + Paths oldPaths = openApi.getPaths(); + if (oldPaths instanceof PlusPaths) { + return; + } + PlusPaths newPaths = new PlusPaths(); + oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v)); + openApi.setPaths(newPaths); + }; + } + + /** + * 单独使用一个类便于判断 解决springdoc路径拼接重复问题 + * + * @author Lion Li + */ + static class PlusPaths extends Paths { + + public PlusPaths() { + super(); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/properties/SwaggerProperties.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/properties/SwaggerProperties.java new file mode 100644 index 00000000..057c70f7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/config/properties/SwaggerProperties.java @@ -0,0 +1,94 @@ +package org.ruoyi.common.doc.config.properties; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.tags.Tag; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import java.util.List; + +/** + * swagger 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "swagger") +public class SwaggerProperties { + + /** + * 文档基本信息 + */ + @NestedConfigurationProperty + private InfoProperties info = new InfoProperties(); + + /** + * 扩展文档地址 + */ + @NestedConfigurationProperty + private ExternalDocumentation externalDocs; + + /** + * 标签 + */ + private List tags = null; + + /** + * 路径 + */ + @NestedConfigurationProperty + private Paths paths = null; + + /** + * 组件 + */ + @NestedConfigurationProperty + private Components components = null; + + /** + *

    + * 文档的基础属性信息 + *

    + * + * @see io.swagger.v3.oas.models.info.Info + * + * 为了 springboot 自动生产配置提示信息,所以这里复制一个类出来 + */ + @Data + public static class InfoProperties { + + /** + * 标题 + */ + private String title = null; + + /** + * 描述 + */ + private String description = null; + + /** + * 联系人信息 + */ + @NestedConfigurationProperty + private Contact contact = null; + + /** + * 许可证 + */ + @NestedConfigurationProperty + private License license = null; + + /** + * 版本 + */ + private String version = null; + + } + +} diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/handler/OpenApiHandler.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/handler/OpenApiHandler.java new file mode 100644 index 00000000..34aff596 --- /dev/null +++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/ruoyi/common/doc/handler/OpenApiHandler.java @@ -0,0 +1,252 @@ +package org.ruoyi.common.doc.handler; + +import cn.hutool.core.io.IoUtil; +import io.swagger.v3.core.jackson.TypeNameResolver; +import io.swagger.v3.core.util.AnnotationsUtils; +import io.swagger.v3.oas.annotations.tags.Tags; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.ServerBaseUrlCustomizer; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.providers.JavadocProvider; +import org.springdoc.core.service.OpenAPIService; +import org.springdoc.core.service.SecurityService; +import org.springdoc.core.utils.PropertyResolverUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.web.method.HandlerMethod; + +import java.io.StringReader; +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 自定义 openapi 处理器 + * 对源码功能进行修改 增强使用 + */ +@Slf4j +@SuppressWarnings("all") +public class OpenApiHandler extends OpenAPIService { + + /** + * The Basic error controller. + */ + private static Class basicErrorController; + + /** + * The Security parser. + */ + private final SecurityService securityParser; + + /** + * The Mappings map. + */ + private final Map mappingsMap = new HashMap<>(); + + /** + * The Springdoc tags. + */ + private final Map springdocTags = new HashMap<>(); + + /** + * The Open api builder customisers. + */ + private final Optional> openApiBuilderCustomisers; + + /** + * The server base URL customisers. + */ + private final Optional> serverBaseUrlCustomizers; + + /** + * The Spring doc config properties. + */ + private final SpringDocConfigProperties springDocConfigProperties; + + /** + * The Cached open api map. + */ + private final Map cachedOpenAPI = new HashMap<>(); + + /** + * The Property resolver utils. + */ + private final PropertyResolverUtils propertyResolverUtils; + + /** + * The javadoc provider. + */ + private final Optional javadocProvider; + + /** + * The Context. + */ + private ApplicationContext context; + + /** + * The Open api. + */ + private OpenAPI openAPI; + + /** + * The Is servers present. + */ + private boolean isServersPresent; + + /** + * The Server base url. + */ + private String serverBaseUrl; + + /** + * Instantiates a new Open api builder. + * + * @param openAPI the open api + * @param securityParser the security parser + * @param springDocConfigProperties the spring doc config properties + * @param propertyResolverUtils the property resolver utils + * @param openApiBuilderCustomizers the open api builder customisers + * @param serverBaseUrlCustomizers the server base url customizers + * @param javadocProvider the javadoc provider + */ + public OpenApiHandler(Optional openAPI, SecurityService securityParser, + SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, + Optional> openApiBuilderCustomizers, + Optional> serverBaseUrlCustomizers, + Optional javadocProvider) { + super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); + if (openAPI.isPresent()) { + this.openAPI = openAPI.get(); + if (this.openAPI.getComponents() == null) + this.openAPI.setComponents(new Components()); + if (this.openAPI.getPaths() == null) + this.openAPI.setPaths(new Paths()); + if (!CollectionUtils.isEmpty(this.openAPI.getServers())) + this.isServersPresent = true; + } + this.propertyResolverUtils = propertyResolverUtils; + this.securityParser = securityParser; + this.springDocConfigProperties = springDocConfigProperties; + this.openApiBuilderCustomisers = openApiBuilderCustomizers; + this.serverBaseUrlCustomizers = serverBaseUrlCustomizers; + this.javadocProvider = javadocProvider; + if (springDocConfigProperties.isUseFqn()) + TypeNameResolver.std.setUseFqn(true); + } + + @Override + public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) { + + Set tags = new HashSet<>(); + Set tagsStr = new HashSet<>(); + + buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale); + buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale); + + if (!CollectionUtils.isEmpty(tagsStr)) + tagsStr = tagsStr.stream() + .map(str -> propertyResolverUtils.resolve(str, locale)) + .collect(Collectors.toSet()); + + if (springdocTags.containsKey(handlerMethod)) { + Tag tag = springdocTags.get(handlerMethod); + tagsStr.add(tag.getName()); + if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) { + openAPI.addTagsItem(tag); + } + } + + if (!CollectionUtils.isEmpty(tagsStr)) { + if (CollectionUtils.isEmpty(operation.getTags())) + operation.setTags(new ArrayList<>(tagsStr)); + else { + Set operationTagsSet = new HashSet<>(operation.getTags()); + operationTagsSet.addAll(tagsStr); + operation.getTags().clear(); + operation.getTags().addAll(operationTagsSet); + } + } + + if (isAutoTagClasses(operation)) { + + + if (javadocProvider.isPresent()) { + String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType()); + if (StringUtils.isNotBlank(description)) { + Tag tag = new Tag(); + + // 自定义部分 修改使用java注释当tag名 + List list = IoUtil.readLines(new StringReader(description), new ArrayList<>()); + // tag.setName(tagAutoName); + tag.setName(list.get(0)); + operation.addTagsItem(list.get(0)); + + tag.setDescription(description); + if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) { + openAPI.addTagsItem(tag); + } + } + } else { + String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName()); + operation.addTagsItem(tagAutoName); + } + } + + if (!CollectionUtils.isEmpty(tags)) { + // Existing tags + List openApiTags = openAPI.getTags(); + if (!CollectionUtils.isEmpty(openApiTags)) + tags.addAll(openApiTags); + openAPI.setTags(new ArrayList<>(tags)); + } + + // Handle SecurityRequirement at operation level + io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser + .getSecurityRequirements(handlerMethod); + if (securityRequirements != null) { + if (securityRequirements.length == 0) + operation.setSecurity(Collections.emptyList()); + else + securityParser.buildSecurityRequirement(securityRequirements, operation); + } + + return operation; + } + + private void buildTagsFromMethod(Method method, Set tags, Set tagsStr, Locale locale) { + // method tags + Set tagsSet = AnnotatedElementUtils + .findAllMergedAnnotations(method, Tags.class); + Set methodTags = tagsSet.stream() + .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet()); + methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class)); + if (!CollectionUtils.isEmpty(methodTags)) { + tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet())); + List allTags = new ArrayList<>(methodTags); + addTags(allTags, tags, locale); + } + } + + private void addTags(List sourceTags, Set tags, Locale locale) { + Optional> optionalTagSet = AnnotationsUtils + .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true); + optionalTagSet.ifPresent(tagsSet -> { + tagsSet.forEach(tag -> { + tag.name(propertyResolverUtils.resolve(tag.getName(), locale)); + tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale)); + if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName()))) + tags.add(tag); + }); + }); + } + +} diff --git a/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 11e07a44..97e76b80 100644 --- a/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.doc.config.SwaggerConfig +org.ruoyi.common.doc.config.SwaggerConfig diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml index d3265d6a..da85259e 100644 --- a/ruoyi-common/ruoyi-common-encrypt/pom.xml +++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -19,7 +19,7 @@ - com.xmzs + org.ruoyi ruoyi-common-core diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/annotation/EncryptField.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/annotation/EncryptField.java new file mode 100644 index 00000000..188c9152 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/annotation/EncryptField.java @@ -0,0 +1,44 @@ +package org.ruoyi.common.encrypt.annotation; + +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +import java.lang.annotation.*; + +/** + * 字段加密注解 + * + * @author 老马 + */ +@Documented +@Inherited +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EncryptField { + + /** + * 加密算法 + */ + AlgorithmType algorithm() default AlgorithmType.DEFAULT; + + /** + * 秘钥。AES、SM4需要 + */ + String password() default ""; + + /** + * 公钥。RSA、SM2需要 + */ + String publicKey() default ""; + + /** + * 公钥。RSA、SM2需要 + */ + String privateKey() default ""; + + /** + * 编码方式。对加密算法为BASE64的不起作用 + */ + EncodeType encode() default EncodeType.DEFAULT; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java new file mode 100644 index 00000000..5667aa9c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/config/EncryptorAutoConfiguration.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.encrypt.config; + +import org.ruoyi.common.encrypt.core.EncryptorManager; +import org.ruoyi.common.encrypt.interceptor.MybatisDecryptInterceptor; +import org.ruoyi.common.encrypt.interceptor.MybatisEncryptInterceptor; +import org.ruoyi.common.encrypt.properties.EncryptorProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * 加解密配置 + * + * @author 老马 + * @version 4.6.0 + */ +@AutoConfiguration +@EnableConfigurationProperties(EncryptorProperties.class) +@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true") +public class EncryptorAutoConfiguration { + + @Autowired + private EncryptorProperties properties; + + @Bean + public EncryptorManager encryptorManager() { + return new EncryptorManager(); + } + + @Bean + public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) { + return new MybatisEncryptInterceptor(encryptorManager, properties); + } + + @Bean + public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) { + return new MybatisDecryptInterceptor(encryptorManager, properties); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptContext.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptContext.java new file mode 100644 index 00000000..73e81792 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptContext.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.encrypt.core; + +import lombok.Data; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +/** + * 加密上下文 用于encryptor传递必要的参数。 + * + * @author 老马 + * @version 4.6.0 + */ +@Data +public class EncryptContext { + + /** + * 默认算法 + */ + private AlgorithmType algorithm; + + /** + * 安全秘钥 + */ + private String password; + + /** + * 公钥 + */ + private String publicKey; + + /** + * 私钥 + */ + private String privateKey; + + /** + * 编码方式,base64/hex + */ + private EncodeType encode; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptorManager.java new file mode 100644 index 00000000..b7a09b5c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/EncryptorManager.java @@ -0,0 +1,94 @@ +package org.ruoyi.common.encrypt.core; + +import cn.hutool.core.util.ReflectUtil; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.encrypt.annotation.EncryptField; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 加密管理类 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +public class EncryptorManager { + + /** + * 缓存加密器 + */ + Map encryptorMap = new ConcurrentHashMap<>(); + + /** + * 类加密字段缓存 + */ + Map, Set> fieldCache = new ConcurrentHashMap<>(); + + /** + * 获取类加密字段缓存 + */ + public Set getFieldCache(Class sourceClazz) { + return fieldCache.computeIfAbsent(sourceClazz, clazz -> { + Field[] declaredFields = clazz.getDeclaredFields(); + Set fieldSet = Arrays.stream(declaredFields).filter(field -> + field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class) + .collect(Collectors.toSet()); + for (Field field : fieldSet) { + field.setAccessible(true); + } + return fieldSet; + }); + } + + /** + * 注册加密执行者到缓存 + * + * @param encryptContext 加密执行者需要的相关配置参数 + */ + public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) { + if (encryptorMap.containsKey(encryptContext)) { + return encryptorMap.get(encryptContext); + } + IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext); + encryptorMap.put(encryptContext, encryptor); + return encryptor; + } + + /** + * 移除缓存中的加密执行者 + * + * @param encryptContext 加密执行者需要的相关配置参数 + */ + public void removeEncryptor(EncryptContext encryptContext) { + this.encryptorMap.remove(encryptContext); + } + + /** + * 根据配置进行加密。会进行本地缓存对应的算法和对应的秘钥信息。 + * + * @param value 待加密的值 + * @param encryptContext 加密相关的配置信息 + */ + public String encrypt(String value, EncryptContext encryptContext) { + IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); + return encryptor.encrypt(value, encryptContext.getEncode()); + } + + /** + * 根据配置进行解密 + * + * @param value 待解密的值 + * @param encryptContext 加密相关的配置信息 + */ + public String decrypt(String value, EncryptContext encryptContext) { + IEncryptor encryptor = this.registAndGetEncryptor(encryptContext); + return encryptor.decrypt(value); + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/IEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/IEncryptor.java new file mode 100644 index 00000000..de3e451d --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/IEncryptor.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.encrypt.core; + +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +/** + * 加解者 + * + * @author 老马 + * @version 4.6.0 + */ +public interface IEncryptor { + + /** + * 获得当前算法 + */ + AlgorithmType algorithm(); + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + * @return 加密后的字符串 + */ + String encrypt(String value, EncodeType encodeType); + + /** + * 解密 + * + * @param value 待加密字符串 + * @return 解密后的字符串 + */ + String decrypt(String value); +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java new file mode 100644 index 00000000..55655f96 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AbstractEncryptor.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.core.IEncryptor; + +/** + * 所有加密执行者的基类 + * + * @author 老马 + * @version 4.6.0 + */ +public abstract class AbstractEncryptor implements IEncryptor { + + public AbstractEncryptor(EncryptContext context) { + // 用户配置校验与配置注入 + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java new file mode 100644 index 00000000..a0c4d386 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/AesEncryptor.java @@ -0,0 +1,69 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +import java.nio.charset.StandardCharsets; + +/** + * AES算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class AesEncryptor extends AbstractEncryptor { + + private final AES aes; + + public AesEncryptor(EncryptContext context) { + super(context); + String password = context.getPassword(); + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES没有获得秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度应该为16位、24位、32位,实际为" + password.length() + "位"); + } + aes = SecureUtil.aes(context.getPassword().getBytes(StandardCharsets.UTF_8)); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.AES; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return aes.encryptHex(value); + } else { + return aes.encryptBase64(value); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return this.aes.decryptStr(value); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java new file mode 100644 index 00000000..fde6f808 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Base64Encryptor.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import cn.hutool.core.codec.Base64; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +/** + * Base64算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Base64Encryptor extends AbstractEncryptor { + + public Base64Encryptor(EncryptContext context) { + super(context); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.BASE64; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + return Base64.encode(value); + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return Base64.decodeStr(value); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java new file mode 100644 index 00000000..1fa774fd --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/RsaEncryptor.java @@ -0,0 +1,65 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + + +/** + * RSA算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class RsaEncryptor extends AbstractEncryptor { + + private final RSA rsa; + + public RsaEncryptor(EncryptContext context) { + super(context); + String privateKey = context.getPrivateKey(); + String publicKey = context.getPublicKey(); + if (StringUtils.isAnyEmpty(privateKey, publicKey)) { + throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。"); + } + this.rsa = SecureUtil.rsa(Base64.decode(privateKey), Base64.decode(publicKey)); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.RSA; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return rsa.encryptHex(value, KeyType.PublicKey); + } else { + return rsa.encryptBase64(value, KeyType.PublicKey); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return this.rsa.decryptStr(value, KeyType.PrivateKey); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java new file mode 100644 index 00000000..08a4b40c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm2Encryptor.java @@ -0,0 +1,64 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.SM2; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +/** + * sm2算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Sm2Encryptor extends AbstractEncryptor { + + private final SM2 sm2; + + public Sm2Encryptor(EncryptContext context) { + super(context); + String privateKey = context.getPrivateKey(); + String publicKey = context.getPublicKey(); + if (StringUtils.isAnyEmpty(privateKey, publicKey)) { + throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。"); + } + this.sm2 = SmUtil.sm2(Base64.decode(privateKey), Base64.decode(publicKey)); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.SM2; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return sm2.encryptHex(value, KeyType.PublicKey); + } else { + return sm2.encryptBase64(value, KeyType.PublicKey); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return this.sm2.decryptStr(value, KeyType.PrivateKey); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java new file mode 100644 index 00000000..975ff421 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/core/encryptor/Sm4Encryptor.java @@ -0,0 +1,67 @@ +package org.ruoyi.common.encrypt.core.encryptor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.symmetric.SM4; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; + +import java.nio.charset.StandardCharsets; + +/** + * sm4算法实现 + * + * @author 老马 + * @version 4.6.0 + */ +public class Sm4Encryptor extends AbstractEncryptor { + + private final SM4 sm4; + + public Sm4Encryptor(EncryptContext context) { + super(context); + String password = context.getPassword(); + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4没有获得秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + if (16 != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度应该为16位,实际为" + password.length() + "位"); + } + this.sm4 = SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 获得当前算法 + */ + @Override + public AlgorithmType algorithm() { + return AlgorithmType.SM4; + } + + /** + * 加密 + * + * @param value 待加密字符串 + * @param encodeType 加密后的编码格式 + */ + @Override + public String encrypt(String value, EncodeType encodeType) { + if (encodeType == EncodeType.HEX) { + return sm4.encryptHex(value); + } else { + return sm4.encryptBase64(value); + } + } + + /** + * 解密 + * + * @param value 待加密字符串 + */ + @Override + public String decrypt(String value) { + return this.sm4.decryptStr(value); + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/AlgorithmType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/AlgorithmType.java new file mode 100644 index 00000000..edc747c2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/AlgorithmType.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.encrypt.enumd; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.encrypt.core.encryptor.*; + +/** + * 算法名称 + * + * @author 老马 + * @version 4.6.0 + */ +@Getter +@AllArgsConstructor +public enum AlgorithmType { + + /** + * 默认走yml配置 + */ + DEFAULT(null), + + /** + * base64 + */ + BASE64(Base64Encryptor.class), + + /** + * aes + */ + AES(AesEncryptor.class), + + /** + * rsa + */ + RSA(RsaEncryptor.class), + + /** + * sm2 + */ + SM2(Sm2Encryptor.class), + + /** + * sm4 + */ + SM4(Sm4Encryptor.class); + + private final Class clazz; +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/EncodeType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/EncodeType.java new file mode 100644 index 00000000..04284de3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/enumd/EncodeType.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.encrypt.enumd; + +/** + * 编码类型 + * + * @author 老马 + * @version 4.6.0 + */ +public enum EncodeType { + + /** + * 默认使用yml配置 + */ + DEFAULT, + + /** + * base64编码 + */ + BASE64, + + /** + * 16进制编码 + */ + HEX; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java new file mode 100644 index 00000000..99b99986 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisDecryptInterceptor.java @@ -0,0 +1,115 @@ +package org.ruoyi.common.encrypt.interceptor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.plugin.*; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.encrypt.annotation.EncryptField; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.core.EncryptorManager; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; +import org.ruoyi.common.encrypt.properties.EncryptorProperties; + +import java.lang.reflect.Field; +import java.sql.Statement; +import java.util.*; + +/** + * 出参解密拦截器 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +@Intercepts({@Signature( + type = ResultSetHandler.class, + method = "handleResultSets", + args = {Statement.class}) +}) +@AllArgsConstructor +public class MybatisDecryptInterceptor implements Interceptor { + + private final EncryptorManager encryptorManager; + private final EncryptorProperties defaultProperties; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // 获取执行mysql执行结果 + Object result = invocation.proceed(); + if (result == null) { + return null; + } + decryptHandler(result); + return result; + } + + /** + * 解密对象 + * + * @param sourceObject 待加密对象 + */ + private void decryptHandler(Object sourceObject) { + if (ObjectUtil.isNull(sourceObject)) { + return; + } + if (sourceObject instanceof Map map) { + new HashSet<>(map.values()).forEach(this::decryptHandler); + return; + } + if (sourceObject instanceof List list) { + if(CollUtil.isEmpty(list)) { + return; + } + // 判断第一个元素是否含有注解。如果没有直接返回,提高效率 + Object firstItem = list.get(0); + if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { + return; + } + list.forEach(this::decryptHandler); + return; + } + Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); + try { + for (Field field : fields) { + field.set(sourceObject, this.decryptField(String.valueOf(field.get(sourceObject)), field)); + } + } catch (Exception e) { + log.error("处理解密字段时出错", e); + } + } + + /** + * 字段值进行加密。通过字段的批注注册新的加密算法 + * + * @param value 待加密的值 + * @param field 待加密字段 + * @return 加密后结果 + */ + private String decryptField(String value, Field field) { + if (ObjectUtil.isNull(value)) { + return null; + } + EncryptField encryptField = field.getAnnotation(EncryptField.class); + EncryptContext encryptContext = new EncryptContext(); + encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); + encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode()); + encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password()); + encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey()); + encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey()); + return this.encryptorManager.decrypt(value, encryptContext); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java new file mode 100644 index 00000000..eacd31ee --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/interceptor/MybatisEncryptInterceptor.java @@ -0,0 +1,119 @@ +package org.ruoyi.common.encrypt.interceptor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.encrypt.annotation.EncryptField; +import org.ruoyi.common.encrypt.core.EncryptContext; +import org.ruoyi.common.encrypt.core.EncryptorManager; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; +import org.ruoyi.common.encrypt.properties.EncryptorProperties; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.util.*; + +/** + * 入参加密拦截器 + * + * @author 老马 + * @version 4.6.0 + */ +@Slf4j +@Intercepts({@Signature( + type = ParameterHandler.class, + method = "setParameters", + args = {PreparedStatement.class}) +}) +@AllArgsConstructor +public class MybatisEncryptInterceptor implements Interceptor { + + private final EncryptorManager encryptorManager; + private final EncryptorProperties defaultProperties; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + return invocation; + } + + @Override + public Object plugin(Object target) { + if (target instanceof ParameterHandler parameterHandler) { + // 进行加密操作 + Object parameterObject = parameterHandler.getParameterObject(); + if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) { + this.encryptHandler(parameterObject); + } + } + return target; + } + + /** + * 加密对象 + * + * @param sourceObject 待加密对象 + */ + private void encryptHandler(Object sourceObject) { + if (ObjectUtil.isNull(sourceObject)) { + return; + } + if (sourceObject instanceof Map map) { + new HashSet<>(map.values()).forEach(this::encryptHandler); + return; + } + if (sourceObject instanceof List list) { + if(CollUtil.isEmpty(list)) { + return; + } + // 判断第一个元素是否含有注解。如果没有直接返回,提高效率 + Object firstItem = list.get(0); + if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { + return; + } + list.forEach(this::encryptHandler); + return; + } + Set fields = encryptorManager.getFieldCache(sourceObject.getClass()); + try { + for (Field field : fields) { + field.set(sourceObject, this.encryptField(String.valueOf(field.get(sourceObject)), field)); + } + } catch (Exception e) { + log.error("处理加密字段时出错", e); + } + } + + /** + * 字段值进行加密。通过字段的批注注册新的加密算法 + * + * @param value 待加密的值 + * @param field 待加密字段 + * @return 加密后结果 + */ + private String encryptField(String value, Field field) { + if (ObjectUtil.isNull(value)) { + return null; + } + EncryptField encryptField = field.getAnnotation(EncryptField.class); + EncryptContext encryptContext = new EncryptContext(); + encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); + encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode()); + encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password()); + encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey()); + encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey()); + return this.encryptorManager.encrypt(value, encryptContext); + } + + + @Override + public void setProperties(Properties properties) { + } +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/properties/EncryptorProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/properties/EncryptorProperties.java new file mode 100644 index 00000000..0806312c --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/properties/EncryptorProperties.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.encrypt.properties; + +import lombok.Data; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; +import org.ruoyi.common.encrypt.enumd.EncodeType; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 加解密属性配置类 + * + * @author 老马 + * @version 4.6.0 + */ +@Data +@ConfigurationProperties(prefix = "mybatis-encryptor") +public class EncryptorProperties { + + /** + * 过滤开关 + */ + private Boolean enable; + + /** + * 默认算法 + */ + private AlgorithmType algorithm; + + /** + * 安全秘钥 + */ + private String password; + + /** + * 公钥 + */ + private String publicKey; + + /** + * 私钥 + */ + private String privateKey; + + /** + * 编码方式,base64/hex + */ + private EncodeType encode; + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/utils/EncryptUtils.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/utils/EncryptUtils.java new file mode 100644 index 00000000..b1a4ea05 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/ruoyi/common/encrypt/utils/EncryptUtils.java @@ -0,0 +1,243 @@ +package org.ruoyi.common.encrypt.utils; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.crypto.asymmetric.SM2; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * 安全相关工具类 + * + * @author 老马 + */ +public class EncryptUtils { + /** + * 公钥 + */ + public static final String PUBLIC_KEY = "publicKey"; + /** + * 私钥 + */ + public static final String PRIVATE_KEY = "privateKey"; + + /** + * Base64加密 + * + * @param data 待加密数据 + * @return 加密后字符串 + */ + public static String encryptByBase64(String data) { + return Base64.encode(data, StandardCharsets.UTF_8); + } + + /** + * Base64解密 + * + * @param data 待解密数据 + * @return 解密后字符串 + */ + public static String decryptByBase64(String data) { + return Base64.decodeStr(data, StandardCharsets.UTF_8); + } + + /** + * AES加密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptByAes(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES需要传入秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位"); + } + return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8); + } + + /** + * AES解密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 解密后字符串 + */ + public static String decryptByAes(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("AES需要传入秘钥信息"); + } + // aes算法的秘钥要求是16位、24位、32位 + int[] array = {16, 24, 32}; + if (!ArrayUtil.contains(array, password.length())) { + throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位"); + } + return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8); + } + + /** + * sm4加密 + * + * @param data 待加密数据 + * @param password 秘钥字符串 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptBySm4(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4需要传入秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + int sm4PasswordLength = 16; + if (sm4PasswordLength != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度要求为16位"); + } + return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8); + } + + /** + * sm4解密 + * + * @param data 待解密数据 + * @param password 秘钥字符串 + * @return 解密后字符串 + */ + public static String decryptBySm4(String data, String password) { + if (StrUtil.isBlank(password)) { + throw new IllegalArgumentException("SM4需要传入秘钥信息"); + } + // sm4算法的秘钥要求是16位长度 + int sm4PasswordLength = 16; + if (sm4PasswordLength != password.length()) { + throw new IllegalArgumentException("SM4秘钥长度要求为16位"); + } + return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8); + } + + /** + * 产生sm2加解密需要的公钥和私钥 + * + * @return 公私钥Map + */ + public static Map generateSm2Key() { + Map keyMap = new HashMap<>(2); + SM2 sm2 = SmUtil.sm2(); + keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64()); + keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64()); + return keyMap; + } + + /** + * sm2公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptBySm2(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("SM2需要传入公钥进行加密"); + } + SM2 sm2 = SmUtil.sm2(null, publicKey); + return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * sm2私钥解密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 解密后字符串 + */ + public static String decryptBySm2(String data, String privateKey) { + if (StrUtil.isBlank(privateKey)) { + throw new IllegalArgumentException("SM2需要传入私钥进行解密"); + } + SM2 sm2 = SmUtil.sm2(privateKey, null); + return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8); + } + + /** + * 产生RSA加解密需要的公钥和私钥 + * + * @return 公私钥Map + */ + public static Map generateRsaKey() { + Map keyMap = new HashMap<>(2); + RSA rsa = SecureUtil.rsa(); + keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64()); + keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64()); + return keyMap; + } + + /** + * rsa公钥加密 + * + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后字符串, 采用Base64编码 + */ + public static String encryptByRsa(String data, String publicKey) { + if (StrUtil.isBlank(publicKey)) { + throw new IllegalArgumentException("RSA需要传入公钥进行加密"); + } + RSA rsa = SecureUtil.rsa(null, publicKey); + return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey); + } + + /** + * rsa私钥解密 + * + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 解密后字符串 + */ + public static String decryptByRsa(String data, String privateKey) { + if (StrUtil.isBlank(privateKey)) { + throw new IllegalArgumentException("RSA需要传入私钥进行解密"); + } + RSA rsa = SecureUtil.rsa(privateKey, null); + return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8); + } + + /** + * md5加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptByMd5(String data) { + return SecureUtil.md5(data); + } + + /** + * sha256加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptBySha256(String data) { + return SecureUtil.sha256(data); + } + + /** + * sm3加密 + * + * @param data 待加密数据 + * @return 加密后字符串, 采用Hex编码 + */ + public static String encryptBySm3(String data) { + return SmUtil.sm3(data); + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 9a15a755..5e6d858b 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.encrypt.config.EncryptorAutoConfiguration +org.ruoyi.common.encrypt.config.EncryptorAutoConfiguration diff --git a/ruoyi-common/ruoyi-common-excel/pom.xml b/ruoyi-common/ruoyi-common-excel/pom.xml index cffda703..0de9c8f3 100644 --- a/ruoyi-common/ruoyi-common-excel/pom.xml +++ b/ruoyi-common/ruoyi-common-excel/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/CellMerge.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/CellMerge.java new file mode 100644 index 00000000..5a6cf9b1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/CellMerge.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.excel.annotation; + +import org.ruoyi.common.excel.core.CellMergeStrategy; + +import java.lang.annotation.*; + +/** + * excel 列单元格合并(合并列相同项) + * + * 需搭配 {@link CellMergeStrategy} 策略使用 + * + * @author Lion Li + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface CellMerge { + + /** + * col index + */ + int index() default -1; + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelDictFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelDictFormat.java new file mode 100644 index 00000000..2c2d5a99 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelDictFormat.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.excel.annotation; + +import org.ruoyi.common.core.utils.StringUtils; + +import java.lang.annotation.*; + +/** + * 字典格式化 + * + * @author Lion Li + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelDictFormat { + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + String separator() default StringUtils.SEPARATOR; + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelEnumFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelEnumFormat.java new file mode 100644 index 00000000..96e4e914 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/annotation/ExcelEnumFormat.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.excel.annotation; + +import java.lang.annotation.*; + +/** + * 枚举格式化 + * + * @author Liang + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ExcelEnumFormat { + + /** + * 字典枚举类型 + */ + Class> enumClass(); + + /** + * 字典枚举类中对应的code属性名称,默认为code + */ + String codeField() default "code"; + + /** + * 字典枚举类中对应的text属性名称,默认为text + */ + String textField() default "text"; + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelBigNumberConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelBigNumberConvert.java new file mode 100644 index 00000000..f91f9d69 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelBigNumberConvert.java @@ -0,0 +1,52 @@ +package org.ruoyi.common.excel.convert; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; + +/** + * 大数值转换 + * Excel 数值长度位15位 大于15位的数值转换位字符串 + * + * @author Lion Li + */ +@Slf4j +public class ExcelBigNumberConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Long.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Long convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return Convert.toLong(cellData.getData()); + } + + @Override + public WriteCellData convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNotNull(object)) { + String str = Convert.toStr(object); + if (str.length() > 15) { + return new WriteCellData<>(str); + } + } + WriteCellData cellData = new WriteCellData<>(new BigDecimal(object)); + cellData.setType(CellDataTypeEnum.NUMBER); + return cellData; + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelDictConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelDictConvert.java new file mode 100644 index 00000000..baffaf7e --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelDictConvert.java @@ -0,0 +1,73 @@ +package org.ruoyi.common.excel.convert; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.service.DictService; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.utils.ExcelUtil; + +import java.lang.reflect.Field; + +/** + * 字典格式化转换处理 + * + * @author Lion Li + */ +@Slf4j +public class ExcelDictConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String label = cellData.getStringValue(); + String value; + if (StringUtils.isBlank(type)) { + value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator()); + } else { + value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator()); + } + return Convert.convert(contentProperty.getField().getType(), value); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String value = Convert.toStr(object); + String label; + if (StringUtils.isBlank(type)) { + label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator()); + } else { + label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator()); + } + return new WriteCellData<>(label); + } + + private ExcelDictFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class); + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelEnumConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelEnumConvert.java new file mode 100644 index 00000000..1e2a32f0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/convert/ExcelEnumConvert.java @@ -0,0 +1,75 @@ +package org.ruoyi.common.excel.convert; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; +import org.ruoyi.common.excel.annotation.ExcelEnumFormat; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * 枚举格式化转换处理 + * + * @author Liang + */ +@Slf4j +public class ExcelEnumConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + Object codeValue = cellData.getData(); + // 如果是空值 + if (ObjectUtil.isNull(codeValue)) { + return null; + } + Map enumValueMap = beforeConvert(contentProperty); + String textValue = enumValueMap.get(codeValue); + return Convert.convert(contentProperty.getField().getType(), textValue); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + Map enumValueMap = beforeConvert(contentProperty); + String value = Convert.toStr(enumValueMap.get(object), ""); + return new WriteCellData<>(value); + } + + private Map beforeConvert(ExcelContentProperty contentProperty) { + ExcelEnumFormat anno = getAnnotation(contentProperty.getField()); + Map enumValueMap = new HashMap<>(); + Enum[] enumConstants = anno.enumClass().getEnumConstants(); + for (Enum enumConstant : enumConstants) { + Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField()); + String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField()); + enumValueMap.put(codeValue, textValue); + } + return enumValueMap; + } + + private ExcelEnumFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class); + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/CellMergeStrategy.java new file mode 100644 index 00000000..fa324970 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/CellMergeStrategy.java @@ -0,0 +1,122 @@ +package org.ruoyi.common.excel.core; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.merge.AbstractMergeStrategy; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; +import org.ruoyi.common.excel.annotation.CellMerge; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 列值重复合并策略 + * + * @author Lion Li + */ +@Slf4j +public class CellMergeStrategy extends AbstractMergeStrategy { + + private final List list; + private final boolean hasTitle; + private int rowIndex; + + public CellMergeStrategy(List list, boolean hasTitle) { + this.list = list; + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + } + + @Override + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + List cellList = handle(list, hasTitle); + // judge the list is not null + if (CollUtil.isNotEmpty(cellList)) { + // the judge is necessary + if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) { + for (CellRangeAddress item : cellList) { + sheet.addMergedRegion(item); + } + } + } + } + + @SneakyThrows + private List handle(List list, boolean hasTitle) { + List cellList = new ArrayList<>(); + if (CollUtil.isEmpty(list)) { + return cellList; + } + Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName())); + + // 有注解的字段 + List mergeFields = new ArrayList<>(); + List mergeFieldsIndex = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (field.isAnnotationPresent(CellMerge.class)) { + CellMerge cm = field.getAnnotation(CellMerge.class); + mergeFields.add(field); + mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + } + + Map map = new HashMap<>(); + // 生成两两合并单元格 + for (int i = 0; i < list.size(); i++) { + for (int j = 0; j < mergeFields.size(); j++) { + Field field = mergeFields.get(j); + Object val = ReflectUtils.invokeGetter(list.get(i), field.getName()); + + int colNum = mergeFieldsIndex.get(j); + if (!map.containsKey(field)) { + map.put(field, new RepeatCell(val, i)); + } else { + RepeatCell repeatCell = map.get(field); + Object cellValue = repeatCell.getValue(); + if (cellValue == null || "".equals(cellValue)) { + // 空值跳过不合并 + continue; + } + if (!cellValue.equals(val)) { + if (i - repeatCell.getCurrent() > 1) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } else if (i == list.size() - 1) { + if (i > repeatCell.getCurrent()) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); + } + } + } + } + } + return cellList; + } + + @Data + @AllArgsConstructor + static class RepeatCell { + + private Object value; + + private int current; + + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelListener.java new file mode 100644 index 00000000..6733068b --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelListener.java @@ -0,0 +1,104 @@ +package org.ruoyi.common.excel.core; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.exception.ExcelAnalysisException; +import com.alibaba.excel.exception.ExcelDataConvertException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.ValidatorUtils; +import org.ruoyi.common.json.utils.JsonUtils; + +import java.util.Map; +import java.util.Set; + +/** + * Excel 导入监听 + * + * @author Yjoioooo + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor +public class DefaultExcelListener extends AnalysisEventListener implements ExcelListener { + + /** + * 是否Validator检验,默认为是 + */ + private Boolean isValidate = Boolean.TRUE; + + /** + * excel 表头数据 + */ + private Map headMap; + + /** + * 导入回执 + */ + private ExcelResult excelResult; + + public DefaultExcelListener(boolean isValidate) { + this.excelResult = new DefaultExcelResult<>(); + this.isValidate = isValidate; + } + + /** + * 处理异常 + * + * @param exception ExcelDataConvertException + * @param context Excel 上下文 + */ + @Override + public void onException(Exception exception, AnalysisContext context) throws Exception { + String errMsg = null; + if (exception instanceof ExcelDataConvertException excelDataConvertException) { + // 如果是某一个单元格的转换异常 能获取到具体行号 + Integer rowIndex = excelDataConvertException.getRowIndex(); + Integer columnIndex = excelDataConvertException.getColumnIndex(); + errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常
    ", + rowIndex + 1, columnIndex + 1, headMap.get(columnIndex)); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + if (exception instanceof ConstraintViolationException constraintViolationException) { + Set> constraintViolations = constraintViolationException.getConstraintViolations(); + String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", "); + errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + excelResult.getErrorList().add(errMsg); + throw new ExcelAnalysisException(errMsg); + } + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + this.headMap = headMap; + log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap)); + } + + @Override + public void invoke(T data, AnalysisContext context) { + if (isValidate) { + ValidatorUtils.validate(data); + } + excelResult.getList().add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + log.debug("所有数据解析完成!"); + } + + @Override + public ExcelResult getExcelResult() { + return excelResult; + } + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelResult.java new file mode 100644 index 00000000..281174d6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/DefaultExcelResult.java @@ -0,0 +1,73 @@ +package org.ruoyi.common.excel.core; + +import cn.hutool.core.util.StrUtil; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 默认excel返回对象 + * + * @author Yjoioooo + * @author Lion Li + */ +public class DefaultExcelResult implements ExcelResult { + + /** + * 数据对象list + */ + @Setter + private List list; + + /** + * 错误信息列表 + */ + @Setter + private List errorList; + + public DefaultExcelResult() { + this.list = new ArrayList<>(); + this.errorList = new ArrayList<>(); + } + + public DefaultExcelResult(List list, List errorList) { + this.list = list; + this.errorList = errorList; + } + + public DefaultExcelResult(ExcelResult excelResult) { + this.list = excelResult.getList(); + this.errorList = excelResult.getErrorList(); + } + + @Override + public List getList() { + return list; + } + + @Override + public List getErrorList() { + return errorList; + } + + /** + * 获取导入回执 + * + * @return 导入回执 + */ + @Override + public String getAnalysis() { + int successCount = list.size(); + int errorCount = errorList.size(); + if (successCount == 0) { + return "读取失败,未解析到数据"; + } else { + if (errorCount == 0) { + return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount); + } else { + return ""; + } + } + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelListener.java new file mode 100644 index 00000000..66145d73 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelListener.java @@ -0,0 +1,14 @@ +package org.ruoyi.common.excel.core; + +import com.alibaba.excel.read.listener.ReadListener; + +/** + * Excel 导入监听 + * + * @author Lion Li + */ +public interface ExcelListener extends ReadListener { + + ExcelResult getExcelResult(); + +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelResult.java new file mode 100644 index 00000000..69e222df --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/core/ExcelResult.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.excel.core; + +import java.util.List; + +/** + * excel返回对象 + * + * @author Lion Li + */ +public interface ExcelResult { + + /** + * 对象列表 + */ + List getList(); + + /** + * 错误列表 + */ + List getErrorList(); + + /** + * 导入回执 + */ + String getAnalysis(); +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/utils/ExcelUtil.java new file mode 100644 index 00000000..abe7726e --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/ruoyi/common/excel/utils/ExcelUtil.java @@ -0,0 +1,327 @@ +package org.ruoyi.common.excel.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ClassPathResource; +import cn.hutool.core.util.IdUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.file.FileUtils; +import org.ruoyi.common.excel.convert.ExcelBigNumberConvert; +import org.ruoyi.common.excel.core.CellMergeStrategy; +import org.ruoyi.common.excel.core.DefaultExcelListener; +import org.ruoyi.common.excel.core.ExcelListener; +import org.ruoyi.common.excel.core.ExcelResult; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Excel相关处理 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ExcelUtil { + + /** + * 同步导入(适用于小数据量) + * + * @param is 输入流 + * @return 转换后集合 + */ + public static List importExcel(InputStream is, Class clazz) { + return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync(); + } + + + /** + * 使用校验监听器 异步导入 同步返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param isValidate 是否 Validator 检验 默认为是 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, boolean isValidate) { + DefaultExcelListener listener = new DefaultExcelListener<>(isValidate); + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 使用自定义监听器 异步导入 自定义返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param listener 自定义监听器 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, ExcelListener listener) { + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, merge, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os) { + exportExcel(list, sheetName, clazz, false, os); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, OutputStream os) { + ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz) + .autoCloseStream(false) + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .sheet(sheetName); + if (merge) { + // 合并处理器 + builder.registerWriteHandler(new CellMergeStrategy(list, true)); + } + builder.doWrite(list); + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplate(List data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplate(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplate(List data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + // 单表多数据导出 模板格式为 {.属性} + for (Object d : data) { + excelWriter.fill(d, writeSheet); + } + excelWriter.finish(); + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplateMultiList(Map data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplateMultiList(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplateMultiList(Map data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + for (Map.Entry map : data.entrySet()) { + // 设置列表后续还有数据 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + if (map.getValue() instanceof Collection) { + // 多表导出必须使用 FillWrapper + excelWriter.fill(new FillWrapper(map.getKey(), (Collection) map.getValue()), fillConfig, writeSheet); + } else { + excelWriter.fill(map.getValue(), writeSheet); + } + } + excelWriter.finish(); + } + + /** + * 重置响应体 + */ + private static void resetResponse(String sheetName, HttpServletResponse response) { + String filename = encodingFilename(sheetName); + FileUtils.setAttachmentResponseHeader(response, filename); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[0].equals(value)) { + propertyString.append(itemArray[1]).append(separator); + break; + } + } + } else { + if (itemArray[0].equals(propertyValue)) { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[1].equals(value)) { + propertyString.append(itemArray[0]).append(separator); + break; + } + } + } else { + if (itemArray[1].equals(propertyValue)) { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 编码文件名 + */ + public static String encodingFilename(String filename) { + return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx"; + } + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/pom.xml b/ruoyi-common/ruoyi-common-idempotent/pom.xml index 7380b17d..f9d8e956 100644 --- a/ruoyi-common/ruoyi-common-idempotent/pom.xml +++ b/ruoyi-common/ruoyi-common-idempotent/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-json - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/annotation/RepeatSubmit.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/annotation/RepeatSubmit.java new file mode 100644 index 00000000..4f478a50 --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/annotation/RepeatSubmit.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.idempotent.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 自定义注解防止表单重复提交 + * + * @author Lion Li + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit { + + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + int interval() default 5000; + + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; + + /** + * 提示消息 支持国际化 格式为 {code} + */ + String message() default "{repeat.submit.message}"; + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/aspectj/RepeatSubmitAspect.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/aspectj/RepeatSubmitAspect.java new file mode 100644 index 00000000..cb45eacf --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/aspectj/RepeatSubmitAspect.java @@ -0,0 +1,152 @@ +package org.ruoyi.common.idempotent.aspectj; + +import cn.dev33.satoken.SaManager; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MessageUtils; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import java.time.Duration; +import java.util.Collection; +import java.util.Map; + +/** + * 防止重复提交(参考美团GTIS防重系统) + * + * @author Lion Li + */ +@Aspect +public class RepeatSubmitAspect { + + private static final ThreadLocal KEY_CACHE = new ThreadLocal<>(); + + @Before("@annotation(repeatSubmit)") + public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { + // 如果注解不为0 则使用注解数值 + long interval = 0; + if (repeatSubmit.interval() > 0) { + interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); + } + if (interval < 1000) { + throw new ServiceException("重复提交间隔时间不能小于'1'秒"); + } + HttpServletRequest request = ServletUtils.getRequest(); + String nowParams = argsArrayToString(point.getArgs()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName())); + + submitKey = SecureUtil.md5(submitKey + ":" + nowParams); + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey; + String key = RedisUtils.getCacheObject(cacheRepeatKey); + if (key == null) { + RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval)); + KEY_CACHE.set(cacheRepeatKey); + } else { + String message = repeatSubmit.message(); + if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { + message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); + } + throw new ServiceException(message); + } + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) { + if (jsonResult instanceof R r) { + try { + // 成功则不删除redis数据 保证在有效时间内无法重复提交 + if (r.getCode() == R.SUCCESS) { + return; + } + RedisUtils.deleteObject(KEY_CACHE.get()); + } finally { + KEY_CACHE.remove(); + } + } + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { + RedisUtils.deleteObject(KEY_CACHE.get()); + KEY_CACHE.remove(); + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + try { + params.append(JsonUtils.toJsonString(o)).append(" "); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/config/IdempotentConfig.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/config/IdempotentConfig.java new file mode 100644 index 00000000..4e243dbf --- /dev/null +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/ruoyi/common/idempotent/config/IdempotentConfig.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.idempotent.config; + +import org.ruoyi.common.idempotent.aspectj.RepeatSubmitAspect; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConfiguration; + +/** + * 幂等功能配置 + * + * @author Lion Li + */ +@AutoConfiguration(after = RedisConfiguration.class) +public class IdempotentConfig { + + @Bean + public RepeatSubmitAspect repeatSubmitAspect() { + return new RepeatSubmitAspect(); + } + +} diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 5498b60b..88f81a68 100644 --- a/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.idempotent.config.IdempotentConfig +org.ruoyi.common.idempotent.config.IdempotentConfig diff --git a/ruoyi-common/ruoyi-common-json/pom.xml b/ruoyi-common/ruoyi-common-json/pom.xml index 13dcf387..c4a00a0a 100644 --- a/ruoyi-common/ruoyi-common-json/pom.xml +++ b/ruoyi-common/ruoyi-common-json/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-core diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/config/JacksonConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/config/JacksonConfig.java new file mode 100644 index 00000000..71c6e357 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/config/JacksonConfig.java @@ -0,0 +1,47 @@ +package org.ruoyi.common.json.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.json.handler.BigNumberSerializer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +/** + * jackson 配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration(before = JacksonAutoConfiguration.class) +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> { + // 全局配置序列化返回 JSON 处理 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + builder.modules(javaTimeModule); + builder.timeZone(TimeZone.getDefault()); + log.info("初始化 jackson 配置"); + }; + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/handler/BigNumberSerializer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/handler/BigNumberSerializer.java new file mode 100644 index 00000000..071a7dc3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/handler/BigNumberSerializer.java @@ -0,0 +1,42 @@ +package org.ruoyi.common.json.handler; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.ser.std.NumberSerializer; + +import java.io.IOException; + +/** + * 超出 JS 最大最小值 处理 + * + * @author Lion Li + */ +@JacksonStdImpl +public class BigNumberSerializer extends NumberSerializer { + + /** + * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 + */ + private static final long MAX_SAFE_INTEGER = 9007199254740991L; + private static final long MIN_SAFE_INTEGER = -9007199254740991L; + + /** + * 提供实例 + */ + public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class); + + public BigNumberSerializer(Class rawType) { + super(rawType); + } + + @Override + public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { + // 超出范围 序列化位字符串 + if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { + super.serialize(value, gen, provider); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/utils/JsonUtils.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/utils/JsonUtils.java new file mode 100644 index 00000000..3653e420 --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/ruoyi/common/json/utils/JsonUtils.java @@ -0,0 +1,113 @@ +package org.ruoyi.common.json.utils; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + * + * @author 芋道源码 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JsonUtils { + + private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class); + + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + + public static String toJsonString(Object object) { + if (ObjectUtil.isNull(object)) { + return null; + } + try { + return OBJECT_MAPPER.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(bytes, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference typeReference) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, typeReference); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Dict parseMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class)); + } catch (MismatchedInputException e) { + // 类型不匹配说明不是json + return null; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArrayMap(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static List parseArray(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return new ArrayList<>(); + } + try { + return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d1882e80..c6a94da8 100644 --- a/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.json.config.JacksonConfig +org.ruoyi.common.json.config.JacksonConfig diff --git a/ruoyi-common/ruoyi-common-log/pom.xml b/ruoyi-common/ruoyi-common-log/pom.xml index a6af821c..b26144d0 100644 --- a/ruoyi-common/ruoyi-common-log/pom.xml +++ b/ruoyi-common/ruoyi-common-log/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -19,12 +19,12 @@ - com.xmzs + org.ruoyi ruoyi-common-satoken - com.xmzs + org.ruoyi ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/annotation/Log.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/annotation/Log.java new file mode 100644 index 00000000..b964b55f --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/annotation/Log.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.log.annotation; + +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.log.enums.OperatorType; + +import java.lang.annotation.*; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log { + /** + * 模块 + */ + String title() default ""; + + /** + * 功能 + */ + BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + boolean isSaveResponseData() default true; + + + /** + * 排除指定的请求参数 + */ + String[] excludeParamNames() default {}; + +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/aspect/LogAspect.java new file mode 100644 index 00000000..63bcefcf --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/aspect/LogAspect.java @@ -0,0 +1,221 @@ +package org.ruoyi.common.log.aspect; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessStatus; +import org.ruoyi.common.log.event.OperLogEvent; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.http.HttpMethod; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; +import java.util.Map; + +/** + * 操作日志记录处理 + * + * @author Lion Li + */ +@Slf4j +@Aspect +@AutoConfiguration +public class LogAspect { + + /** + * 排除敏感属性字段 + */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + + /** + * 计算操作消耗时间 + */ + private static final ThreadLocal TIME_THREADLOCAL = new TransmittableThreadLocal<>(); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(JoinPoint joinPoint, Log controllerLog) { + StopWatch stopWatch = new StopWatch(); + TIME_THREADLOCAL.set(stopWatch); + stopWatch.start(); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { + + // *========数据库日志=========*// + OperLogEvent operLog = new OperLogEvent(); + operLog.setTenantId(LoginHelper.getTenantId()); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = ServletUtils.getClientIP(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + operLog.setOperName(LoginHelper.getUsername()); + + if (e != null) { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + StopWatch stopWatch = TIME_THREADLOCAL.get(); + stopWatch.stop(); + operLog.setCostTime(stopWatch.getTime()); + // 发布事件保存数据库 + SpringUtils.context().publishEvent(operLog); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } finally { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (MapUtil.isEmpty(paramsMap) + && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES); + MapUtil.removeAny(paramsMap, excludeParamNames); + operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + try { + String str = JsonUtils.toJsonString(o); + Dict dict = JsonUtils.parseMap(str); + if (MapUtil.isNotEmpty(dict)) { + MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); + MapUtil.removeAny(dict, excludeParamNames); + str = JsonUtils.toJsonString(dict); + } + params.append(str).append(" "); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessStatus.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessStatus.java new file mode 100644 index 00000000..22702a03 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessStatus.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.log.enums; + +/** + * 操作状态 + * + * @author ruoyi + */ +public enum BusinessStatus { + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessType.java new file mode 100644 index 00000000..5d4162c3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/BusinessType.java @@ -0,0 +1,58 @@ +package org.ruoyi.common.log.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType { + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/OperatorType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/OperatorType.java new file mode 100644 index 00000000..1bbb8855 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/enums/OperatorType.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.log.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType { + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/LogininforEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/LogininforEvent.java new file mode 100644 index 00000000..39799eed --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/LogininforEvent.java @@ -0,0 +1,51 @@ +package org.ruoyi.common.log.event; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 登录事件 + * + * @author Lion Li + */ + +@Data +public class LogininforEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 用户账号 + */ + private String username; + + /** + * 登录状态 0成功 1失败 + */ + private String status; + + /** + * 提示消息 + */ + private String message; + + /** + * 请求体 + */ + private HttpServletRequest request; + + /** + * 其他参数 + */ + private Object[] args; + +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/OperLogEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/OperLogEvent.java new file mode 100644 index 00000000..fca72419 --- /dev/null +++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/ruoyi/common/log/event/OperLogEvent.java @@ -0,0 +1,115 @@ +package org.ruoyi.common.log.event; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 操作日志事件 + * + * @author Lion Li + */ + +@Data +public class OperLogEvent implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日志主键 + */ + private Long operId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 操作模块 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求url + */ + private String operUrl; + + /** + * 操作地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 消耗时间 + */ + private Long costTime; +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 746b32fe..337ec747 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.log.aspect.LogAspect +org.ruoyi.common.log.aspect.LogAspect diff --git a/ruoyi-common/ruoyi-common-mail/pom.xml b/ruoyi-common/ruoyi-common-mail/pom.xml index 484c71b4..a1453420 100644 --- a/ruoyi-common/ruoyi-common-mail/pom.xml +++ b/ruoyi-common/ruoyi-common-mail/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-core diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java new file mode 100644 index 00000000..acd82278 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java @@ -0,0 +1,55 @@ +package org.ruoyi.common.mail.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.mail.utils.MailAccount; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * JavaMail 配置 + * + * @author Michelle.Chung + */ + +@RequiredArgsConstructor +@Configuration +@Slf4j +public class MailConfig { + + private final ConfigService configService; + private MailAccount account; // 缓存MailAccount实例 + + @Bean + @Scope("singleton") + public MailAccount mailAccount() { + if (account == null) { + account = new MailAccount(); + updateMailAccount(); + } + return account; + } + + @Scheduled(fixedDelay = 10000) // 每10秒检查一次 + public void updateMailAccount() { + account.setHost(getKey("host")); + account.setPort(NumberUtils.toInt(getKey("port"), 465)); + account.setAuth(true); + account.setFrom(getKey("from")); + account.setUser(getKey("user")); + account.setPass(getKey("pass")); + account.setSocketFactoryPort(NumberUtils.toInt(getKey("port"), 465)); + account.setStarttlsEnable(true); + account.setSslEnable(true); + account.setTimeout(0); + account.setConnectionTimeout(0); + } + + public String getKey(String key){ + return configService.getConfigValue("mail", key); + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java new file mode 100644 index 00000000..246172bb --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java @@ -0,0 +1,67 @@ +package org.ruoyi.common.mail.config.properties; + +import lombok.Data; + +/** + * JavaMail 配置属性 + * + * @author Michelle.Chung + */ +@Data +public class MailProperties { + + /** + * 过滤开关 + */ + private Boolean enabled; + + /** + * SMTP服务器域名 + */ + private String host; + + /** + * SMTP服务端口 + */ + private Integer port; + + /** + * 是否需要用户名密码验证 + */ + private Boolean auth; + + /** + * 用户名 + */ + private String user; + + /** + * 密码 + */ + private String pass; + + /** + * 发送方,遵循RFC-822标准 + */ + private String from; + + /** + * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + */ + private Boolean starttlsEnable; + + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + + /** + * SMTP超时时长,单位毫秒,缺省值不超时 + */ + private Long timeout; + + /** + * Socket连接超时值,单位毫秒,缺省值不超时 + */ + private Long connectionTimeout; +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/GlobalMailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/GlobalMailAccount.java new file mode 100644 index 00000000..447738c9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/GlobalMailAccount.java @@ -0,0 +1,46 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.io.IORuntimeException; + +/** + * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS} + * + * @author looly + */ +public enum GlobalMailAccount { + INSTANCE; + + private final MailAccount mailAccount; + + /** + * 构造 + */ + GlobalMailAccount() { + mailAccount = createDefaultAccount(); + } + + /** + * 获得邮件帐户 + * + * @return 邮件帐户 + */ + public MailAccount getAccount() { + return this.mailAccount; + } + + /** + * 创建默认帐户 + * + * @return MailAccount + */ + private MailAccount createDefaultAccount() { + for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) { + try { + return new MailAccount(mailSettingPath); + } catch (IORuntimeException ignore) { + //ignore + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/InternalMailUtil.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/InternalMailUtil.java new file mode 100644 index 00000000..b74accf1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/InternalMailUtil.java @@ -0,0 +1,108 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.util.ArrayUtil; +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeUtility; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 邮件内部工具类 + * + * @author looly + * @since 3.2.3 + */ +public class InternalMailUtil { + + /** + * 将多个字符串邮件地址转为{@link InternetAddress}列表
    + * 单个字符串地址可以是多个地址合并的字符串 + * + * @param addrStrs 地址数组 + * @param charset 编码(主要用于中文用户名的编码) + * @return 地址数组 + * @since 4.0.3 + */ + public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) { + final List resultList = new ArrayList<>(addrStrs.length); + InternetAddress[] addrs; + for (String addrStr : addrStrs) { + addrs = parseAddress(addrStr, charset); + if (ArrayUtil.isNotEmpty(addrs)) { + Collections.addAll(resultList, addrs); + } + } + return resultList.toArray(new InternetAddress[0]); + } + + /** + * 解析第一个地址 + * + * @param address 地址字符串 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 + * @return 地址列表 + */ + public static InternetAddress parseFirstAddress(String address, Charset charset) { + final InternetAddress[] internetAddresses = parseAddress(address, charset); + if (ArrayUtil.isEmpty(internetAddresses)) { + try { + return new InternetAddress(address); + } catch (AddressException e) { + throw new MailException(e); + } + } + return internetAddresses[0]; + } + + /** + * 将一个地址字符串解析为多个地址
    + * 地址间使用" "、","、";"分隔 + * + * @param address 地址字符串 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 + * @return 地址列表 + */ + public static InternetAddress[] parseAddress(String address, Charset charset) { + InternetAddress[] addresses; + try { + addresses = InternetAddress.parse(address); + } catch (AddressException e) { + throw new MailException(e); + } + //编码用户名 + if (ArrayUtil.isNotEmpty(addresses)) { + final String charsetStr = null == charset ? null : charset.name(); + for (InternetAddress internetAddress : addresses) { + try { + internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr); + } catch (UnsupportedEncodingException e) { + throw new MailException(e); + } + } + } + + return addresses; + } + + /** + * 编码中文字符
    + * 编码失败返回原字符串 + * + * @param text 被编码的文本 + * @param charset 编码 + * @return 编码后的结果 + */ + public static String encodeText(String text, Charset charset) { + try { + return MimeUtility.encodeText(text, charset.name(), null); + } catch (UnsupportedEncodingException e) { + // ignore + } + return text; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/Mail.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/Mail.java new file mode 100644 index 00000000..00d3341b --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/Mail.java @@ -0,0 +1,483 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.builder.Builder; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; +import jakarta.activation.FileDataSource; +import jakarta.activation.FileTypeMap; +import jakarta.mail.*; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import jakarta.mail.internet.MimeUtility; +import jakarta.mail.util.ByteArrayDataSource; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.Date; + +/** + * 邮件发送客户端 + * + * @author looly + * @since 3.2.0 + */ +public class Mail implements Builder { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 邮箱帐户信息以及一些客户端配置信息 + */ + private final MailAccount mailAccount; + /** + * 收件人列表 + */ + private String[] tos; + /** + * 抄送人列表(carbon copy) + */ + private String[] ccs; + /** + * 密送人列表(blind carbon copy) + */ + private String[] bccs; + /** + * 回复地址(reply-to) + */ + private String[] reply; + /** + * 标题 + */ + private String title; + /** + * 内容 + */ + private String content; + /** + * 是否为HTML + */ + private boolean isHtml; + /** + * 正文、附件和图片的混合部分 + */ + private final Multipart multipart = new MimeMultipart(); + /** + * 是否使用全局会话,默认为false + */ + private boolean useGlobalSession = false; + + /** + * debug输出位置,可以自定义debug日志 + */ + private PrintStream debugOutput; + + /** + * 创建邮件客户端 + * + * @param mailAccount 邮件帐号 + * @return Mail + */ + public static Mail create(MailAccount mailAccount) { + return new Mail(mailAccount); + } + + /** + * 创建邮件客户端,使用全局邮件帐户 + * + * @return Mail + */ + public static Mail create() { + return new Mail(); + } + + // --------------------------------------------------------------- Constructor start + + /** + * 构造,使用全局邮件帐户 + */ + public Mail() { + this(GlobalMailAccount.INSTANCE.getAccount()); + } + + /** + * 构造 + * + * @param mailAccount 邮件帐户,如果为null使用默认配置文件的全局邮件配置 + */ + public Mail(MailAccount mailAccount) { + mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount(); + this.mailAccount = mailAccount.defaultIfEmpty(); + } + // --------------------------------------------------------------- Constructor end + + // --------------------------------------------------------------- Getters and Setters start + + /** + * 设置收件人 + * + * @param tos 收件人列表 + * @return this + * @see #setTos(String...) + */ + public Mail to(String... tos) { + return setTos(tos); + } + + /** + * 设置多个收件人 + * + * @param tos 收件人列表 + * @return this + */ + public Mail setTos(String... tos) { + this.tos = tos; + return this; + } + + /** + * 设置多个抄送人(carbon copy) + * + * @param ccs 抄送人列表 + * @return this + * @since 4.0.3 + */ + public Mail setCcs(String... ccs) { + this.ccs = ccs; + return this; + } + + /** + * 设置多个密送人(blind carbon copy) + * + * @param bccs 密送人列表 + * @return this + * @since 4.0.3 + */ + public Mail setBccs(String... bccs) { + this.bccs = bccs; + return this; + } + + /** + * 设置多个回复地址(reply-to) + * + * @param reply 回复地址(reply-to)列表 + * @return this + * @since 4.6.0 + */ + public Mail setReply(String... reply) { + this.reply = reply; + return this; + } + + /** + * 设置标题 + * + * @param title 标题 + * @return this + */ + public Mail setTitle(String title) { + this.title = title; + return this; + } + + /** + * 设置正文
    + * 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML + * + * @param content 正文 + * @return this + */ + public Mail setContent(String content) { + this.content = content; + return this; + } + + /** + * 设置是否是HTML + * + * @param isHtml 是否为HTML + * @return this + */ + public Mail setHtml(boolean isHtml) { + this.isHtml = isHtml; + return this; + } + + /** + * 设置正文 + * + * @param content 正文内容 + * @param isHtml 是否为HTML + * @return this + */ + public Mail setContent(String content, boolean isHtml) { + setContent(content); + return setHtml(isHtml); + } + + /** + * 设置文件类型附件,文件可以是图片文件,此时自动设置cid(正文中引用图片),默认cid为文件名 + * + * @param files 附件文件列表 + * @return this + */ + public Mail setFiles(File... files) { + if (ArrayUtil.isEmpty(files)) { + return this; + } + + final DataSource[] attachments = new DataSource[files.length]; + for (int i = 0; i < files.length; i++) { + attachments[i] = new FileDataSource(files[i]); + } + return setAttachments(attachments); + } + + /** + * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件 + * + * @param attachments 附件列表 + * @return this + * @since 4.0.9 + */ + public Mail setAttachments(DataSource... attachments) { + if (ArrayUtil.isNotEmpty(attachments)) { + final Charset charset = this.mailAccount.getCharset(); + MimeBodyPart bodyPart; + String nameEncoded; + try { + for (DataSource attachment : attachments) { + bodyPart = new MimeBodyPart(); + bodyPart.setDataHandler(new DataHandler(attachment)); + nameEncoded = attachment.getName(); + if (this.mailAccount.isEncodefilename()) { + nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset); + } + // 普通附件文件名 + bodyPart.setFileName(nameEncoded); + if (StrUtil.startWith(attachment.getContentType(), "image/")) { + // 图片附件,用于正文中引用图片 + bodyPart.setContentID(nameEncoded); + } + this.multipart.addBodyPart(bodyPart); + } + } catch (MessagingException e) { + throw new MailException(e); + } + } + return this; + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg" + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageStream 图片文件 + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, InputStream imageStream) { + return addImage(cid, imageStream, null); + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串 + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageStream 图片流,不关闭 + * @param contentType 图片类型,null赋值默认的"image/jpeg" + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, InputStream imageStream, String contentType) { + ByteArrayDataSource imgSource; + try { + imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg")); + } catch (IOException e) { + throw new IORuntimeException(e); + } + imgSource.setName(cid); + return setAttachments(imgSource); + } + + /** + * 增加图片,图片的键对应到邮件模板中的占位字符串 + * + * @param cid 图片与占位符,占位符格式为cid:${cid} + * @param imageFile 图片文件 + * @return this + * @since 4.6.3 + */ + public Mail addImage(String cid, File imageFile) { + InputStream in = null; + try { + in = FileUtil.getInputStream(imageFile); + return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile)); + } finally { + IoUtil.close(in); + } + } + + /** + * 设置字符集编码 + * + * @param charset 字符集编码 + * @return this + * @see MailAccount#setCharset(Charset) + */ + public Mail setCharset(Charset charset) { + this.mailAccount.setCharset(charset); + return this; + } + + /** + * 设置是否使用全局会话,默认为true + * + * @param isUseGlobalSession 是否使用全局会话,默认为true + * @return this + * @since 4.0.2 + */ + public Mail setUseGlobalSession(boolean isUseGlobalSession) { + this.useGlobalSession = isUseGlobalSession; + return this; + } + + /** + * 设置debug输出位置,可以自定义debug日志 + * + * @param debugOutput debug输出位置 + * @return this + * @since 5.5.6 + */ + public Mail setDebugOutput(PrintStream debugOutput) { + this.debugOutput = debugOutput; + return this; + } + // --------------------------------------------------------------- Getters and Setters end + + @Override + public MimeMessage build() { + try { + return buildMsg(); + } catch (MessagingException e) { + throw new MailException(e); + } + } + + /** + * 发送 + * + * @return message-id + * @throws MailException 邮件发送异常 + */ + public String send() throws MailException { + try { + return doSend(); + } catch (MessagingException e) { + if (e instanceof SendFailedException) { + // 当地址无效时,显示更加详细的无效地址信息 + final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses(); + final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses)); + throw new MailException(msg, e); + } + throw new MailException(e); + } + } + + // --------------------------------------------------------------- Private method start + + /** + * 执行发送 + * + * @return message-id + * @throws MessagingException 发送异常 + */ + private String doSend() throws MessagingException { + final MimeMessage mimeMessage = buildMsg(); + Transport.send(mimeMessage); + return mimeMessage.getMessageID(); + } + + /** + * 构建消息 + * + * @return {@link MimeMessage}消息 + * @throws MessagingException 消息异常 + */ + private MimeMessage buildMsg() throws MessagingException { + final Charset charset = this.mailAccount.getCharset(); + final MimeMessage msg = new MimeMessage(getSession()); + // 发件人 + final String from = this.mailAccount.getFrom(); + if (StrUtil.isEmpty(from)) { + // 用户未提供发送方,则从Session中自动获取 + msg.setFrom(); + } else { + msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset)); + } + // 标题 + msg.setSubject(this.title, (null == charset) ? null : charset.name()); + // 发送时间 + msg.setSentDate(new Date()); + // 内容和附件 + msg.setContent(buildContent(charset)); + // 收件人 + msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset)); + // 抄送人 + if (ArrayUtil.isNotEmpty(this.ccs)) { + msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset)); + } + // 密送人 + if (ArrayUtil.isNotEmpty(this.bccs)) { + msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset)); + } + // 回复地址(reply-to) + if (ArrayUtil.isNotEmpty(this.reply)) { + msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset)); + } + + return msg; + } + + /** + * 构建邮件信息主体 + * + * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()} + * @return 邮件信息主体 + * @throws MessagingException 消息异常 + */ + private Multipart buildContent(Charset charset) throws MessagingException { + final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset(); + // 正文 + final MimeBodyPart body = new MimeBodyPart(); + body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr)); + this.multipart.addBodyPart(body); + + return this.multipart; + } + + /** + * 获取默认邮件会话
    + * 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话 + * + * @return 邮件会话 {@link Session} + */ + private Session getSession() { + final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession); + + if (null != this.debugOutput) { + session.setDebugOut(debugOutput); + } + + return session; + } + // --------------------------------------------------------------- Private method end +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailAccount.java new file mode 100644 index 00000000..96a1f813 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailAccount.java @@ -0,0 +1,659 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.setting.Setting; + +import java.io.Serial; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * 邮件账户对象 + * + * @author Luxiaolei + */ +public class MailAccount implements Serializable { + @Serial + private static final long serialVersionUID = -6937313421815719204L; + + private static final String MAIL_PROTOCOL = "mail.transport.protocol"; + private static final String SMTP_HOST = "mail.smtp.host"; + private static final String SMTP_PORT = "mail.smtp.port"; + private static final String SMTP_AUTH = "mail.smtp.auth"; + private static final String SMTP_TIMEOUT = "mail.smtp.timeout"; + private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout"; + private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout"; + + // SSL + private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable"; + private static final String SSL_ENABLE = "mail.smtp.ssl.enable"; + private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols"; + private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class"; + private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback"; + private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port"; + + // System Properties + private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; + //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename"; + //private static final String CHARSET = "mail.mime.charset"; + + // 其他 + private static final String MAIL_DEBUG = "mail.debug"; + + public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"}; + + /** + * SMTP服务器域名 + */ + private String host; + /** + * SMTP服务端口 + */ + private Integer port; + /** + * 是否需要用户名密码验证 + */ + private Boolean auth; + /** + * 用户名 + */ + private String user; + /** + * 密码 + */ + private String pass; + /** + * 发送方,遵循RFC-822标准 + */ + private String from; + + /** + * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + */ + private boolean debug; + /** + * 编码用于编码邮件正文和发送人、收件人等中文 + */ + private Charset charset = CharsetUtil.CHARSET_UTF_8; + /** + * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) + */ + private boolean splitlongparameters = false; + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + */ + private boolean encodefilename = true; + + /** + * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + */ + private boolean starttlsEnable = false; + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + + /** + * SSL协议,多个协议用空格分隔 + */ + private String sslProtocols; + + /** + * 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + */ + private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory"; + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + */ + private boolean socketFactoryFallback; + /** + * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + */ + private int socketFactoryPort = 465; + + /** + * SMTP超时时长,单位毫秒,缺省值不超时 + */ + private long timeout; + /** + * Socket连接超时值,单位毫秒,缺省值不超时 + */ + private long connectionTimeout; + /** + * Socket写出超时值,单位毫秒,缺省值不超时 + */ + private long writeTimeout; + + /** + * 自定义的其他属性,此自定义属性会覆盖默认属性 + */ + private final Map customProperty = new HashMap<>(); + + // -------------------------------------------------------------- Constructor start + + /** + * 构造,所有参数需自行定义或保持默认值 + */ + public MailAccount() { + } + + /** + * 构造 + * + * @param settingPath 配置文件路径 + */ + public MailAccount(String settingPath) { + this(new Setting(settingPath)); + } + + /** + * 构造 + * + * @param setting 配置文件 + */ + public MailAccount(Setting setting) { + setting.toBean(this); + } + + // -------------------------------------------------------------- Constructor end + + /** + * 获得SMTP服务器域名 + * + * @return SMTP服务器域名 + */ + public String getHost() { + return host; + } + + /** + * 设置SMTP服务器域名 + * + * @param host SMTP服务器域名 + * @return this + */ + public MailAccount setHost(String host) { + this.host = host; + return this; + } + + /** + * 获得SMTP服务端口 + * + * @return SMTP服务端口 + */ + public Integer getPort() { + return port; + } + + /** + * 设置SMTP服务端口 + * + * @param port SMTP服务端口 + * @return this + */ + public MailAccount setPort(Integer port) { + this.port = port; + return this; + } + + /** + * 是否需要用户名密码验证 + * + * @return 是否需要用户名密码验证 + */ + public Boolean isAuth() { + return auth; + } + + /** + * 设置是否需要用户名密码验证 + * + * @param isAuth 是否需要用户名密码验证 + * @return this + */ + public MailAccount setAuth(boolean isAuth) { + this.auth = isAuth; + return this; + } + + /** + * 获取用户名 + * + * @return 用户名 + */ + public String getUser() { + return user; + } + + /** + * 设置用户名 + * + * @param user 用户名 + * @return this + */ + public MailAccount setUser(String user) { + this.user = user; + return this; + } + + /** + * 获取密码 + * + * @return 密码 + */ + public String getPass() { + return pass; + } + + /** + * 设置密码 + * + * @param pass 密码 + * @return this + */ + public MailAccount setPass(String pass) { + this.pass = pass; + return this; + } + + /** + * 获取发送方,遵循RFC-822标准 + * + * @return 发送方,遵循RFC-822标准 + */ + public String getFrom() { + return from; + } + + /** + * 设置发送方,遵循RFC-822标准
    + * 发件人可以是以下形式: + * + *
    +     * 1. user@xxx.xx
    +     * 2.  name <user@xxx.xx>
    +     * 
    + * + * @param from 发送方,遵循RFC-822标准 + * @return this + */ + public MailAccount setFrom(String from) { + this.from = from; + return this; + } + + /** + * 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * + * @return 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * @since 4.0.2 + */ + public boolean isDebug() { + return debug; + } + + /** + * 设置是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * + * @param debug 是否打开调试模式,调试模式会显示与邮件服务器通信过程,默认不开启 + * @return this + * @since 4.0.2 + */ + public MailAccount setDebug(boolean debug) { + this.debug = debug; + return this; + } + + /** + * 获取字符集编码 + * + * @return 编码,可能为{@code null} + */ + public Charset getCharset() { + return charset; + } + + /** + * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置: + *
    +     * 	System.setProperty("mail.mime.charset", charset);
    +     * 
    + * + * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性 + * @return this + */ + public MailAccount setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) + * + * @return 对于超长参数是否切分为多份 + */ + public boolean isSplitlongparameters() { + return splitlongparameters; + } + + /** + * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
    + * 注意此项为全局设置,此项会调用 + *
    +     * System.setProperty("mail.mime.splitlongparameters", true)
    +     * 
    + * + * @param splitlongparameters 对于超长参数是否切分为多份 + */ + public void setSplitlongparameters(boolean splitlongparameters) { + this.splitlongparameters = splitlongparameters; + } + + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * + * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * @since 5.7.16 + */ + public boolean isEncodefilename() { + + return encodefilename; + } + + /** + * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置
    + * 如果此选项设置为{@code false},则是否编码取决于两个系统属性: + *
      + *
    • mail.mime.encodefilename 是否编码附件文件名
    • + *
    • mail.mime.charset 编码文件名的编码
    • + *
    + * + * @param encodefilename 对于文件名是否使用{@link #charset}编码 + * @since 5.7.16 + */ + public void setEncodefilename(boolean encodefilename) { + this.encodefilename = encodefilename; + } + + /** + * 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + * + * @return 是否使用 STARTTLS安全连接 + */ + public boolean isStarttlsEnable() { + return this.starttlsEnable; + } + + /** + * 设置是否使用STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + * + * @param startttlsEnable 是否使用STARTTLS安全连接 + * @return this + */ + public MailAccount setStarttlsEnable(boolean startttlsEnable) { + this.starttlsEnable = startttlsEnable; + return this; + } + + /** + * 是否使用 SSL安全连接 + * + * @return 是否使用 SSL安全连接 + */ + public Boolean isSslEnable() { + return this.sslEnable; + } + + /** + * 设置是否使用SSL安全连接 + * + * @param sslEnable 是否使用SSL安全连接 + * @return this + */ + public MailAccount setSslEnable(Boolean sslEnable) { + this.sslEnable = sslEnable; + return this; + } + + /** + * 获取SSL协议,多个协议用空格分隔 + * + * @return SSL协议,多个协议用空格分隔 + * @since 5.5.7 + */ + public String getSslProtocols() { + return sslProtocols; + } + + /** + * 设置SSL协议,多个协议用空格分隔 + * + * @param sslProtocols SSL协议,多个协议用空格分隔 + * @since 5.5.7 + */ + public void setSslProtocols(String sslProtocols) { + this.sslProtocols = sslProtocols; + } + + /** + * 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * + * @return 指定实现javax.net.SocketFactory接口的类的名称, 这个类将被用于创建SMTP的套接字 + */ + public String getSocketFactoryClass() { + return socketFactoryClass; + } + + /** + * 设置指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * + * @param socketFactoryClass 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字 + * @return this + */ + public MailAccount setSocketFactoryClass(String socketFactoryClass) { + this.socketFactoryClass = socketFactoryClass; + return this; + } + + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * + * @return 如果设置为true, 未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + */ + public boolean isSocketFactoryFallback() { + return socketFactoryFallback; + } + + /** + * 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * + * @param socketFactoryFallback 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true + * @return this + */ + public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) { + this.socketFactoryFallback = socketFactoryFallback; + return this; + } + + /** + * 获取指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * + * @return 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + */ + public int getSocketFactoryPort() { + return socketFactoryPort; + } + + /** + * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * + * @param socketFactoryPort 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + * @return this + */ + public MailAccount setSocketFactoryPort(int socketFactoryPort) { + this.socketFactoryPort = socketFactoryPort; + return this; + } + + /** + * 设置SMTP超时时长,单位毫秒,缺省值不超时 + * + * @param timeout SMTP超时时长,单位毫秒,缺省值不超时 + * @return this + * @since 4.1.17 + */ + public MailAccount setTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + /** + * 设置Socket连接超时值,单位毫秒,缺省值不超时 + * + * @param connectionTimeout Socket连接超时值,单位毫秒,缺省值不超时 + * @return this + * @since 4.1.17 + */ + public MailAccount setConnectionTimeout(long connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + /** + * 设置Socket写出超时值,单位毫秒,缺省值不超时 + * + * @param writeTimeout Socket写出超时值,单位毫秒,缺省值不超时 + * @return this + * @since 5.8.3 + */ + public MailAccount setWriteTimeout(long writeTimeout) { + this.writeTimeout = writeTimeout; + return this; + } + + /** + * 获取自定义属性列表 + * + * @return 自定义参数列表 + * @since 5.6.4 + */ + public Map getCustomProperty() { + return customProperty; + } + + /** + * 设置自定义属性,如mail.smtp.ssl.socketFactory + * + * @param key 属性名,空白被忽略 + * @param value 属性值, null被忽略 + * @return this + * @since 5.6.4 + */ + public MailAccount setCustomProperty(String key, Object value) { + if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) { + this.customProperty.put(key, value); + } + return this; + } + + /** + * 获得SMTP相关信息 + * + * @return {@link Properties} + */ + public Properties getSmtpProps() { + //全局系统参数 + System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters)); + + final Properties p = new Properties(); + p.put(MAIL_PROTOCOL, "smtp"); + p.put(SMTP_HOST, this.host); + p.put(SMTP_PORT, String.valueOf(this.port)); + p.put(SMTP_AUTH, String.valueOf(this.auth)); + if (this.timeout > 0) { + p.put(SMTP_TIMEOUT, String.valueOf(this.timeout)); + } + if (this.connectionTimeout > 0) { + p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout)); + } + // issue#2355 + if (this.writeTimeout > 0) { + p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout)); + } + + p.put(MAIL_DEBUG, String.valueOf(this.debug)); + + if (this.starttlsEnable) { + //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + p.put(STARTTLS_ENABLE, "true"); + + if (null == this.sslEnable) { + //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待 + this.sslEnable = true; + } + } + + // SSL + if (null != this.sslEnable && this.sslEnable) { + p.put(SSL_ENABLE, "true"); + p.put(SOCKET_FACTORY, socketFactoryClass); + p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback)); + p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort)); + // issue#IZN95@Gitee,在Linux下需自定义SSL协议版本 + if (StrUtil.isNotBlank(this.sslProtocols)) { + p.put(SSL_PROTOCOLS, this.sslProtocols); + } + } + + // 补充自定义属性,允许自定属性覆盖已经设置的值 + p.putAll(this.customProperty); + + return p; + } + + /** + * 如果某些值为null,使用默认值 + * + * @return this + */ + public MailAccount defaultIfEmpty() { + // 去掉发件人的姓名部分 + final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress(); + + if (StrUtil.isBlank(this.host)) { + // 如果SMTP地址为空,默认使用smtp.<发件人邮箱后缀> + this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1)); + } + if (StrUtil.isBlank(user)) { + // 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee) + //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + this.user = fromAddress; + } + if (null == this.auth) { + // 如果密码非空白,则使用认证模式 + this.auth = (false == StrUtil.isBlank(this.pass)); + } + if (null == this.port) { + // 端口在SSL状态下默认与socketFactoryPort一致,非SSL状态下默认为25 + this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25; + } + if (null == this.charset) { + // 默认UTF-8编码 + this.charset = CharsetUtil.CHARSET_UTF_8; + } + + return this; + } + + @Override + public String toString() { + return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable=" + + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]"; + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailException.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailException.java new file mode 100644 index 00000000..6099f0dc --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailException.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.Serial; + +/** + * 邮件异常 + * + * @author xiaoleilu + */ +public class MailException extends RuntimeException { + @Serial + private static final long serialVersionUID = 8247610319171014183L; + + public MailException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public MailException(String message) { + super(message); + } + + public MailException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public MailException(String message, Throwable throwable) { + super(message, throwable); + } + + public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); + } + + public MailException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailUtils.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailUtils.java new file mode 100644 index 00000000..eaa8bc0a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/MailUtils.java @@ -0,0 +1,468 @@ +package org.ruoyi.common.mail.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; +import jakarta.mail.Authenticator; +import jakarta.mail.Session; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; + +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * 邮件工具类 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MailUtils { + + private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class); + + /** + * 获取邮件发送实例 + */ + public static MailAccount getMailAccount() { + return ACCOUNT; + } + + /** + * 获取邮件发送实例 (自定义发送人以及授权码) + * + * @param user 发送人 + * @param pass 授权码 + */ + public static MailAccount getMailAccount(String from, String user, String pass) { + ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom())); + ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser())); + ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass())); + return ACCOUNT; + } + + /** + * 使用配置文件中设置的账户发送文本邮件,发送给单个或多个收件人
    + * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendText(String to, String subject, String content, File... files) { + return send(to, subject, content, false, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
    + * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(String to, String subject, String content, File... files) { + return send(to, subject, content, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
    + * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(String to, String subject, String content, boolean isHtml, File... files) { + return send(splitAddress(to), subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
    + * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) { + return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送文本邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + */ + public static String sendText(Collection tos, String subject, String content, File... files) { + return send(tos, subject, content, false, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(Collection tos, String subject, String content, File... files) { + return send(tos, subject, content, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(Collection tos, String subject, String content, boolean isHtml, File... files) { + return send(tos, null, null, subject, content, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) { + return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files); + } + + // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件认证对象 + * @param to 收件人,多个收件人逗号或者分号隔开 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, splitAddress(to), subject, content, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + */ + public static String send(MailAccount mailAccount, Collection tos, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, tos, null, null, subject, content, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, boolean isHtml, File... files) { + return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给单个或多个收件人
    + * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(String to, String subject, String content, Map imageMap, File... files) { + return send(to, subject, content, imageMap, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
    + * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(String to, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(splitAddress(to), subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人
    + * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * + * @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(String to, String cc, String bcc, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送HTML邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String sendHtml(Collection tos, String subject, String content, Map imageMap, File... files) { + return send(tos, subject, content, imageMap, true, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + */ + public static String send(Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(tos, null, null, subject, content, imageMap, isHtml, files); + } + + /** + * 使用配置文件中设置的账户发送邮件,发送给多人 + * + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML + * @param files 附件列表 + * @return message-id + * @since 4.0.3 + */ + public static String send(Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files); + } + + // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件认证对象 + * @param to 收件人,多个收件人逗号或者分号隔开 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 3.2.0 + */ + public static String send(MailAccount mailAccount, String to, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + public static String send(MailAccount mailAccount, Collection tos, String subject, String content, Map imageMap, boolean isHtml, File... files) { + return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files); + } + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + public static String send(MailAccount mailAccount, Collection tos, Collection ccs, Collection bccs, String subject, String content, Map imageMap, + boolean isHtml, File... files) { + return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files); + } + + /** + * 根据配置文件,获取邮件客户端会话 + * + * @param mailAccount 邮件账户配置 + * @param isSingleton 是否单例(全局共享会话) + * @return {@link Session} + * @since 5.5.7 + */ + public static Session getSession(MailAccount mailAccount, boolean isSingleton) { + Authenticator authenticator = null; + if (mailAccount.isAuth()) { + authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass()); + } + + return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) // + : Session.getInstance(mailAccount.getSmtpProps(), authenticator); + } + + // ------------------------------------------------------------------------------------------------------------------------ Private method start + + /** + * 发送邮件给多人 + * + * @param mailAccount 邮件帐户信息 + * @param useGlobalSession 是否全局共享Session + * @param tos 收件人列表 + * @param ccs 抄送人列表,可以为null或空 + * @param bccs 密送人列表,可以为null或空 + * @param subject 标题 + * @param content 正文 + * @param imageMap 图片与占位符,占位符格式为cid:${cid} + * @param isHtml 是否为HTML格式 + * @param files 附件列表 + * @return message-id + * @since 4.6.3 + */ + private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection tos, Collection ccs, Collection bccs, String subject, String content, + Map imageMap, boolean isHtml, File... files) { + final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession); + + // 可选抄送人 + if (CollUtil.isNotEmpty(ccs)) { + mail.setCcs(ccs.toArray(new String[0])); + } + // 可选密送人 + if (CollUtil.isNotEmpty(bccs)) { + mail.setBccs(bccs.toArray(new String[0])); + } + + mail.setTos(tos.toArray(new String[0])); + mail.setTitle(subject); + mail.setContent(content); + mail.setHtml(isHtml); + mail.setFiles(files); + + // 图片 + if (MapUtil.isNotEmpty(imageMap)) { + for (Map.Entry entry : imageMap.entrySet()) { + mail.addImage(entry.getKey(), entry.getValue()); + // 关闭流 + IoUtil.close(entry.getValue()); + } + } + + return mail.send(); + } + + /** + * 将多个联系人转为列表,分隔符为逗号或者分号 + * + * @param addresses 多个联系人,如果为空返回null + * @return 联系人列表 + */ + private static List splitAddress(String addresses) { + if (StrUtil.isBlank(addresses)) { + return null; + } + + List result; + if (StrUtil.contains(addresses, CharUtil.COMMA)) { + result = StrUtil.splitTrim(addresses, CharUtil.COMMA); + } else if (StrUtil.contains(addresses, ';')) { + result = StrUtil.splitTrim(addresses, ';'); + } else { + result = CollUtil.newArrayList(addresses); + } + return result; + } + + // ------------------------------------------------------------------------------------------------------------------------ Private method end + +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/UserPassAuthenticator.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/UserPassAuthenticator.java new file mode 100644 index 00000000..dd47dfdf --- /dev/null +++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/utils/UserPassAuthenticator.java @@ -0,0 +1,33 @@ +package org.ruoyi.common.mail.utils; + +import jakarta.mail.Authenticator; +import jakarta.mail.PasswordAuthentication; + +/** + * 用户名密码验证器 + * + * @author looly + * @since 3.1.2 + */ +public class UserPassAuthenticator extends Authenticator { + + private final String user; + private final String pass; + + /** + * 构造 + * + * @param user 用户名 + * @param pass 密码 + */ + public UserPassAuthenticator(String user, String pass) { + this.user = user; + this.pass = pass; + } + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(this.user, this.pass); + } + +} diff --git a/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index a9d39906..3d40d1b4 100644 --- a/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.mail.config.MailConfig +org.ruoyi.common.mail.config.MailConfig diff --git a/ruoyi-common/ruoyi-common-mybatis/pom.xml b/ruoyi-common/ruoyi-common-mybatis/pom.xml index 227b902e..0f6505e0 100644 --- a/ruoyi-common/ruoyi-common-mybatis/pom.xml +++ b/ruoyi-common/ruoyi-common-mybatis/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-core - com.xmzs + org.ruoyi ruoyi-common-satoken diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java new file mode 100644 index 00000000..a3419da5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.mybatis.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限 + * + * 一个注解只能对应一个模板 + * + * @author Lion Li + * @version 3.5.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataColumn { + + /** + * 占位符关键字 + */ + String[] key() default "deptName"; + + /** + * 占位符替换值 + */ + String[] value() default "dept_id"; + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java new file mode 100644 index 00000000..037eb81a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.mybatis.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限组 + * + * @author Lion Li + * @version 3.5.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + + DataColumn[] value(); + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java new file mode 100644 index 00000000..7f28b1f6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java @@ -0,0 +1,102 @@ +package org.ruoyi.common.mybatis.config; + +import cn.hutool.core.net.NetUtil; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.ruoyi.common.mybatis.handler.InjectionMetaObjectHandler; +import org.ruoyi.common.mybatis.interceptor.PlusDataPermissionInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * mybatis-plus配置类(下方注释有插件介绍) + * + * @author Lion Li + */ +@EnableTransactionManagement(proxyTargetClass = true) +@AutoConfiguration +@MapperScan("${mybatis-plus.mapperPackage}") +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 数据权限处理 + interceptor.addInnerInterceptor(dataPermissionInterceptor()); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + return interceptor; + } + + /** + * 数据权限拦截器 + */ + public PlusDataPermissionInterceptor dataPermissionInterceptor() { + return new PlusDataPermissionInterceptor(); + } + + /** + * 分页插件,自动识别数据库类型 + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + // 分页合理化 + paginationInnerInterceptor.setOverflow(true); + return paginationInnerInterceptor; + } + + /** + * 乐观锁插件 + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 元对象字段填充控制器 + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new InjectionMetaObjectHandler(); + } + + /** + * 使用网卡信息绑定雪花生成器 + * 防止集群雪花ID重复 + */ + @Bean + public IdentifierGenerator idGenerator() { + return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); + } + + /** + * PaginationInnerInterceptor 分页插件,自动识别数据库类型 + * https://baomidou.com/pages/97710a/ + * OptimisticLockerInnerInterceptor 乐观锁插件 + * https://baomidou.com/pages/0d93c0/ + * MetaObjectHandler 元对象字段填充控制器 + * https://baomidou.com/pages/4c6bcf/ + * ISqlInjector sql注入器 + * https://baomidou.com/pages/42ea4a/ + * BlockAttackInnerInterceptor 如果是对全表的删除或更新操作,就会终止该操作 + * https://baomidou.com/pages/f9a237/ + * IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截) + * IdentifierGenerator 自定义主键策略 + * https://baomidou.com/pages/568eb2/ + * TenantLineInnerInterceptor 多租户插件 + * https://baomidou.com/pages/aef2f2/ + * DynamicTableNameInnerInterceptor 动态表名插件 + * https://baomidou.com/pages/2a45ff/ + */ + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java new file mode 100644 index 00000000..bfc43c54 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java @@ -0,0 +1,71 @@ +package org.ruoyi.common.mybatis.core.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Entity基类 + * + * @author Lion Li + */ + +@Data +public class BaseEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 搜索值 + */ + @JsonIgnore + @TableField(exist = false) + private String searchValue; + + /** + * 创建部门 + */ + @TableField(fill = FieldFill.INSERT) + private Long createDept; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private Long createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 更新者 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** + * 请求参数 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @TableField(exist = false) + private Map params = new HashMap<>(); + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java new file mode 100644 index 00000000..b14c14ef --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java @@ -0,0 +1,198 @@ +package org.ruoyi.common.mybatis.core.mapper; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.ruoyi.common.core.utils.MapstructUtils; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 自定义 Mapper 接口, 实现 自定义扩展 + * + * @param table 泛型 + * @param vo 泛型 + * @author Lion Li + * @since 2021-05-13 + */ +@SuppressWarnings("unchecked") +public interface BaseMapperPlus extends BaseMapper { + + Log log = LogFactory.getLog(BaseMapperPlus.class); + + default Class currentVoClass() { + return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1); + } + + default Class currentModelClass() { + return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0); + } + + default List selectList() { + return this.selectList(new QueryWrapper<>()); + } + + /** + * 批量插入 + */ + default boolean insertBatch(Collection entityList) { + return Db.saveBatch(entityList); + } + + /** + * 批量更新 + */ + default boolean updateBatchById(Collection entityList) { + return Db.updateBatchById(entityList); + } + + /** + * 批量插入或更新 + */ + default boolean insertOrUpdateBatch(Collection entityList) { + return Db.saveOrUpdateBatch(entityList); + } + + /** + * 批量插入(包含限制条数) + */ + default boolean insertBatch(Collection entityList, int batchSize) { + return Db.saveBatch(entityList, batchSize); + } + + /** + * 批量更新(包含限制条数) + */ + default boolean updateBatchById(Collection entityList, int batchSize) { + return Db.updateBatchById(entityList, batchSize); + } + + /** + * 批量插入或更新(包含限制条数) + */ + default boolean insertOrUpdateBatch(Collection entityList, int batchSize) { + return Db.saveOrUpdateBatch(entityList, batchSize); + } + + /** + * 插入或更新(包含限制条数) + */ + default boolean insertOrUpdate(T entity) { + return Db.saveOrUpdate(entity); + } + + default V selectVoById(Serializable id) { + return selectVoById(id, this.currentVoClass()); + } + + /** + * 根据 ID 查询 + */ + default C selectVoById(Serializable id, Class voClass) { + T obj = this.selectById(id); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + default List selectVoBatchIds(Collection idList) { + return selectVoBatchIds(idList, this.currentVoClass()); + } + + /** + * 查询(根据ID 批量查询) + */ + default List selectVoBatchIds(Collection idList, Class voClass) { + List list = this.selectBatchIds(idList); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default List selectVoByMap(Map map) { + return selectVoByMap(map, this.currentVoClass()); + } + + /** + * 查询(根据 columnMap 条件) + */ + default List selectVoByMap(Map map, Class voClass) { + List list = this.selectByMap(map); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default V selectVoOne(Wrapper wrapper) { + return selectVoOne(wrapper, this.currentVoClass()); + } + + /** + * 根据 entity 条件,查询一条记录 + */ + default C selectVoOne(Wrapper wrapper, Class voClass) { + T obj = this.selectOne(wrapper); + if (ObjectUtil.isNull(obj)) { + return null; + } + return MapstructUtils.convert(obj, voClass); + } + + default List selectVoList() { + return selectVoList(new QueryWrapper<>(), this.currentVoClass()); + } + + default List selectVoList(Wrapper wrapper) { + return selectVoList(wrapper, this.currentVoClass()); + } + + /** + * 根据 entity 条件,查询全部记录 + */ + default List selectVoList(Wrapper wrapper, Class voClass) { + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return MapstructUtils.convert(list, voClass); + } + + default

    > P selectVoPage(IPage page, Wrapper wrapper) { + return selectVoPage(page, wrapper, this.currentVoClass()); + } + + /** + * 分页查询VO + */ + default > P selectVoPage(IPage page, Wrapper wrapper, Class voClass) { + IPage pageData = this.selectPage(page, wrapper); + IPage voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal()); + if (CollUtil.isEmpty(pageData.getRecords())) { + return (P) voPage; + } + voPage.setRecords(MapstructUtils.convert(pageData.getRecords(), voClass)); + return (P) voPage; + } + + default List selectObjs(Wrapper wrapper, Function mapper) { + return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList()); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java new file mode 100644 index 00000000..33e0190d --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java @@ -0,0 +1,114 @@ +package org.ruoyi.common.mybatis.core.page; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.Data; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.sql.SqlUtil; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 分页查询实体类 + * + * @author Lion Li + */ + +@Data +public class PageQuery implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 分页大小 + */ + private Integer pageSize; + + /** + * 当前页数 + */ + private Integer pageNum; + + /** + * 排序列 + */ + private String orderByColumn; + + /** + * 排序的方向desc或者asc + */ + private String isAsc; + + /** + * 当前记录起始索引 默认值 + */ + public static final int DEFAULT_PAGE_NUM = 1; + + /** + * 每页显示记录数 默认值 默认查全部 + */ + public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE; + + public Page build() { + Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); + Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); + if (pageNum <= 0) { + pageNum = DEFAULT_PAGE_NUM; + } + Page page = new Page<>(pageNum, pageSize); + List orderItems = buildOrderItem(); + if (CollUtil.isNotEmpty(orderItems)) { + page.addOrder(orderItems); + } + return page; + } + + /** + * 构建排序 + * + * 支持的用法如下: + * {isAsc:"asc",orderByColumn:"id"} order by id asc + * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc + * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc + * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc + */ + private List buildOrderItem() { + if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) { + return null; + } + String orderBy = SqlUtil.escapeOrderBySql(orderByColumn); + orderBy = StringUtils.toUnderScoreCase(orderBy); + + // 兼容前端排序类型 + isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); + + String[] orderByArr = orderBy.split(StringUtils.SEPARATOR); + String[] isAscArr = isAsc.split(StringUtils.SEPARATOR); + if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { + throw new ServiceException("排序参数有误"); + } + + List list = new ArrayList<>(); + // 每个字段各自排序 + for (int i = 0; i < orderByArr.length; i++) { + String orderByStr = orderByArr[i]; + String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i]; + if ("asc".equals(isAscStr)) { + list.add(OrderItem.asc(orderByStr)); + } else if ("desc".equals(isAscStr)) { + list.add(OrderItem.desc(orderByStr)); + } else { + throw new ServiceException("排序参数有误"); + } + } + return list; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java new file mode 100644 index 00000000..b4f7cd70 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java @@ -0,0 +1,81 @@ +package org.ruoyi.common.mybatis.core.page; + +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +public class TableDataInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 总记录数 + */ + private long total; + + /** + * 列表数据 + */ + private List rows; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg; + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, long total) { + this.rows = list; + this.total = total; + } + + public static TableDataInfo build(IPage page) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(page.getRecords()); + rspData.setTotal(page.getTotal()); + return rspData; + } + + public static TableDataInfo build(List list) { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(list.size()); + return rspData; + } + + public static TableDataInfo build() { + TableDataInfo rspData = new TableDataInfo<>(); + rspData.setCode(HttpStatus.HTTP_OK); + rspData.setMsg("查询成功"); + return rspData; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java new file mode 100644 index 00000000..fba59340 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java @@ -0,0 +1,49 @@ +package org.ruoyi.common.mybatis.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * 数据库类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum DataBaseType { + + /** + * MySQL + */ + MY_SQL("MySQL"), + + /** + * Oracle + */ + ORACLE("Oracle"), + + /** + * PostgreSQL + */ + POSTGRE_SQL("PostgreSQL"), + + /** + * SQL Server + */ + SQL_SERVER("Microsoft SQL Server"); + + private final String type; + + public static DataBaseType find(String databaseProductName) { + if (StringUtils.isBlank(databaseProductName)) { + return null; + } + for (DataBaseType type : values()) { + if (type.getType().equals(databaseProductName)) { + return type; + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java new file mode 100644 index 00000000..65362ad0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java @@ -0,0 +1,73 @@ +package org.ruoyi.common.mybatis.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.helper.DataPermissionHelper; + +/** + * 数据权限类型 + *

    + * 语法支持 spel 模板表达式 + *

    + * 内置数据 user 当前用户 内容参考 LoginUser + * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作 + * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService + * 如需扩展更多自定义服务 可以参考 sdss 自行编写 + * + * @author Lion Li + * @version 3.5.0 + */ +@Getter +@AllArgsConstructor +public enum DataScopeType { + + /** + * 全部数据权限 + */ + ALL("1", "", ""), + + /** + * 自定数据权限 + */ + CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""), + + /** + * 部门数据权限 + */ + DEPT("3", " #{#deptName} = #{#user.deptId} ", ""), + + /** + * 部门及以下数据权限 + */ + DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""), + + /** + * 仅本人数据权限 + */ + SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "); + + private final String code; + + /** + * 语法 采用 spel 模板表达式 + */ + private final String sqlTemplate; + + /** + * 不满足 sqlTemplate 则填充 + */ + private final String elseSql; + + public static DataScopeType findCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + for (DataScopeType type : values()) { + if (type.getCode().equals(code)) { + return type; + } + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java new file mode 100644 index 00000000..dfa24f0b --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java @@ -0,0 +1,81 @@ +package org.ruoyi.common.mybatis.handler; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.common.satoken.utils.LoginHelper; + +import java.util.Date; + +/** + * MP注入处理器 + * + * @author Lion Li + * @date 2021/4/25 + */ +@Slf4j +public class InjectionMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) + ? baseEntity.getCreateTime() : new Date(); + baseEntity.setCreateTime(current); + baseEntity.setUpdateTime(current); + LoginUser loginUser = getLoginUser(); + if (ObjectUtil.isNotNull(loginUser)) { + Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy()) + ? baseEntity.getCreateBy() : loginUser.getUserId(); + // 当前已登录 且 创建人为空 则填充 + baseEntity.setCreateBy(userId); + // 当前已登录 且 更新人为空 则填充 + baseEntity.setUpdateBy(userId); + baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept()) + ? baseEntity.getCreateDept() : loginUser.getDeptId()); + } + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + @Override + public void updateFill(MetaObject metaObject) { + try { + if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { + Date current = new Date(); + // 更新时间填充(不管为不为空) + baseEntity.setUpdateTime(current); + LoginUser loginUser = getLoginUser(); + // 当前已登录 更新人填充(不管为不为空) + if (ObjectUtil.isNotNull(loginUser)) { + baseEntity.setUpdateBy(loginUser.getUserId()); + } + } + } catch (Exception e) { + throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); + } + } + + /** + * 获取登录用户名 + */ + private LoginUser getLoginUser() { + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + } catch (Exception e) { + log.warn("自动注入警告 => 用户未登录"); + return null; + } + return loginUser; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java new file mode 100644 index 00000000..281d172d --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.mybatis.handler; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.mybatis.spring.MyBatisSystemException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * Mybatis异常处理器 + * + * @author Lion Li + */ +@Slf4j +@RestControllerAdvice +public class MybatisExceptionHandler { + + /** + * 主键或UNIQUE索引,数据重复异常 + */ + @ExceptionHandler(DuplicateKeyException.class) + public R handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage()); + return R.fail("数据库中已存在该记录,请联系管理员确认"); + } + + /** + * Mybatis系统异常 通用处理 + */ + @ExceptionHandler(MyBatisSystemException.class) + public R handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String message = e.getMessage(); + if (message.contains("CannotFindDataSourceException")) { + log.error("请求地址'{}', 未找到数据源", requestURI); + return R.fail("未找到数据源,请联系管理员确认"); + } + log.error("请求地址'{}', Mybatis系统异常", requestURI, e); + return R.fail(message); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java new file mode 100644 index 00000000..f928f42a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java @@ -0,0 +1,198 @@ +package org.ruoyi.common.mybatis.handler; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ConcurrentHashSet; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.ruoyi.common.core.domain.dto.RoleDTO; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.enums.DataScopeType; +import org.ruoyi.common.mybatis.helper.DataPermissionHelper; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * 数据权限过滤 + * + * @author Lion Li + * @version 3.5.0 + */ +@Slf4j +public class PlusDataPermissionHandler { + + /** + * 方法或类(名称) 与 注解的映射关系缓存 + */ + private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); + + /** + * 无效注解方法缓存用于快速返回 + */ + private final Set invalidCacheSet = new ConcurrentHashSet<>(); + + /** + * spel 解析器 + */ + private final ExpressionParser parser = new SpelExpressionParser(); + private final ParserContext parserContext = new TemplateParserContext(); + /** + * bean解析器 用于处理 spel 表达式中对 bean 的调用 + */ + private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); + + + public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { + DataColumn[] dataColumns = findAnnotation(mappedStatementId); + if (ArrayUtil.isEmpty(dataColumns)) { + invalidCacheSet.add(mappedStatementId); + return where; + } + LoginUser currentUser = DataPermissionHelper.getVariable("user"); + if (ObjectUtil.isNull(currentUser)) { + currentUser = LoginHelper.getLoginUser(); + DataPermissionHelper.setVariable("user", currentUser); + } + // 如果是超级管理员或租户管理员,则不过滤数据 + if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { + return where; + } + String dataFilterSql = buildDataFilter(dataColumns, isSelect); + if (StringUtils.isBlank(dataFilterSql)) { + return where; + } + try { + Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); + // 数据权限使用单独的括号 防止与其他条件冲突 + Parenthesis parenthesis = new Parenthesis(expression); + if (ObjectUtil.isNotNull(where)) { + return new AndExpression(where, parenthesis); + } else { + return parenthesis; + } + } catch (JSQLParserException e) { + throw new ServiceException("数据权限解析异常 => " + e.getMessage()); + } + } + + /** + * 构造数据过滤sql + */ + private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) { + // 更新或删除需满足所有条件 + String joinStr = isSelect ? " OR " : " AND "; + LoginUser user = DataPermissionHelper.getVariable("user"); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(beanResolver); + DataPermissionHelper.getContext().forEach(context::setVariable); + Set conditions = new HashSet<>(); + for (RoleDTO role : user.getRoles()) { + user.setRoleId(role.getRoleId()); + // 获取角色权限泛型 + DataScopeType type = DataScopeType.findCode(role.getDataScope()); + if (ObjectUtil.isNull(type)) { + throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); + } + // 全部数据权限直接返回 + if (type == DataScopeType.ALL) { + return ""; + } + boolean isSuccess = false; + for (DataColumn dataColumn : dataColumns) { + if (dataColumn.key().length != dataColumn.value().length) { + throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); + } + // 不包含 key 变量 则不处理 + if (!StringUtils.containsAny(type.getSqlTemplate(), + Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new) + )) { + continue; + } + // 设置注解变量 key 为表达式变量 value 为变量值 + for (int i = 0; i < dataColumn.key().length; i++) { + context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); + } + + // 解析sql模板并填充 + String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class); + conditions.add(joinStr + sql); + isSuccess = true; + } + // 未处理成功则填充兜底方案 + if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { + conditions.add(joinStr + type.getElseSql()); + } + } + + if (CollUtil.isNotEmpty(conditions)) { + String sql = StreamUtils.join(conditions, Function.identity(), ""); + return sql.substring(joinStr.length()); + } + return ""; + } + + private DataColumn[] findAnnotation(String mappedStatementId) { + StringBuilder sb = new StringBuilder(mappedStatementId); + int index = sb.lastIndexOf("."); + String clazzName = sb.substring(0, index); + String methodName = sb.substring(index + 1, sb.length()); + Class clazz = ClassUtil.loadClass(clazzName); + List methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz)) + .filter(method -> method.getName().equals(methodName)).toList(); + DataPermission dataPermission; + // 获取方法注解 + for (Method method : methods) { + dataPermission = dataPermissionCacheMap.get(mappedStatementId); + if (ObjectUtil.isNotNull(dataPermission)) { + return dataPermission.value(); + } + if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); + dataPermissionCacheMap.put(mappedStatementId, dataPermission); + return dataPermission.value(); + } + } + dataPermission = dataPermissionCacheMap.get(clazz.getName()); + if (ObjectUtil.isNotNull(dataPermission)) { + return dataPermission.value(); + } + // 获取类注解 + if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { + dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); + dataPermissionCacheMap.put(clazz.getName(), dataPermission); + return dataPermission.value(); + } + return null; + } + + /** + * 是否为无效方法 无数据权限 + */ + public boolean isInvalid(String mappedStatementId) { + return invalidCacheSet.contains(mappedStatementId); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java new file mode 100644 index 00000000..1e589136 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java @@ -0,0 +1,72 @@ +package org.ruoyi.common.mybatis.helper; + +import cn.hutool.core.convert.Convert; +import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.mybatis.enums.DataBaseType; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +/** + * 数据库助手 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DataBaseHelper { + + private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); + + /** + * 获取当前数据库类型 + */ + public static DataBaseType getDataBaseType() { + DataSource dataSource = DS.determineDataSource(); + try (Connection conn = dataSource.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + String databaseProductName = metaData.getDatabaseProductName(); + return DataBaseType.find(databaseProductName); + } catch (SQLException e) { + throw new ServiceException(e.getMessage()); + } + } + + public static boolean isMySql() { + return DataBaseType.MY_SQL == getDataBaseType(); + } + + public static boolean isOracle() { + return DataBaseType.ORACLE == getDataBaseType(); + } + + public static boolean isPostgerSql() { + return DataBaseType.POSTGRE_SQL == getDataBaseType(); + } + + public static boolean isSqlServer() { + return DataBaseType.SQL_SERVER == getDataBaseType(); + } + + public static String findInSet(Object var1, String var2) { + DataBaseType dataBasyType = getDataBaseType(); + String var = Convert.toStr(var1); + if (dataBasyType == DataBaseType.SQL_SERVER) { + // charindex(',100,' , ',0,100,101,') <> 0 + return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); + } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { + // (select position(',100,' in ',0,100,101,')) <> 0 + return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2); + } else if (dataBasyType == DataBaseType.ORACLE) { + // instr(',0,100,101,' , ',100,') <> 0 + return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); + } + // find_in_set(100 , '0,100,101') + return "find_in_set('%s' , %s) <> 0".formatted(var, var2); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java new file mode 100644 index 00000000..8e3de7a3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java @@ -0,0 +1,93 @@ +package org.ruoyi.common.mybatis.helper; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * 数据权限助手 + * + * @author Lion Li + * @version 3.5.0 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings("unchecked cast") +public class DataPermissionHelper { + + private static final String DATA_PERMISSION_KEY = "data:permission"; + + public static T getVariable(String key) { + Map context = getContext(); + return (T) context.get(key); + } + + + public static void setVariable(String key, Object value) { + Map context = getContext(); + context.put(key, value); + } + + public static Map getContext() { + SaStorage saStorage = SaHolder.getStorage(); + Object attribute = saStorage.get(DATA_PERMISSION_KEY); + if (ObjectUtil.isNull(attribute)) { + saStorage.set(DATA_PERMISSION_KEY, new HashMap<>()); + attribute = saStorage.get(DATA_PERMISSION_KEY); + } + if (attribute instanceof Map map) { + return map; + } + throw new NullPointerException("data permission context type exception"); + } + + /** + * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) + */ + public static void enableIgnore() { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); + } + + /** + * 关闭忽略数据权限 + */ + public static void disableIgnore() { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static void ignore(Runnable handle) { + enableIgnore(); + try { + handle.run(); + } finally { + disableIgnore(); + } + } + + /** + * 在忽略数据权限中执行 + * + * @param handle 处理执行方法 + */ + public static T ignore(Supplier handle) { + enableIgnore(); + try { + return handle.get(); + } finally { + disableIgnore(); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java new file mode 100644 index 00000000..98895d0a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java @@ -0,0 +1,107 @@ +package org.ruoyi.common.mybatis.interceptor; + +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SetOperationList; +import net.sf.jsqlparser.statement.update.Update; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.ruoyi.common.mybatis.handler.PlusDataPermissionHandler; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +/** + * 数据权限拦截器 + * + * @author Lion Li + * @version 3.5.0 + */ +public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor { + + private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); + + @Override + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { + // 检查忽略注解 + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + // 检查是否无效 无数据权限注解 + if (dataPermissionHandler.isInvalid(ms.getId())) { + return; + } + // 解析 sql 分配对应方法 + PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); + mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); + } + + @Override + public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { + PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); + MappedStatement ms = mpSh.mappedStatement(); + SqlCommandType sct = ms.getSqlCommandType(); + if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { + if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { + return; + } + PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); + mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); + } + } + + @Override + protected void processSelect(Select select, int index, String sql, Object obj) { + SelectBody selectBody = select.getSelectBody(); + if (selectBody instanceof PlainSelect plainSelect) { + this.setWhere(plainSelect, (String) obj); + } else if (selectBody instanceof SetOperationList setOperationList) { + List selectBodyList = setOperationList.getSelects(); + selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); + } + } + + @Override + protected void processUpdate(Update update, int index, String sql, Object obj) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); + if (null != sqlSegment) { + update.setWhere(sqlSegment); + } + } + + @Override + protected void processDelete(Delete delete, int index, String sql, Object obj) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); + if (null != sqlSegment) { + delete.setWhere(sqlSegment); + } + } + + /** + * 设置 where 条件 + * + * @param plainSelect 查询对象 + * @param mappedStatementId 执行方法id + */ + protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { + Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); + if (null != sqlSegment) { + plainSelect.setWhere(sqlSegment); + } + } + +} + diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java new file mode 100644 index 00000000..fd67e07c --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2018 organization baomidou + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ruoyi.common.mybatis.jakarta; + +import com.baomidou.dynamic.datasource.processor.DsProcessor; +import jakarta.servlet.http.HttpServletRequest; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * @author TaoYu + * @since 3.6.0 + */ +public class DsJakartaHeaderProcessor extends DsProcessor { + + /** + * header prefix + */ + private static final String HEADER_PREFIX = "#header"; + + @Override + public boolean matches(String key) { + return key.startsWith(HEADER_PREFIX); + } + + @Override + public String doDetermineDatasource(MethodInvocation invocation, String key) { + HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + return request.getHeader(key.substring(8)); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java new file mode 100644 index 00000000..f055b463 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright © 2018 organization baomidou + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ruoyi.common.mybatis.jakarta; + +import com.baomidou.dynamic.datasource.processor.DsProcessor; +import jakarta.servlet.http.HttpServletRequest; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + + +/** + * @author TaoYu + * @since 3.6.0 + */ +public class DsJakartaSessionProcessor extends DsProcessor { + + /** + * session开头 + */ + private static final String SESSION_PREFIX = "#session"; + + @Override + public boolean matches(String key) { + return key.startsWith(SESSION_PREFIX); + } + + @Override + public String doDetermineDatasource(MethodInvocation invocation, String key) { + HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + return request.getSession().getAttribute(key.substring(9)).toString(); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 70d6416b..24c62b3a 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.mybatis.config.MybatisPlusConfig +org.ruoyi.common.mybatis.config.MybatisPlusConfig diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml index c5b0e7c8..716b1fad 100644 --- a/ruoyi-common/ruoyi-common-oss/pom.xml +++ b/ruoyi-common/ruoyi-common-oss/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-json - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/constant/OssConstant.java new file mode 100644 index 00000000..f79a5a63 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/constant/OssConstant.java @@ -0,0 +1,38 @@ +package org.ruoyi.common.oss.constant; + +import java.util.Arrays; +import java.util.List; + +/** + * 对象存储常量 + * + * @author Lion Li + */ +public interface OssConstant { + + /** + * 默认配置KEY + */ + String DEFAULT_CONFIG_KEY = "sys_oss:default_config"; + + /** + * 预览列表资源开关Key + */ + String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource"; + + /** + * 系统数据ids + */ + List SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L); + + /** + * 云服务商 + */ + String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; + + /** + * https 状态 + */ + String IS_HTTPS = "Y"; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java new file mode 100644 index 00000000..f9d4d09f --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/core/OssClient.java @@ -0,0 +1,245 @@ +package org.ruoyi.common.oss.core; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.HttpMethod; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; +import org.ruoyi.common.core.utils.DateUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.oss.constant.OssConstant; +import org.ruoyi.common.oss.entity.UploadResult; +import org.ruoyi.common.oss.enumd.AccessPolicyType; +import org.ruoyi.common.oss.enumd.PolicyType; +import org.ruoyi.common.oss.exception.OssException; +import org.ruoyi.common.oss.properties.OssProperties; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; + +/** + * S3 存储协议 所有兼容S3协议的云厂商均支持 + * 阿里云 腾讯云 七牛云 minio + * + * @author Lion Li + */ +public class OssClient { + + private final String configKey; + + private final OssProperties properties; + + private final AmazonS3 client; + + public OssClient(String configKey, OssProperties ossProperties) { + this.configKey = configKey; + this.properties = ossProperties; + try { + AwsClientBuilder.EndpointConfiguration endpointConfig = + new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); + + AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); + AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); + ClientConfiguration clientConfig = new ClientConfiguration(); + if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { + clientConfig.setProtocol(Protocol.HTTPS); + } else { + clientConfig.setProtocol(Protocol.HTTP); + } + AmazonS3ClientBuilder build = AmazonS3Client.builder() + .withEndpointConfiguration(endpointConfig) + .withClientConfiguration(clientConfig) + .withCredentials(credentialsProvider) + .disableChunkedEncoding(); + if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { + // minio 使用https限制使用域名访问 需要此配置 站点填域名 + build.enablePathStyleAccess(); + } + this.client = build.build(); + + createBucket(); + } catch (Exception e) { + if (e instanceof OssException) { + throw e; + } + throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); + } + } + + public void createBucket() { + try { + String bucketName = properties.getBucketName(); + if (client.doesBucketExistV2(bucketName)) { + return; + } + CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); + AccessPolicyType accessPolicy = getAccessPolicy(); + createBucketRequest.setCannedAcl(accessPolicy.getAcl()); + client.createBucket(createBucketRequest); + client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); + } catch (Exception e) { + throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult upload(byte[] data, String path, String contentType) { + return upload(new ByteArrayInputStream(data), path, contentType); + } + + public UploadResult upload(InputStream inputStream, String path, String contentType) { + if (!(inputStream instanceof ByteArrayInputStream)) { + inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); + } + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(contentType); + metadata.setContentLength(inputStream.available()); + PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); + // 设置上传对象的 Acl 为公共读 + putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); + client.putObject(putObjectRequest); + } catch (Exception e) { + throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); + } + + public void delete(String path) { + path = path.replace(getUrl() + "/", ""); + try { + client.deleteObject(properties.getBucketName(), path); + } catch (Exception e) { + throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { + return upload(data, getPath(properties.getPrefix(), suffix), contentType); + } + + public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { + return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); + } + + /** + * 获取文件元数据 + * + * @param path 完整文件路径 + */ + public ObjectMetadata getObjectMetadata(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectMetadata(); + } + + public InputStream getObjectContent(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectContent(); + } + + public String getUrl() { + String domain = properties.getDomain(); + String endpoint = properties.getEndpoint(); + String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; + // 云服务商直接返回 + if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { + if (StringUtils.isNotBlank(domain)) { + return header + domain; + } + return header + properties.getBucketName() + "." + endpoint; + } + // minio 单独处理 + if (StringUtils.isNotBlank(domain)) { + return header + domain + "/" + properties.getBucketName(); + } + return header + endpoint + "/" + properties.getBucketName(); + } + + public String getPath(String prefix, String suffix) { + // 生成uuid + String uuid = IdUtil.fastSimpleUUID(); + // 文件路径 + String path = DateUtils.datePath() + "/" + uuid; + if (StringUtils.isNotBlank(prefix)) { + path = prefix + "/" + path; + } + return path + suffix; + } + + + public String getConfigKey() { + return configKey; + } + + /** + * 获取私有URL链接 + * + * @param objectKey 对象KEY + * @param second 授权时间 + */ + public String getPrivateUrl(String objectKey, Integer second) { + GeneratePresignedUrlRequest generatePresignedUrlRequest = + new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) + .withMethod(HttpMethod.GET) + .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); + URL url = client.generatePresignedUrl(generatePresignedUrlRequest); + return url.toString(); + } + + /** + * 检查配置是否相同 + */ + public boolean checkPropertiesSame(OssProperties properties) { + return this.properties.equals(properties); + } + + /** + * 获取当前桶权限类型 + * + * @return 当前桶权限类型code + */ + public AccessPolicyType getAccessPolicy() { + return AccessPolicyType.getByType(properties.getAccessPolicy()); + } + + private static String getPolicy(String bucketName, PolicyType policyType) { + StringBuilder builder = new StringBuilder(); + builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); + builder.append(switch (policyType) { + case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"; + case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"; + default -> "\"s3:GetBucketLocation\"\n"; + }); + builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + if (policyType == PolicyType.READ) { + builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + } + builder.append("{\n\"Action\": "); + builder.append(switch (policyType) { + case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + default -> "\"s3:GetObject\",\n"; + }); + builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); + return builder.toString(); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/entity/UploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/entity/UploadResult.java new file mode 100644 index 00000000..a13ebf6a --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/entity/UploadResult.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传返回体 + * + * @author Lion Li + */ +@Data +@Builder +public class UploadResult { + + /** + * 文件路径 + */ + private String url; + + /** + * 文件名 + */ + private String filename; +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/AccessPolicyType.java new file mode 100644 index 00000000..cd440696 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/AccessPolicyType.java @@ -0,0 +1,55 @@ +package org.ruoyi.common.oss.enumd; + +import com.amazonaws.services.s3.model.CannedAccessControlList; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 桶访问策略配置 + * + * @author 陈賝 + */ +@Getter +@AllArgsConstructor +public enum AccessPolicyType { + + /** + * private + */ + PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE), + + /** + * public + */ + PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ), + + /** + * custom + */ + CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ); + + /** + * 桶 权限类型 + */ + private final String type; + + /** + * 文件对象 权限类型 + */ + private final CannedAccessControlList acl; + + /** + * 桶策略类型 + */ + private final PolicyType policyType; + + public static AccessPolicyType getByType(String type) { + for (AccessPolicyType value : values()) { + if (value.getType().equals(type)) { + return value; + } + } + throw new RuntimeException("'type' not found By " + type); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/PolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/PolicyType.java new file mode 100644 index 00000000..bc27ec27 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/enumd/PolicyType.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.oss.enumd; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * minio策略配置 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum PolicyType { + + /** + * 只读 + */ + READ("read-only"), + + /** + * 只写 + */ + WRITE("write-only"), + + /** + * 读写 + */ + READ_WRITE("read-write"); + + /** + * 类型 + */ + private final String type; + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/exception/OssException.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/exception/OssException.java new file mode 100644 index 00000000..45f49b52 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/exception/OssException.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.oss.exception; + +import java.io.Serial; + +/** + * OSS异常类 + * + * @author Lion Li + */ +public class OssException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public OssException(String msg) { + super(msg); + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/factory/OssFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/factory/OssFactory.java new file mode 100644 index 00000000..f9fb072f --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/factory/OssFactory.java @@ -0,0 +1,63 @@ +package org.ruoyi.common.oss.factory; + +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.oss.constant.OssConstant; +import org.ruoyi.common.oss.core.OssClient; +import org.ruoyi.common.oss.exception.OssException; +import org.ruoyi.common.oss.properties.OssProperties; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.common.redis.utils.RedisUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文件上传Factory + * + * @author Lion Li + */ +@Slf4j +public class OssFactory { + + private static final Map CLIENT_CACHE = new ConcurrentHashMap<>(); + + /** + * 获取默认实例 + */ + public static OssClient instance() { + // 获取redis 默认类型 + String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY); + if (StringUtils.isEmpty(configKey)) { + throw new OssException("文件存储服务类型无法找到!"); + } + return instance(configKey); + } + + /** + * 根据类型获取实例 + */ + public static OssClient instance(String configKey) { + String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey); + if (json == null) { + throw new OssException("系统异常, '" + configKey + "'配置信息不存在!"); + } + OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); + OssClient client = CLIENT_CACHE.get(configKey); + if (client == null) { + CLIENT_CACHE.put(configKey, new OssClient(configKey, properties)); + log.info("创建OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(configKey); + } + // 配置不相同则重新构建 + if (!client.checkPropertiesSame(properties)) { + CLIENT_CACHE.put(configKey, new OssClient(configKey, properties)); + log.info("重载OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(configKey); + } + return client; + } + +} diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/properties/OssProperties.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/properties/OssProperties.java new file mode 100644 index 00000000..e8e296b8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/ruoyi/common/oss/properties/OssProperties.java @@ -0,0 +1,58 @@ +package org.ruoyi.common.oss.properties; + +import lombok.Data; + +/** + * OSS对象存储 配置属性 + * + * @author Lion Li + */ +@Data +public class OssProperties { + + /** + * 访问站点 + */ + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 前缀 + */ + private String prefix; + + /** + * ACCESS_KEY + */ + private String accessKey; + + /** + * SECRET_KEY + */ + private String secretKey; + + /** + * 存储空间名 + */ + private String bucketName; + + /** + * 存储区域 + */ + private String region; + + /** + * 是否https(Y=是,N=否) + */ + private String isHttps; + + /** + * 桶权限类型(0private 1public 2custom) + */ + private String accessPolicy; + +} diff --git a/ruoyi-common/ruoyi-common-pay/pom.xml b/ruoyi-common/ruoyi-common-pay/pom.xml index ff90424f..036991ac 100644 --- a/ruoyi-common/ruoyi-common-pay/pom.xml +++ b/ruoyi-common/ruoyi-common-pay/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -13,7 +13,7 @@ ruoyi-common-pay - com.xmzs + org.ruoyi ruoyi-common-web @@ -26,6 +26,13 @@ spring-boot-starter-test test + + + com.stripe + stripe-java + 22.5.1 + + cn.hutool hutool-all diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayConfig.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayConfig.java new file mode 100644 index 00000000..a5c92c3b --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayConfig.java @@ -0,0 +1,53 @@ +package org.ruoyi.common.config; + +import lombok.Data; + +/** + * 支付配置信息 + * + * @author Admin + */ +@Data +public class PayConfig { + + /** + * 商户ID + */ + private String pid; + + /** + * 接口地址 + */ + private String payUrl; + + /** + * 私钥 + */ + private String key ; + + /** + * 服务器异步通知地址 + */ + private String notify_url; + + /** + * 页面跳转通知地址 + */ + private String return_url; + + /** + * 支付方式 + */ + private String type; + + /** + * 设备类型 + */ + private String device; + + /** + * 加密方式默认MD5 + */ + private String sign_type; + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayInit.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayInit.java new file mode 100644 index 00000000..47126cef --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayInit.java @@ -0,0 +1,51 @@ +package org.ruoyi.common.config; + +import org.ruoyi.common.core.service.ConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * 支付配置信息 + * + * @author Admin + */ +@RequiredArgsConstructor +@Configuration +@Slf4j +public class PayInit { + + private final ConfigService configService; + + private PayConfig payConfig; + + @Bean + @Scope("singleton") + public PayConfig payConfig() { + if (payConfig == null) { + payConfig = new PayConfig(); + updatePayConfig(); + } + return payConfig; + } + + @Scheduled(fixedDelay = 10000) // 每10秒检查一次 + public void updatePayConfig() { + payConfig.setType("wxpay"); + payConfig.setDevice("pc"); + payConfig.setSign_type("MD5"); + payConfig.setPid(getKey("pid")); + payConfig.setKey(getKey("key")); + payConfig.setPayUrl(getKey("payUrl")); + payConfig.setNotify_url(getKey("notify_url")); + payConfig.setReturn_url(getKey("return_url")); + } + + public String getKey(String key){ + return configService.getConfigValue("pay", key); + } + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/WxPayConfiguration.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/WxPayConfiguration.java new file mode 100644 index 00000000..cbe33cd0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/WxPayConfiguration.java @@ -0,0 +1,47 @@ +package org.ruoyi.common.config; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; + +import org.ruoyi.common.core.service.ConfigService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Binary Wang + */ +@Configuration +@RequiredArgsConstructor +public class WxPayConfiguration { + + private final ConfigService configService; + + private WxPayService wxPayService; + + @PostConstruct + public void init() { + WxPayConfig payConfig = new WxPayConfig(); + payConfig.setAppId(StringUtils.trimToNull(getKey("appId"))); + payConfig.setMchId(StringUtils.trimToNull(getKey("mchId"))); + payConfig.setMchKey(StringUtils.trimToNull(getKey("appSecret"))); + payConfig.setUseSandboxEnv(false); + wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(payConfig); + } + + @Bean + @ConditionalOnMissingBean + public WxPayService wxService() { + return wxPayService; + } + + public String getKey(String key) { + return configService.getConfigValue("weixin", key); + } + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/listener/ConfigChangeListener.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/listener/ConfigChangeListener.java new file mode 100644 index 00000000..b6b4fec8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/listener/ConfigChangeListener.java @@ -0,0 +1,37 @@ +package org.ruoyi.common.listener; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import org.ruoyi.common.core.event.ConfigChangeEvent; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * 描述:创建一个监听器,用于处理配置变化事件 + * + * @author ageerle@163.com + * date 2024/5/19 + */ +@Component +public class ConfigChangeListener implements ApplicationListener { + private final WxPayService wxPayService; + private final ConfigService configService; + + public ConfigChangeListener(WxPayService wxPayService, ConfigService configService) { + this.wxPayService = wxPayService; + this.configService = configService; + } + + @Override + public void onApplicationEvent(@NotNull ConfigChangeEvent event) { + WxPayConfig newConfig = new WxPayConfig(); + newConfig.setAppId(StringUtils.trimToNull(configService.getConfigValue("weixin", "appId"))); + newConfig.setMchId(StringUtils.trimToNull(configService.getConfigValue("weixin", "mchId"))); + newConfig.setMchKey(StringUtils.trimToNull(configService.getConfigValue("weixin", "mchKey"))); + newConfig.setUseSandboxEnv(false); + wxPayService.setConfig(newConfig); + } +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/response/PayResponse.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/response/PayResponse.java new file mode 100644 index 00000000..21690be0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/response/PayResponse.java @@ -0,0 +1,70 @@ +package org.ruoyi.common.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 支付结果响应 + * + * @author: wangle + * @date: 2023/7/3 + */ +@Data +public class PayResponse { + + /** + * 商户ID + */ + private String pid; + + /** + * 易支付订单号 + */ + + @JsonProperty("trade_no") + private String trade_no; + + /** + * 商户订单号 + */ + @JsonProperty("out_trade_no") + private String out_trade_no; + + /** + * 支付方式 + */ + private String type; + + /** + * 商品名称 + */ + private String name; + + /** + * 商品金额 + */ + private String money; + + /** + * 支付状态 + */ + @JsonProperty("trade_status") + private String trade_status; + + /** + * 业务扩展参数 + */ + private String param; + + /** + * 签名字符串 + */ + private String sign; + + /** + * 签名类型 + */ + @JsonProperty("sign_type") + private String signType; + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/PayService.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/PayService.java new file mode 100644 index 00000000..f11ed6e4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/PayService.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.service; + +/** + * 支付服务 + * + * @author: wangle + * @date: 2023/7/3 + */ +public interface PayService { + + /** + * 获取支付地址 + * + * @Date 2023/7/3 + * @param orderNo + * @param name + * @param money + * @param clientIp + * @return String + **/ + String getPayUrl(String orderNo, String name, double money, String clientIp); + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/impl/PayServiceImpl.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/impl/PayServiceImpl.java new file mode 100644 index 00000000..8e994529 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/service/impl/PayServiceImpl.java @@ -0,0 +1,53 @@ +package org.ruoyi.common.service.impl; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; + +import org.ruoyi.common.config.PayConfig; +import org.ruoyi.common.service.PayService; +import org.ruoyi.common.utils.MD5Util; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * 支付服务 + * @author Admin + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class PayServiceImpl implements PayService { + + private final PayConfig payConfig; + + @Override + public String getPayUrl(String orderNo, String name, double money, String clientIp) { + String out_trade_no = orderNo, sign = ""; + //封装请求参数 + String mdString = "clientip=" + clientIp + "&device=" + payConfig.getDevice() + "&money=" + money + "&name=" + name + "&" + + "notify_url=" + payConfig.getNotify_url() + "&out_trade_no=" + out_trade_no + "&pid=" + payConfig.getPid() + "&return_url=" + payConfig.getReturn_url() + + "&type=" + payConfig.getType() + payConfig.getKey(); + sign = MD5Util.GetMD5Code(mdString); + Map map = new HashMap<>(10); + map.put("clientip", clientIp); + map.put("device", payConfig.getDevice()); + map.put("money", money); + map.put("name", name); + map.put("notify_url", payConfig.getNotify_url()); + map.put("out_trade_no", out_trade_no); + map.put("pid", payConfig.getPid()); + map.put("return_url", payConfig.getReturn_url()); + map.put("sign_type", payConfig.getSign_type()); + map.put("type", payConfig.getType()); + map.put("sign", sign); + String body = HttpUtil.post(payConfig.getPayUrl(), map); + log.info("支付返回信息:{},配置信息: {}",body,payConfig); + JSONObject jsonObject = new JSONObject(body); + return (String) jsonObject.get("qrcode"); + } + +} diff --git a/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/utils/MD5Util.java b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/utils/MD5Util.java new file mode 100644 index 00000000..3aec5f28 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/utils/MD5Util.java @@ -0,0 +1,106 @@ +package org.ruoyi.common.utils; + +import cn.hutool.core.util.StrUtil; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.TreeMap; + +/** + * MD5 算法 + * + * @author Admin + */ +public class MD5Util { + + /** + * 全局数组 + */ + public final static String[] strDigits = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + + public MD5Util() { + } + + /** + * 返回形式为数字跟字符串 + * + * @Date 2023/7/3 + * @param bByte + * @return String + **/ + public static String byteToArrayString(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + int iD1 = iRet / 16; + int iD2 = iRet % 16; + return strDigits[iD1] + strDigits[iD2]; + } + + /** + * 转换字节数组为16进制字串 + * + * @Date 2023/7/3 + * @param bByte + * @return String + **/ + public static String byteToString(byte[] bByte) { + StringBuffer sBuffer = new StringBuffer(); + for (int i = 0; i < bByte.length; i++) { + sBuffer.append(byteToArrayString(bByte[i])); + } + return sBuffer.toString(); + } + + /** + * 生成md5代码 + * + * @Date 2023/7/3 + * @param strObj + * @return String + **/ + public static String GetMD5Code(String strObj) { + String resultString = null; + try { + resultString = new String(strObj); + MessageDigest md = MessageDigest.getInstance("MD5"); + resultString = byteToString(md.digest(strObj.getBytes())); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + return resultString; + } + + /** + * 组装签名的字段 + * + * @param params 参数 + * @param urlEncoder 是否urlEncoder + * @return {String} + */ + public static String packageSign(Map params, boolean urlEncoder) { + // 先将参数以其参数名的字典序升序进行排序 + TreeMap sortedParams = new TreeMap(params); + // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起 + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry param : sortedParams.entrySet()) { + String value = String.valueOf(param.getValue()); + if (StrUtil.isBlank(value)) { + continue; + } + if (first) { + first = false; + } else { + sb.append("&"); + } + sb.append(param.getKey()).append("="); + sb.append(value); + } + return sb.toString(); + } + +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/pom.xml b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml index f08316b4..cc327952 100644 --- a/ruoyi-common/ruoyi-common-ratelimiter/pom.xml +++ b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-core - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/annotation/RateLimiter.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/annotation/RateLimiter.java new file mode 100644 index 00000000..90c961ae --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/annotation/RateLimiter.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.ratelimiter.annotation; + +import org.ruoyi.common.ratelimiter.enums.LimitType; + +import java.lang.annotation.*; + +/** + * 限流注解 + * + * @author Lion Li + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + /** + * 限流key,支持使用Spring el表达式来动态获取方法上的参数值 + * 格式类似于 #code.id #{#code} + */ + String key() default ""; + + /** + * 限流时间,单位秒 + */ + int time() default 60; + + /** + * 限流次数 + */ + int count() default 100; + + /** + * 限流类型 + */ + LimitType limitType() default LimitType.DEFAULT; + + /** + * 提示消息 支持国际化 格式为 {code} + */ + String message() default "{rate.limiter.message}"; +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/aspectj/RateLimiterAspect.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/aspectj/RateLimiterAspect.java new file mode 100644 index 00000000..ec651f57 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/aspectj/RateLimiterAspect.java @@ -0,0 +1,127 @@ +package org.ruoyi.common.ratelimiter.aspectj; + +import cn.hutool.core.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MessageUtils; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.ratelimiter.annotation.RateLimiter; +import org.ruoyi.common.ratelimiter.enums.LimitType; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.redisson.api.RateType; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; + +/** + * 限流处理 + * + * @author Lion Li + */ +@Slf4j +@Aspect +public class RateLimiterAspect { + + /** + * 定义spel表达式解析器 + */ + private final ExpressionParser parser = new SpelExpressionParser(); + /** + * 定义spel解析模版 + */ + private final ParserContext parserContext = new TemplateParserContext(); + /** + * 定义spel上下文对象进行解析 + */ + private final EvaluationContext context = new StandardEvaluationContext(); + /** + * 方法参数解析器 + */ + private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + String combineKey = getCombineKey(rateLimiter, point); + try { + RateType rateType = RateType.OVERALL; + if (rateLimiter.limitType() == LimitType.CLUSTER) { + rateType = RateType.PER_CLIENT; + } + long number = RedisUtils.rateLimiter(combineKey, rateType, count, time); + if (number == -1) { + String message = rateLimiter.message(); + if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { + message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1)); + } + throw new ServiceException(message); + } + log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey); + } catch (Exception e) { + if (e instanceof ServiceException) { + throw e; + } else { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { + String key = rateLimiter.key(); + // 获取方法(通过方法签名来获取) + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + // 判断是否是spel格式 + if (StringUtils.containsAny(key, "#")) { + // 获取参数值 + Object[] args = point.getArgs(); + // 获取方法上参数的名称 + String[] parameterNames = pnd.getParameterNames(method); + if (ArrayUtil.isEmpty(parameterNames)) { + throw new ServiceException("限流key解析异常!请联系管理员!"); + } + for (int i = 0; i < parameterNames.length; i++) { + context.setVariable(parameterNames[i], args[i]); + } + // 解析返回给key + try { + Expression expression; + if (StringUtils.startsWith(key, parserContext.getExpressionPrefix()) + && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) { + expression = parser.parseExpression(key, parserContext); + } else { + expression = parser.parseExpression(key); + } + key = expression.getValue(context, String.class) + ":"; + } catch (Exception e) { + throw new ServiceException("限流key解析异常!请联系管理员!"); + } + } + StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY); + stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":"); + if (rateLimiter.limitType() == LimitType.IP) { + // 获取请求ip + stringBuffer.append(ServletUtils.getClientIP()).append(":"); + } else if (rateLimiter.limitType() == LimitType.CLUSTER) { + // 获取客户端实例id + stringBuffer.append(RedisUtils.getClient().getId()).append(":"); + } + return stringBuffer.append(key).toString(); + } +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/config/RateLimiterConfig.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/config/RateLimiterConfig.java new file mode 100644 index 00000000..2d3d08ef --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/config/RateLimiterConfig.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.ratelimiter.config; + +import org.ruoyi.common.ratelimiter.aspectj.RateLimiterAspect; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.RedisConfiguration; + +/** + * @author guangxin + * @date 2023/1/18 + */ +@AutoConfiguration(after = RedisConfiguration.class) +public class RateLimiterConfig { + + @Bean + public RateLimiterAspect rateLimiterAspect() { + return new RateLimiterAspect(); + } + +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/enums/LimitType.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/enums/LimitType.java new file mode 100644 index 00000000..dd079fb4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/ruoyi/common/ratelimiter/enums/LimitType.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.ratelimiter.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType { + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP, + + /** + * 实例限流(集群多后端实例) + */ + CLUSTER +} diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 46fa24a2..cac6f9ad 100644 --- a/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.ratelimiter.config.RateLimiterConfig +org.ruoyi.common.ratelimiter.config.RateLimiterConfig diff --git a/ruoyi-common/ruoyi-common-redis/pom.xml b/ruoyi-common/ruoyi-common-redis/pom.xml index c085069b..a3c2fe6a 100644 --- a/ruoyi-common/ruoyi-common-redis/pom.xml +++ b/ruoyi-common/ruoyi-common-redis/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -17,9 +17,9 @@ - + - com.xmzs + org.ruoyi ruoyi-common-core diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/RedisConfig.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/RedisConfig.java new file mode 100644 index 00000000..0314d002 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/RedisConfig.java @@ -0,0 +1,130 @@ +package org.ruoyi.common.redis.config; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.ruoyi.common.redis.config.properties.RedissonProperties; +import org.ruoyi.common.redis.handler.KeyPrefixHandler; +import org.ruoyi.common.redis.manager.PlusSpringCacheManager; +import lombok.extern.slf4j.Slf4j; +import org.redisson.codec.JsonJacksonCodec; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; + +/** + * redis配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +@EnableCaching +@EnableConfigurationProperties(RedissonProperties.class) +public class RedisConfig { + + @Autowired + private RedissonProperties redissonProperties; + + @Autowired + private ObjectMapper objectMapper; + + @Bean + public RedissonAutoConfigurationCustomizer redissonCustomizer() { + return config -> { + config.setThreads(redissonProperties.getThreads()) + .setNettyThreads(redissonProperties.getNettyThreads()) + .setCodec(new JsonJacksonCodec(objectMapper)); + RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + config.useSingleServer() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(singleServerConfig.getTimeout()) + .setClientName(singleServerConfig.getClientName()) + .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) + .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) + .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()); + } + // 集群配置方式 参考下方注释 + RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); + if (ObjectUtil.isNotNull(clusterServersConfig)) { + config.useClusterServers() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(clusterServersConfig.getTimeout()) + .setClientName(clusterServersConfig.getClientName()) + .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize()) + .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize()) + .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize()) + .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize()) + .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize()) + .setReadMode(clusterServersConfig.getReadMode()) + .setSubscriptionMode(clusterServersConfig.getSubscriptionMode()); + } + log.info("初始化 redis 配置"); + }; + } + + /** + * 自定义缓存管理器 整合spring-cache + */ + @Bean + public CacheManager cacheManager() { + return new PlusSpringCacheManager(); + } + + /** + * redis集群配置 yml + * + * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉) + * spring: + * redis: + * cluster: + * nodes: + * - 192.168.0.100:6379 + * - 192.168.0.101:6379 + * - 192.168.0.102:6379 + * # 密码 + * password: + * # 连接超时时间 + * timeout: 10s + * # 是否开启ssl + * ssl: false + * + * redisson: + * # 线程池数量 + * threads: 16 + * # Netty线程池数量 + * nettyThreads: 32 + * # 集群配置 + * clusterServersConfig: + * # 客户端名称 + * clientName: ${ruoyi.name} + * # master最小空闲连接数 + * masterConnectionMinimumIdleSize: 32 + * # master连接池大小 + * masterConnectionPoolSize: 64 + * # slave最小空闲连接数 + * slaveConnectionMinimumIdleSize: 32 + * # slave连接池大小 + * slaveConnectionPoolSize: 64 + * # 连接空闲超时,单位:毫秒 + * idleConnectionTimeout: 10000 + * # 命令等待超时,单位:毫秒 + * timeout: 3000 + * # 发布和订阅连接池大小 + * subscriptionConnectionPoolSize: 50 + * # 读取模式 + * readMode: "SLAVE" + * # 订阅模式 + * subscriptionMode: "MASTER" + */ + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/properties/RedissonProperties.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/properties/RedissonProperties.java new file mode 100644 index 00000000..91a41bd1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/config/properties/RedissonProperties.java @@ -0,0 +1,135 @@ +package org.ruoyi.common.redis.config.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.redisson.config.ReadMode; +import org.redisson.config.SubscriptionMode; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Redisson 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "redisson") +public class RedissonProperties { + + /** + * redis缓存key前缀 + */ + private String keyPrefix; + + /** + * 线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int threads; + + /** + * Netty线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int nettyThreads; + + /** + * 单机服务配置 + */ + private SingleServerConfig singleServerConfig; + + /** + * 集群服务配置 + */ + private ClusterServersConfig clusterServersConfig; + + @Data + @NoArgsConstructor + public static class SingleServerConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * 最小空闲连接数 + */ + private int connectionMinimumIdleSize; + + /** + * 连接池大小 + */ + private int connectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + } + + @Data + @NoArgsConstructor + public static class ClusterServersConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * master最小空闲连接数 + */ + private int masterConnectionMinimumIdleSize; + + /** + * master连接池大小 + */ + private int masterConnectionPoolSize; + + /** + * slave最小空闲连接数 + */ + private int slaveConnectionMinimumIdleSize; + + /** + * slave连接池大小 + */ + private int slaveConnectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + /** + * 读取模式 + */ + private ReadMode readMode; + + /** + * 订阅模式 + */ + private SubscriptionMode subscriptionMode; + + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/handler/KeyPrefixHandler.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/handler/KeyPrefixHandler.java new file mode 100644 index 00000000..16d453f7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/handler/KeyPrefixHandler.java @@ -0,0 +1,50 @@ +package org.ruoyi.common.redis.handler; + +import org.ruoyi.common.core.utils.StringUtils; +import org.redisson.api.NameMapper; + +/** + * redis缓存key前缀处理 + * + * @author ye + * @date 2022/7/14 17:44 + * @since 4.3.0 + */ +public class KeyPrefixHandler implements NameMapper { + + private final String keyPrefix; + + public KeyPrefixHandler(String keyPrefix) { + //前缀为空 则返回空前缀 + this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":"; + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) { + return keyPrefix + name; + } + return name; + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) { + return name.substring(keyPrefix.length()); + } + return name; + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/manager/PlusSpringCacheManager.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/manager/PlusSpringCacheManager.java new file mode 100644 index 00000000..797ecc77 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/manager/PlusSpringCacheManager.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ruoyi.common.redis.manager; + +import org.ruoyi.common.redis.utils.RedisUtils; +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.spring.cache.CacheConfig; +import org.redisson.spring.cache.RedissonCache; +import org.springframework.boot.convert.DurationStyle; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A {@link CacheManager} implementation + * backed by Redisson instance. + *

    + * 修改 RedissonSpringCacheManager 源码 + * 重写 cacheName 处理方法 支持多参数 + * + * @author Nikita Koksharov + * + */ +@SuppressWarnings("unchecked") +public class PlusSpringCacheManager implements CacheManager { + + private boolean dynamic = true; + + private boolean allowNullValues = true; + + private boolean transactionAware = true; + + Map configMap = new ConcurrentHashMap<>(); + ConcurrentMap instanceMap = new ConcurrentHashMap<>(); + + /** + * Creates CacheManager supplied by Redisson instance + */ + public PlusSpringCacheManager() { + } + + + /** + * Defines possibility of storing {@code null} values. + *

    + * Default is true + * + * @param allowNullValues stores if true + */ + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + /** + * Defines if cache aware of Spring-managed transactions. + * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. + *

    + * Default is false + * + * @param transactionAware cache is transaction aware if true + */ + public void setTransactionAware(boolean transactionAware) { + this.transactionAware = transactionAware; + } + + /** + * Defines 'fixed' cache names. + * A new cache instance will not be created in dynamic for non-defined names. + *

    + * `null` parameter setups dynamic mode + * + * @param names of caches + */ + public void setCacheNames(Collection names) { + if (names != null) { + for (String name : names) { + getCache(name); + } + dynamic = false; + } else { + dynamic = true; + } + } + + /** + * Set cache config mapped by cache name + * + * @param config object + */ + public void setConfig(Map config) { + this.configMap = (Map) config; + } + + protected CacheConfig createDefaultConfig() { + return new CacheConfig(); + } + + @Override + public Cache getCache(String name) { + Cache cache = instanceMap.get(name); + if (cache != null) { + return cache; + } + if (!dynamic) { + return cache; + } + + CacheConfig config = configMap.get(name); + if (config == null) { + config = createDefaultConfig(); + configMap.put(name, config); + } + + // 重写 cacheName 支持多参数 + String[] array = StringUtils.delimitedListToStringArray(name, "#"); + name = array[0]; + if (array.length > 1) { + config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); + } + if (array.length > 2) { + config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); + } + if (array.length > 3) { + config.setMaxSize(Integer.parseInt(array[3])); + } + + if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { + return createMap(name, config); + } + + return createMapCache(name, config); + } + + private Cache createMap(String name, CacheConfig config) { + RMap map = RedisUtils.getClient().getMap(name); + + Cache cache = new RedissonCache(map, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } + return cache; + } + + private Cache createMapCache(String name, CacheConfig config) { + RMapCache map = RedisUtils.getClient().getMapCache(name); + + Cache cache = new RedissonCache(map, config, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } else { + map.setMaxSize(config.getMaxSize()); + } + return cache; + } + + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(configMap.keySet()); + } + + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/CacheUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/CacheUtils.java new file mode 100644 index 00000000..dcee090d --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/CacheUtils.java @@ -0,0 +1,75 @@ +package org.ruoyi.common.redis.utils; + +import org.ruoyi.common.core.utils.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.RMap; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import java.util.Set; + +/** + * 缓存操作工具类 {@link } + * + * @author Michelle.Chung + * @date 2022/8/13 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked"}) +public class CacheUtils { + + private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class); + + /** + * 获取缓存组内所有的KEY + * + * @param cacheNames 缓存组名称 + */ + public static Set keys(String cacheNames) { + RMap rmap = (RMap) CACHE_MANAGER.getCache(cacheNames).getNativeCache(); + return rmap.keySet(); + } + + /** + * 获取缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static T get(String cacheNames, Object key) { + Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key); + return wrapper != null ? (T) wrapper.get() : null; + } + + /** + * 保存缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + * @param value 缓存值 + */ + public static void put(String cacheNames, Object key, Object value) { + CACHE_MANAGER.getCache(cacheNames).put(key, value); + } + + /** + * 删除缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static void evict(String cacheNames, Object key) { + CACHE_MANAGER.getCache(cacheNames).evict(key); + } + + /** + * 清空缓存值 + * + * @param cacheNames 缓存组名称 + */ + public static void clear(String cacheNames) { + CACHE_MANAGER.getCache(cacheNames).clear(); + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/QueueUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/QueueUtils.java new file mode 100644 index 00000000..cee279d1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/QueueUtils.java @@ -0,0 +1,180 @@ +package org.ruoyi.common.redis.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.SpringUtils; +import org.redisson.api.*; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * 分布式队列工具 + * 轻量级队列 重量级数据量 请使用 MQ + * 要求 redis 5.X 以上 + * + * @author Lion Li + * @version 3.6.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class QueueUtils { + + private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class); + + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 添加普通队列数据 + * + * @param queueName 队列名 + * @param data 数据 + */ + public static boolean addQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.offer(data); + } + + /** + * 通用获取一个队列数据 没有数据返回 null(不支持延迟队列) + * + * @param queueName 队列名 + */ + public static T getQueueObject(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.poll(); + } + + /** + * 通用删除队列数据(不支持延迟队列) + */ + public static boolean removeQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.remove(data); + } + + /** + * 通用销毁队列 所有阻塞监听 报错(不支持延迟队列) + */ + public static boolean destroyQueue(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.delete(); + } + + /** + * 添加延迟队列数据 默认毫秒 + * + * @param queueName 队列名 + * @param data 数据 + * @param time 延迟时间 + */ + public static void addDelayedQueueObject(String queueName, T data, long time) { + addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS); + } + + /** + * 添加延迟队列数据 + * + * @param queueName 队列名 + * @param data 数据 + * @param time 延迟时间 + * @param timeUnit 单位 + */ + public static void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + delayedQueue.offer(data, time, timeUnit); + } + + /** + * 获取一个延迟队列数据 没有数据返回 null + * + * @param queueName 队列名 + */ + public static T getDelayedQueueObject(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + return delayedQueue.poll(); + } + + /** + * 删除延迟队列数据 + */ + public static boolean removeDelayedQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + return delayedQueue.remove(data); + } + + /** + * 销毁延迟队列 所有阻塞监听 报错 + */ + public static void destroyDelayedQueue(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + delayedQueue.destroy(); + } + + /** + * 添加优先队列数据 + * + * @param queueName 队列名 + * @param data 数据 + */ + public static boolean addPriorityQueueObject(String queueName, T data) { + RPriorityBlockingQueue priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName); + return priorityBlockingQueue.offer(data); + } + + /** + * 尝试设置 有界队列 容量 用于限制数量 + * + * @param queueName 队列名 + * @param capacity 容量 + */ + public static boolean trySetBoundedQueueCapacity(String queueName, int capacity) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + return boundedBlockingQueue.trySetCapacity(capacity); + } + + /** + * 尝试设置 有界队列 容量 用于限制数量 + * + * @param queueName 队列名 + * @param capacity 容量 + * @param destroy 已存在是否销毁 + */ + public static boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + if (boundedBlockingQueue.isExists() && destroy) { + destroyQueue(queueName); + } + return boundedBlockingQueue.trySetCapacity(capacity); + } + + /** + * 添加有界队列数据 + * + * @param queueName 队列名 + * @param data 数据 + * @return 添加成功 true 已达到界限 false + */ + public static boolean addBoundedQueueObject(String queueName, T data) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + return boundedBlockingQueue.offer(data); + } + + /** + * 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等) + */ + public static void subscribeBlockingQueue(String queueName, Consumer consumer) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + queue.subscribeOnElements(consumer); + } + +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/RedisUtils.java new file mode 100644 index 00000000..d17bfc8e --- /dev/null +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/ruoyi/common/redis/utils/RedisUtils.java @@ -0,0 +1,462 @@ +package org.ruoyi.common.redis.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.SpringUtils; +import org.redisson.api.*; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * redis 工具类 + * + * @author Lion Li + * @version 3.1.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +public class RedisUtils { + + private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class); + + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS); + if (rateLimiter.tryAcquire()) { + return rateLimiter.availablePermits(); + } else { + return -1L; + } + } + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 发布通道消息 + * + * @param channelKey 通道key + * @param msg 发送数据 + * @param consumer 自定义处理 + */ + public static void publish(String channelKey, T msg, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + consumer.accept(msg); + } + + public static void publish(String channelKey, T msg) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + } + + /** + * 订阅通道接收消息 + * + * @param channelKey 通道key + * @param clazz 消息类型 + * @param consumer 自定义处理 + */ + public static void subscribe(String channelKey, Class clazz, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.addListener(clazz, (channel, msg) -> consumer.accept(msg)); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public static void setCacheObject(final String key, final T value) { + setCacheObject(key, value, false); + } + + /** + * 缓存基本的对象,保留当前对象 TTL 有效期 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90) + * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案 + */ + public static void setCacheObject(final String key, final T value, final boolean isSaveTtl) { + RBucket bucket = CLIENT.getBucket(key); + if (isSaveTtl) { + try { + bucket.setAndKeepTTL(value); + } catch (Exception e) { + long timeToLive = bucket.remainTimeToLive(); + setCacheObject(key, value, Duration.ofMillis(timeToLive)); + } + } else { + bucket.set(value); + } + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param duration 时间 + */ + public static void setCacheObject(final String key, final T value, final Duration duration) { + RBatch batch = CLIENT.createBatch(); + RBucketAsync bucket = batch.getBucket(key); + bucket.setAsync(value); + bucket.expireAsync(duration); + batch.execute(); + } + + /** + * 注册对象监听器 + *

    + * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addObjectListener(final String key, final ObjectListener listener) { + RBucket result = CLIENT.getBucket(key); + result.addListener(listener); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final long timeout) { + return expire(key, Duration.ofSeconds(timeout)); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param duration 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final Duration duration) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.expire(duration); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public static T getCacheObject(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.get(); + } + + /** + * 获得key剩余存活时间 + * + * @param key 缓存键值 + * @return 剩余存活时间 + */ + public static long getTimeToLive(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.remainTimeToLive(); + } + + /** + * 删除单个对象 + * + * @param key 缓存的键值 + */ + public static boolean deleteObject(final String key) { + return CLIENT.getBucket(key).delete(); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + */ + public static void deleteObject(final Collection collection) { + RBatch batch = CLIENT.createBatch(); + collection.forEach(t -> { + batch.getBucket(t.toString()).deleteAsync(); + }); + batch.execute(); + } + + /** + * 检查缓存对象是否存在 + * + * @param key 缓存的键值 + */ + public static boolean isExistsObject(final String key) { + return CLIENT.getBucket(key).isExists(); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public static boolean setCacheList(final String key, final List dataList) { + RList rList = CLIENT.getList(key); + return rList.addAll(dataList); + } + + /** + * 注册List监听器 + *

    + * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addListListener(final String key, final ObjectListener listener) { + RList rList = CLIENT.getList(key); + rList.addListener(listener); + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public static List getCacheList(final String key) { + RList rList = CLIENT.getList(key); + return rList.readAll(); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public static boolean setCacheSet(final String key, final Set dataSet) { + RSet rSet = CLIENT.getSet(key); + return rSet.addAll(dataSet); + } + + /** + * 注册Set监听器 + *

    + * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addSetListener(final String key, final ObjectListener listener) { + RSet rSet = CLIENT.getSet(key); + rSet.addListener(listener); + } + + /** + * 获得缓存的set + * + * @param key 缓存的key + * @return set对象 + */ + public static Set getCacheSet(final String key) { + RSet rSet = CLIENT.getSet(key); + return rSet.readAll(); + } + + /** + * 缓存Map + * + * @param key 缓存的键值 + * @param dataMap 缓存的数据 + */ + public static void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + RMap rMap = CLIENT.getMap(key); + rMap.putAll(dataMap); + } + } + + /** + * 注册Map监听器 + *

    + * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addMapListener(final String key, final ObjectListener listener) { + RMap rMap = CLIENT.getMap(key); + rMap.addListener(listener); + } + + /** + * 获得缓存的Map + * + * @param key 缓存的键值 + * @return map对象 + */ + public static Map getCacheMap(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(rMap.keySet()); + } + + /** + * 获得缓存Map的key列表 + * + * @param key 缓存的键值 + * @return key列表 + */ + public static Set getCacheMapKeySet(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.keySet(); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public static void setCacheMapValue(final String key, final String hKey, final T value) { + RMap rMap = CLIENT.getMap(key); + rMap.put(hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T getCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.get(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T delCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.remove(hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public static Map getMultiCacheMapValue(final String key, final Set hKeys) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(hKeys); + } + + /** + * 设置原子值 + * + * @param key Redis键 + * @param value 值 + */ + public static void setAtomicValue(String key, long value) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + atomic.set(value); + } + + /** + * 获取原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long getAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.get(); + } + + /** + * 递增原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long incrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.incrementAndGet(); + } + + /** + * 递减原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long decrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.decrementAndGet(); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public static Collection keys(final String pattern) { + Stream stream = CLIENT.getKeys().getKeysStreamByPattern(pattern); + return stream.collect(Collectors.toList()); + } + + /** + * 删除缓存的基本对象列表 + * + * @param pattern 字符串前缀 + */ + public static void deleteKeys(final String pattern) { + CLIENT.getKeys().deleteByPattern(pattern); + } + + /** + * 检查redis中是否存在key + * + * @param key 键 + */ + public static Boolean hasKey(String key) { + RKeys rKeys = CLIENT.getKeys(); + return rKeys.countExists(key) > 0; + } +} diff --git a/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 0fdeef66..460d768e 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.redis.config.RedisConfig +org.ruoyi.common.redis.config.RedisConfig diff --git a/ruoyi-common/ruoyi-common-satoken/pom.xml b/ruoyi-common/ruoyi-common-satoken/pom.xml index 10ecb486..9883296f 100644 --- a/ruoyi-common/ruoyi-common-satoken/pom.xml +++ b/ruoyi-common/ruoyi-common-satoken/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -15,13 +15,13 @@ - com.xmzs + org.ruoyi ruoyi-common-core - + - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/config/SaTokenConfig.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/config/SaTokenConfig.java new file mode 100644 index 00000000..dd668a11 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/config/SaTokenConfig.java @@ -0,0 +1,43 @@ +package org.ruoyi.common.satoken.config; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpLogic; +import org.ruoyi.common.satoken.core.dao.PlusSaTokenDao; +import org.ruoyi.common.satoken.core.service.SaPermissionImpl; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * sa-token 配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class SaTokenConfig implements WebMvcConfigurer { + + @Bean + public StpLogic getStpLogicJwt() { + // Sa-Token 整合 jwt (简单模式) + return new StpLogicJwtForSimple(); + } + + /** + * 权限接口实现(使用bean注入方便用户替换) + */ + @Bean + public StpInterface stpInterface() { + return new SaPermissionImpl(); + } + + /** + * 自定义dao层存储 + */ + @Bean + public SaTokenDao saTokenDao() { + return new PlusSaTokenDao(); + } + +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/dao/PlusSaTokenDao.java new file mode 100644 index 00000000..93e73615 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/dao/PlusSaTokenDao.java @@ -0,0 +1,176 @@ +package org.ruoyi.common.satoken.core.dao; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.util.SaFoxUtil; +import org.ruoyi.common.redis.utils.RedisUtils; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) + * + * @author Lion Li + */ +public class PlusSaTokenDao implements SaTokenDao { + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, value); + } else { + RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, object); + } else { + RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); + } + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Collection keys = RedisUtils.keys(prefix + "*" + keyword + "*"); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/service/SaPermissionImpl.java new file mode 100644 index 00000000..5ee2f9f9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/core/service/SaPermissionImpl.java @@ -0,0 +1,47 @@ +package org.ruoyi.common.satoken.core.service; + +import cn.dev33.satoken.stp.StpInterface; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.enums.UserType; +import org.ruoyi.common.satoken.utils.LoginHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * sa-token 权限管理实现类 + * + * @author Lion Li + */ +public class SaPermissionImpl implements StpInterface { + + /** + * 获取菜单权限列表 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getMenuPermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } + + /** + * 获取角色权限列表 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getRolePermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/listener/UserActionListener.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/listener/UserActionListener.java new file mode 100644 index 00000000..02f1ad6a --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/listener/UserActionListener.java @@ -0,0 +1,138 @@ +package org.ruoyi.common.satoken.listener; + +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.constant.CacheConstants; +import org.ruoyi.common.core.domain.dto.UserOnlineDTO; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.enums.UserType; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +/** + * 用户行为 侦听器的实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class UserActionListener implements SaTokenListener { + + private final SaTokenConfig tokenConfig; + + /** + * 每次登录时触发 + */ + @Override + public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { + UserType userType = UserType.getUserType(loginId.toString()); + if (userType == UserType.SYS_USER) { + UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = ServletUtils.getClientIP(); + LoginUser user = LoginHelper.getLoginUser(); + UserOnlineDTO dto = new UserOnlineDTO(); + dto.setIpaddr(ip); + // dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + dto.setBrowser(userAgent.getBrowser().getName()); + dto.setOs(userAgent.getOs().getName()); + dto.setLoginTime(System.currentTimeMillis()); + dto.setTokenId(tokenValue); + dto.setUserName(user.getUsername()); + dto.setDeptName(user.getDeptName()); + if(tokenConfig.getTimeout() == -1) { + RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); + } else { + RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout())); + } + log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue); + } else if (userType == UserType.APP_USER) { + // app端 自行根据业务编写 + } + } + + /** + * 每次注销时触发 + */ + @Override + public void doLogout(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被踢下线时触发 + */ + @Override + public void doKickout(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被顶下线时触发 + */ + @Override + public void doReplaced(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被封禁时触发 + */ + @Override + public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { + } + + /** + * 每次被解封时触发 + */ + @Override + public void doUntieDisable(String loginType, Object loginId, String service) { + } + + /** + * 每次打开二级认证时触发 + */ + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCreateSession(String id) { + } + + /** + * 每次注销Session时触发 + */ + @Override + public void doLogoutSession(String id) { + } + + /** + * 每次Token续期时触发 + */ + @Override + public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { + } +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java new file mode 100644 index 00000000..fd8f8f2e --- /dev/null +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java @@ -0,0 +1,165 @@ +package org.ruoyi.common.satoken.utils; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.enums.DeviceType; +import org.ruoyi.common.core.enums.UserType; + +import java.util.Set; + +/** + * 登录鉴权助手 + *

    + * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app + * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios + * 可以组成 用户类型与设备类型多对多的 权限灵活控制 + *

    + * 多用户体系 针对 多种用户类型 但权限控制不一致 + * 可以组成 多用户类型表与多设备类型 分别控制权限 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoginHelper { + + public static final String LOGIN_USER_KEY = "loginUser"; + public static final String TENANT_KEY = "tenantId"; + public static final String USER_KEY = "userId"; + + /** + * 登录系统 + * + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser) { + loginByDevice(loginUser, null); + } + + /** + * 登录系统 基于 设备类型 + * 针对相同用户体系不同设备 + * + * @param loginUser 登录用户信息 + */ + public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) { + SaStorage storage = SaHolder.getStorage(); + storage.set(LOGIN_USER_KEY, loginUser); + storage.set(TENANT_KEY, loginUser.getTenantId()); + storage.set(USER_KEY, loginUser.getUserId()); + SaLoginModel model = new SaLoginModel(); + if (ObjectUtil.isNotNull(deviceType)) { + model.setDevice(deviceType.getDevice()); + } + StpUtil.login(loginUser.getLoginId(), + model.setExtra(TENANT_KEY, loginUser.getTenantId()) + .setExtra(USER_KEY, loginUser.getUserId())); + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + /** + * 获取用户(多级缓存) + */ + public static LoginUser getLoginUser() { + LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY); + if (loginUser != null) { + return loginUser; + } + loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY); + SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser); + return loginUser; + } + + /** + * 获取用户id + */ + public static Long getUserId() { + Long userId; + try { + userId = Convert.toLong(SaHolder.getStorage().get(USER_KEY)); + if (ObjectUtil.isNull(userId)) { + userId = Convert.toLong(StpUtil.getExtra(USER_KEY)); + SaHolder.getStorage().set(USER_KEY, userId); + } + } catch (Exception e) { + return null; + } + return userId; + } + + /** + * 获取租户ID + */ + public static String getTenantId() { + String tenantId; + try { + tenantId = (String) SaHolder.getStorage().get(TENANT_KEY); + if (ObjectUtil.isNull(tenantId)) { + tenantId = (String) StpUtil.getExtra(TENANT_KEY); + SaHolder.getStorage().set(TENANT_KEY, tenantId); + } + } catch (Exception e) { + return null; + } + return tenantId; + } + + /** + * 获取部门ID + */ + public static Long getDeptId() { + return getLoginUser().getDeptId(); + } + + /** + * 获取用户账户 + */ + public static String getUsername() { + return getLoginUser().getUsername(); + } + + /** + * 获取用户类型 + */ + public static UserType getUserType() { + String loginId = StpUtil.getLoginIdAsString(); + return UserType.getUserType(loginId); + } + + /** + * 是否为超级管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isSuperAdmin(Long userId) { + return UserConstants.SUPER_ADMIN_ID.equals(userId); + } + + public static boolean isSuperAdmin() { + return isSuperAdmin(getUserId()); + } + + /** + * 是否为超级管理员 + * + * @param rolePermission 角色权限标识组 + * @return 结果 + */ + public static boolean isTenantAdmin(Set rolePermission) { + return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY); + } + + public static boolean isTenantAdmin() { + return isTenantAdmin(getLoginUser().getRolePermission()); + } + +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 3744e52e..61f01464 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.satoken.config.SaTokenConfig +org.ruoyi.common.satoken.config.SaTokenConfig diff --git a/ruoyi-common/ruoyi-common-security/pom.xml b/ruoyi-common/ruoyi-common-security/pom.xml index 00957938..c22b3278 100644 --- a/ruoyi-common/ruoyi-common-security/pom.xml +++ b/ruoyi-common/ruoyi-common-security/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-satoken diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/SecurityConfig.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/SecurityConfig.java new file mode 100644 index 00000000..339ecc68 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/SecurityConfig.java @@ -0,0 +1,59 @@ +package org.ruoyi.common.security.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.security.config.properties.SecurityProperties; +import org.ruoyi.common.security.handler.AllUrlHandler; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 权限安全配置 + * + * @author Lion Li + */ + +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(SecurityProperties.class) +@RequiredArgsConstructor +public class SecurityConfig implements WebMvcConfigurer { + + private final SecurityProperties securityProperties; + + /** + * 注册sa-token的拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册路由拦截器,自定义验证规则 + registry.addInterceptor(new SaInterceptor(handler -> { + AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class); + // 登录验证 -- 排除多个路径 + SaRouter + // 获取所有的 + .match(allUrlHandler.getUrls()) + // 对未排除的路径进行检查 + .check(() -> { + // 检查是否登录 是否有token + StpUtil.checkLogin(); + + // 有效率影响 用于临时测试 + // if (log.isDebugEnabled()) { + // log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout()); + // log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout()); + // } + + }); + })).addPathPatterns("/**") + // 排除不需要拦截的路径 + .excludePathPatterns(securityProperties.getExcludes()); + } + +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/properties/SecurityProperties.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/properties/SecurityProperties.java new file mode 100644 index 00000000..dc75314c --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/config/properties/SecurityProperties.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.security.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Security 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "security") +public class SecurityProperties { + + /** + * 排除路径 + */ + private String[] excludes; + + +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/AllUrlHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/AllUrlHandler.java new file mode 100644 index 00000000..4be94b26 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/AllUrlHandler.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.security.handler; + +import cn.hutool.core.util.ReUtil; +import lombok.Data; +import org.ruoyi.common.core.utils.SpringUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * 获取所有Url配置 + * + * @author Lion Li + */ +@Data +public class AllUrlHandler implements InitializingBean { + + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private List urls = new ArrayList<>(); + + @Override + public void afterPropertiesSet() { + Set set = new HashSet<>(); + RequestMappingHandlerMapping mapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + map.keySet().forEach(info -> { + // 获取注解上边的 path 替代 path variable 为 * + if(info.getPathPatternsCondition()!=null){ + Objects.requireNonNull(info.getPathPatternsCondition().getPatterns()) + .forEach(url -> set.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "*"))); + } + }); + urls.addAll(set); + } + +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..d5445691 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/ruoyi/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,140 @@ +package org.ruoyi.common.security.handler; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.exception.DemoModeException; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.StreamUtils; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author Lion Li + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public R handleNotRoleException(NotRoleException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 认证失败 + */ + @ExceptionHandler(NotLoginException.class) + public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return R.fail(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return R.fail(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return R.fail(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public R handleBindException(BindException e) { + log.error(e.getMessage(), e); + String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", "); + return R.fail(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + public R constraintViolationException(ConstraintViolationException e) { + log.error(e.getMessage(), e); + String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", "); + return R.fail(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return R.fail(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public R handleDemoModeException(DemoModeException e) { + return R.fail("演示模式,不允许操作"); + } +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 8df4c8bb..24cc784c 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,3 @@ -com.xmzs.common.security.handler.GlobalExceptionHandler -com.xmzs.common.security.handler.AllUrlHandler -com.xmzs.common.security.config.SecurityConfig +org.ruoyi.common.security.handler.GlobalExceptionHandler +org.ruoyi.common.security.handler.AllUrlHandler +org.ruoyi.common.security.config.SecurityConfig diff --git a/ruoyi-common/ruoyi-common-sensitive/pom.xml b/ruoyi-common/ruoyi-common-sensitive/pom.xml index d0a2af7d..6f91ae83 100644 --- a/ruoyi-common/ruoyi-common-sensitive/pom.xml +++ b/ruoyi-common/ruoyi-common-sensitive/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/annotation/Sensitive.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/annotation/Sensitive.java new file mode 100644 index 00000000..493950a7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/annotation/Sensitive.java @@ -0,0 +1,24 @@ +package org.ruoyi.common.sensitive.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.ruoyi.common.sensitive.core.SensitiveStrategy; +import org.ruoyi.common.sensitive.handler.SensitiveHandler; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据脱敏注解 + * + * @author zhujie + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveHandler.class) +public @interface Sensitive { + SensitiveStrategy strategy(); +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveService.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveService.java new file mode 100644 index 00000000..e7c4fac4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveService.java @@ -0,0 +1,18 @@ +package org.ruoyi.common.sensitive.core; + +/** + * 脱敏服务 + * 默认管理员不过滤 + * 需自行根据业务重写实现 + * + * @author Lion Li + * @version 3.6.0 + */ +public interface SensitiveService { + + /** + * 是否脱敏 + */ + boolean isSensitive(); + +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveStrategy.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveStrategy.java new file mode 100644 index 00000000..a100b945 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/core/SensitiveStrategy.java @@ -0,0 +1,49 @@ +package org.ruoyi.common.sensitive.core; + +import cn.hutool.core.util.DesensitizedUtil; +import lombok.AllArgsConstructor; + +import java.util.function.Function; + +/** + * 脱敏策略 + * + * @author Yjoioooo + * @version 3.6.0 + */ +@AllArgsConstructor +public enum SensitiveStrategy { + + /** + * 身份证脱敏 + */ + ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)), + + /** + * 手机号脱敏 + */ + PHONE(DesensitizedUtil::mobilePhone), + + /** + * 地址脱敏 + */ + ADDRESS(s -> DesensitizedUtil.address(s, 8)), + + /** + * 邮箱脱敏 + */ + EMAIL(DesensitizedUtil::email), + + /** + * 银行卡 + */ + BANK_CARD(DesensitizedUtil::bankCard); + + //可自行添加其他脱敏策略 + + private final Function desensitizer; + + public Function desensitizer() { + return desensitizer; + } +} diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/handler/SensitiveHandler.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/handler/SensitiveHandler.java new file mode 100644 index 00000000..5c6c5764 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/ruoyi/common/sensitive/handler/SensitiveHandler.java @@ -0,0 +1,54 @@ +package org.ruoyi.common.sensitive.handler; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.sensitive.annotation.Sensitive; +import org.ruoyi.common.sensitive.core.SensitiveService; +import org.ruoyi.common.sensitive.core.SensitiveStrategy; +import org.springframework.beans.BeansException; + +import java.io.IOException; +import java.util.Objects; + +/** + * 数据脱敏json序列化工具 + * + * @author Yjoioooo + */ +@Slf4j +public class SensitiveHandler extends JsonSerializer implements ContextualSerializer { + + private SensitiveStrategy strategy; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + try { + SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class); + if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive()) { + gen.writeString(strategy.desensitizer().apply(value)); + } else { + gen.writeString(value); + } + } catch (BeansException e) { + log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage()); + gen.writeString(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { + this.strategy = annotation.strategy(); + return this; + } + return prov.findValueSerializer(property.getType(), property); + } +} diff --git a/ruoyi-common/ruoyi-common-sms/pom.xml b/ruoyi-common/ruoyi-common-sms/pom.xml index d77feae3..1a0ccdc8 100644 --- a/ruoyi-common/ruoyi-common-sms/pom.xml +++ b/ruoyi-common/ruoyi-common-sms/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,7 +18,7 @@ - com.xmzs + org.ruoyi ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/SmsConfig.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/SmsConfig.java new file mode 100644 index 00000000..5fbb37c1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/SmsConfig.java @@ -0,0 +1,48 @@ +package org.ruoyi.common.sms.config; + +import org.ruoyi.common.sms.config.properties.SmsProperties; +import org.ruoyi.common.sms.core.AliyunSmsTemplate; +import org.ruoyi.common.sms.core.SmsTemplate; +import org.ruoyi.common.sms.core.TencentSmsTemplate; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 短信配置类 + * + * @author Lion Li + * @version 4.2.0 + */ +@AutoConfiguration +@EnableConfigurationProperties(SmsProperties.class) +public class SmsConfig { + + @Configuration + @ConditionalOnProperty(value = "sms.enabled", havingValue = "true") + @ConditionalOnClass(com.aliyun.dysmsapi20170525.Client.class) + static class AliyunSmsConfig { + + @Bean + public SmsTemplate aliyunSmsTemplate(SmsProperties smsProperties) { + return new AliyunSmsTemplate(smsProperties); + } + + } + + @Configuration + @ConditionalOnProperty(value = "sms.enabled", havingValue = "true") + @ConditionalOnClass(com.tencentcloudapi.sms.v20190711.SmsClient.class) + static class TencentSmsConfig { + + @Bean + public SmsTemplate tencentSmsTemplate(SmsProperties smsProperties) { + return new TencentSmsTemplate(smsProperties); + } + + } + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/properties/SmsProperties.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/properties/SmsProperties.java new file mode 100644 index 00000000..b6b83a60 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/config/properties/SmsProperties.java @@ -0,0 +1,45 @@ +package org.ruoyi.common.sms.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * SMS短信 配置属性 + * + * @author Lion Li + * @version 4.2.0 + */ +@Data +@ConfigurationProperties(prefix = "sms") +public class SmsProperties { + + private Boolean enabled; + + /** + * 配置节点 + * 阿里云 dysmsapi.aliyuncs.com + * 腾讯云 sms.tencentcloudapi.com + */ + private String endpoint; + + /** + * key + */ + private String accessKeyId; + + /** + * 密匙 + */ + private String accessKeySecret; + + /* + * 短信签名 + */ + private String signName; + + /** + * 短信应用ID (腾讯专属) + */ + private String sdkAppId; + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/AliyunSmsTemplate.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/AliyunSmsTemplate.java new file mode 100644 index 00000000..68b2cd2d --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/AliyunSmsTemplate.java @@ -0,0 +1,66 @@ +package org.ruoyi.common.sms.core; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teaopenapi.models.Config; +import lombok.SneakyThrows; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.sms.config.properties.SmsProperties; +import org.ruoyi.common.sms.entity.SmsResult; +import org.ruoyi.common.sms.exception.SmsException; + +import java.util.Map; + +/** + * Aliyun 短信模板 + * + * @author Lion Li + * @version 4.2.0 + */ +public class AliyunSmsTemplate implements SmsTemplate { + + private SmsProperties properties; + + private Client client; + + @SneakyThrows(Exception.class) + public AliyunSmsTemplate(SmsProperties smsProperties) { + this.properties = smsProperties; + Config config = new Config() + // 您的AccessKey ID + .setAccessKeyId(smsProperties.getAccessKeyId()) + // 您的AccessKey Secret + .setAccessKeySecret(smsProperties.getAccessKeySecret()) + // 访问的域名 + .setEndpoint(smsProperties.getEndpoint()); + this.client = new Client(config); + } + + @Override + public SmsResult send(String phones, String templateId, Map param) { + if (StringUtils.isBlank(phones)) { + throw new SmsException("手机号不能为空"); + } + if (StringUtils.isBlank(templateId)) { + throw new SmsException("模板ID不能为空"); + } + SendSmsRequest req = new SendSmsRequest() + .setPhoneNumbers(phones) + .setSignName(properties.getSignName()) + .setTemplateCode(templateId) + .setTemplateParam(JsonUtils.toJsonString(param)); + try { + SendSmsResponse resp = client.sendSms(req); + return SmsResult.builder() + .isSuccess("OK".equals(resp.getBody().getCode())) + .message(resp.getBody().getMessage()) + .response(JsonUtils.toJsonString(resp)) + .build(); + } catch (Exception e) { + throw new SmsException(e.getMessage()); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/SmsTemplate.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/SmsTemplate.java new file mode 100644 index 00000000..b19389a6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/SmsTemplate.java @@ -0,0 +1,26 @@ +package org.ruoyi.common.sms.core; + +import org.ruoyi.common.sms.entity.SmsResult; + +import java.util.Map; + +/** + * 短信模板 + * + * @author Lion Li + * @version 4.2.0 + */ +public interface SmsTemplate { + + /** + * 发送短信 + * + * @param phones 电话号(多个逗号分割) + * @param templateId 模板id + * @param param 模板对应参数 + * 阿里 需使用 模板变量名称对应内容 例如: code=1234 + * 腾讯 需使用 模板变量顺序对应内容 例如: 1=1234, 1为模板内第一个参数 + */ + SmsResult send(String phones, String templateId, Map param); + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/TencentSmsTemplate.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/TencentSmsTemplate.java new file mode 100644 index 00000000..4c2d642d --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/core/TencentSmsTemplate.java @@ -0,0 +1,82 @@ +package org.ruoyi.common.sms.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.sms.v20190711.SmsClient; +import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest; +import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse; +import com.tencentcloudapi.sms.v20190711.models.SendStatus; +import lombok.SneakyThrows; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.sms.config.properties.SmsProperties; +import org.ruoyi.common.sms.entity.SmsResult; +import org.ruoyi.common.sms.exception.SmsException; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Tencent 短信模板 + * + * @author Lion Li + * @version 4.2.0 + */ +public class TencentSmsTemplate implements SmsTemplate { + + private SmsProperties properties; + + private SmsClient client; + + @SneakyThrows(Exception.class) + public TencentSmsTemplate(SmsProperties smsProperties) { + this.properties = smsProperties; + Credential credential = new Credential(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint(smsProperties.getEndpoint()); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + this.client = new SmsClient(credential, "", clientProfile); + } + + @Override + public SmsResult send(String phones, String templateId, Map param) { + if (StringUtils.isBlank(phones)) { + throw new SmsException("手机号不能为空"); + } + if (StringUtils.isBlank(templateId)) { + throw new SmsException("模板ID不能为空"); + } + SendSmsRequest req = new SendSmsRequest(); + Set set = Arrays.stream(phones.split(StringUtils.SEPARATOR)).map(p -> "+86" + p).collect(Collectors.toSet()); + req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class)); + if (CollUtil.isNotEmpty(param)) { + req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class)); + } + req.setTemplateID(templateId); + req.setSign(properties.getSignName()); + req.setSmsSdkAppid(properties.getSdkAppId()); + try { + SendSmsResponse resp = client.SendSms(req); + SmsResult.SmsResultBuilder builder = SmsResult.builder() + .isSuccess(true) + .message("send success") + .response(JsonUtils.toJsonString(resp)); + for (SendStatus sendStatus : resp.getSendStatusSet()) { + if (!"Ok".equals(sendStatus.getCode())) { + builder.isSuccess(false).message(sendStatus.getMessage()); + break; + } + } + return builder.build(); + } catch (Exception e) { + throw new SmsException(e.getMessage()); + } + } + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/entity/SmsResult.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/entity/SmsResult.java new file mode 100644 index 00000000..02b02a95 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/entity/SmsResult.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.sms.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传返回体 + * + * @author Lion Li + */ +@Data +@Builder +public class SmsResult { + + /** + * 是否成功 + */ + private boolean isSuccess; + + /** + * 响应消息 + */ + private String message; + + /** + * 实际响应体 + *

    + * 可自行转换为 SDK 对应的 SendSmsResponse + */ + private String response; +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/exception/SmsException.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/exception/SmsException.java new file mode 100644 index 00000000..8abd05a1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/ruoyi/common/sms/exception/SmsException.java @@ -0,0 +1,19 @@ +package org.ruoyi.common.sms.exception; + +import java.io.Serial; + +/** + * Sms异常类 + * + * @author Lion Li + */ +public class SmsException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public SmsException(String msg) { + super(msg); + } + +} diff --git a/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 8486f346..ce95212c 100644 --- a/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.sms.config.SmsConfig +org.ruoyi.common.sms.config.SmsConfig diff --git a/ruoyi-common/ruoyi-common-tenant/pom.xml b/ruoyi-common/ruoyi-common-tenant/pom.xml index 663236d6..22e4ff0b 100644 --- a/ruoyi-common/ruoyi-common-tenant/pom.xml +++ b/ruoyi-common/ruoyi-common-tenant/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-mybatis - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/config/TenantConfig.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/config/TenantConfig.java new file mode 100644 index 00000000..6bf0c56c --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/config/TenantConfig.java @@ -0,0 +1,100 @@ +package org.ruoyi.common.tenant.config; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; +import org.ruoyi.common.mybatis.config.MybatisPlusConfig; +import org.ruoyi.common.redis.config.RedisConfig; +import org.ruoyi.common.redis.config.properties.RedissonProperties; +import org.ruoyi.common.tenant.core.TenantSaTokenDao; +import org.ruoyi.common.tenant.handle.PlusTenantLineHandler; +import org.ruoyi.common.tenant.handle.TenantKeyPrefixHandler; +import org.ruoyi.common.tenant.manager.TenantSpringCacheManager; +import org.ruoyi.common.tenant.properties.TenantProperties; +import org.redisson.config.ClusterServersConfig; +import org.redisson.config.SingleServerConfig; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import java.util.ArrayList; +import java.util.List; + +/** + * 租户配置类 + * + * @author Lion Li + */ +@EnableConfigurationProperties(TenantProperties.class) +@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class}) +@ConditionalOnProperty(value = "tenant.enable", havingValue = "true") +public class TenantConfig { + + /** + * 初始化租户配置 + */ + @Bean + public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor, + TenantProperties tenantProperties) { + List interceptors = new ArrayList<>(); + // 多租户插件 必须放到第一位 + interceptors.add(tenantLineInnerInterceptor(tenantProperties)); + interceptors.addAll(mybatisPlusInterceptor.getInterceptors()); + mybatisPlusInterceptor.setInterceptors(interceptors); + return true; + } + + /** + * 多租户插件 + */ + public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) { + return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties)); + } + + @Bean + public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) { + return config -> { + TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix()); + SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig"); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + // 设置多租户 redis key前缀 + singleServerConfig.setNameMapper(nameMapper); + ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig); + } + ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig"); + // 集群配置方式 参考下方注释 + if (ObjectUtil.isNotNull(clusterServersConfig)) { + // 设置多租户 redis key前缀 + clusterServersConfig.setNameMapper(nameMapper); + ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig); + } + }; + } + + /** + * 多租户缓存管理器 + */ + @Primary + @Bean + public CacheManager tenantCacheManager() { + return new TenantSpringCacheManager(); + } + + /** + * 多租户鉴权dao实现 + */ + @Primary + @Bean + public SaTokenDao tenantSaTokenDao() { + return new TenantSaTokenDao(); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantEntity.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantEntity.java new file mode 100644 index 00000000..61f5e982 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantEntity.java @@ -0,0 +1,21 @@ +package org.ruoyi.common.tenant.core; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +/** + * 租户基类 + * + * @author Michelle.Chung + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TenantEntity extends BaseEntity { + + /** + * 租户编号 + */ + private String tenantId; + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantSaTokenDao.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantSaTokenDao.java new file mode 100644 index 00000000..d1020e97 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantSaTokenDao.java @@ -0,0 +1,148 @@ +package org.ruoyi.common.tenant.core; + +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.satoken.core.dao.PlusSaTokenDao; + +import java.time.Duration; +import java.util.List; + +/** + * SaToken 认证数据持久层 适配多租户 + * + * @author Lion Li + */ +public class TenantSaTokenDao extends PlusSaTokenDao { + + @Override + public String get(String key) { + return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + @Override + public void set(String key, String value, long timeout) { + super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout); + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout)); + } + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout); + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key); + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/exception/TenantException.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/exception/TenantException.java new file mode 100644 index 00000000..b361f443 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/exception/TenantException.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.tenant.exception; + +import org.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 租户异常类 + * + * @author Lion Li + */ +public class TenantException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public TenantException(String code, Object... args) { + super("tenant", code, args, null); + } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/PlusTenantLineHandler.java new file mode 100644 index 00000000..2f627a6f --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/PlusTenantLineHandler.java @@ -0,0 +1,59 @@ +package org.ruoyi.common.tenant.handle; + +import cn.hutool.core.collection.ListUtil; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import lombok.AllArgsConstructor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.StringValue; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.common.tenant.properties.TenantProperties; + +import java.util.List; + +/** + * 自定义租户处理器 + * + * @author Lion Li + */ +@AllArgsConstructor +public class PlusTenantLineHandler implements TenantLineHandler { + + private final TenantProperties tenantProperties; + + @Override + public Expression getTenantId() { + String tenantId = LoginHelper.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + return new NullValue(); + } + String dynamicTenantId = TenantHelper.getDynamic(); + if (StringUtils.isNotBlank(dynamicTenantId)) { + // 返回动态租户 + return new StringValue(dynamicTenantId); + } + // 返回固定租户 + return new StringValue(tenantId); + } + + @Override + public boolean ignoreTable(String tableName) { + String tenantId = LoginHelper.getTenantId(); + // 判断是否有租户 + if (StringUtils.isNotBlank(tenantId)) { + // 不需要过滤租户的表 + List excludes = tenantProperties.getExcludes(); + // 非业务表 + List tables = ListUtil.toList( + "gen_table", + "gen_table_column" + ); + tables.addAll(excludes); + return tables.contains(tableName); + } + return true; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java new file mode 100644 index 00000000..990d930d --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/handle/TenantKeyPrefixHandler.java @@ -0,0 +1,58 @@ +package org.ruoyi.common.tenant.handle; + +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.redis.handler.KeyPrefixHandler; +import org.ruoyi.common.tenant.helper.TenantHelper; + +/** + * 多租户redis缓存key前缀处理 + * + * @author Lion Li + */ +public class TenantKeyPrefixHandler extends KeyPrefixHandler { + + public TenantKeyPrefixHandler(String keyPrefix) { + super(keyPrefix); + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.map(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.startsWith(name, tenantId)) { + // 如果存在则直接返回 + return super.map(name); + } + return super.map(tenantId + ":" + name); + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + String unmap = super.unmap(name); + if (StringUtils.isBlank(unmap)) { + return null; + } + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.unmap(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.startsWith(unmap, tenantId)) { + // 如果存在则删除 + return unmap.substring((tenantId + ":").length()); + } + return unmap; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/helper/TenantHelper.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/helper/TenantHelper.java new file mode 100644 index 00000000..7ce74f7c --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/helper/TenantHelper.java @@ -0,0 +1,140 @@ +package org.ruoyi.common.tenant.helper; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.spring.SpringMVCUtil; +import cn.hutool.core.convert.Convert; +import com.alibaba.ttl.TransmittableThreadLocal; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; + +import java.util.function.Supplier; + +/** + * 租户助手 + * + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TenantHelper { + + private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"; + + private static final ThreadLocal TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>(); + + /** + * 租户功能是否启用 + */ + public static boolean isEnable() { + return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false); + } + + /** + * 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭) + */ + public static void enableIgnore() { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build()); + } + + /** + * 关闭忽略租户 + */ + public static void disableIgnore() { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } + + /** + * 在忽略租户中执行 + * + * @param handle 处理执行方法 + */ + public static void ignore(Runnable handle) { + enableIgnore(); + try { + handle.run(); + } finally { + disableIgnore(); + } + } + + /** + * 在忽略租户中执行 + * + * @param handle 处理执行方法 + */ + public static T ignore(Supplier handle) { + enableIgnore(); + try { + return handle.get(); + } finally { + disableIgnore(); + } + } + + /** + * 设置动态租户(一直有效 需要手动清理) + *

    + * 如果为非web环境 那么只在当前线程内生效 + */ + public static void setDynamic(String tenantId) { + if (!SpringMVCUtil.isWeb()) { + TEMP_DYNAMIC_TENANT.set(tenantId); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.setCacheObject(cacheKey, tenantId); + SaHolder.getStorage().set(cacheKey, tenantId); + } + + /** + * 获取动态租户(一直有效 需要手动清理) + *

    + * 如果为非web环境 那么只在当前线程内生效 + */ + public static String getDynamic() { + if (!SpringMVCUtil.isWeb()) { + return TEMP_DYNAMIC_TENANT.get(); + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + String tenantId = (String) SaHolder.getStorage().get(cacheKey); + if (StringUtils.isNotBlank(tenantId)) { + return tenantId; + } + tenantId = RedisUtils.getCacheObject(cacheKey); + SaHolder.getStorage().set(cacheKey, tenantId); + return tenantId; + } + + /** + * 清除动态租户 + */ + public static void clearDynamic() { + if (!SpringMVCUtil.isWeb()) { + TEMP_DYNAMIC_TENANT.remove(); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.deleteObject(cacheKey); + SaHolder.getStorage().delete(cacheKey); + } + + /** + * 获取当前租户id(动态租户优先) + */ + public static String getTenantId() { + String tenantId = TenantHelper.getDynamic(); + if (StringUtils.isBlank(tenantId)) { + tenantId = LoginHelper.getTenantId(); + } + return tenantId; + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/manager/TenantSpringCacheManager.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/manager/TenantSpringCacheManager.java new file mode 100644 index 00000000..9de69225 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/manager/TenantSpringCacheManager.java @@ -0,0 +1,32 @@ +package org.ruoyi.common.tenant.manager; + +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.redis.manager.PlusSpringCacheManager; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.springframework.cache.Cache; + +/** + * 重写 cacheName 处理方法 支持多租户 + * + * @author Lion Li + */ +public class TenantSpringCacheManager extends PlusSpringCacheManager { + + public TenantSpringCacheManager() { + } + + @Override + public Cache getCache(String name) { + if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { + return super.getCache(name); + } + String tenantId = TenantHelper.getTenantId(); + if (StringUtils.startsWith(name, tenantId)) { + // 如果存在则直接返回 + return super.getCache(name); + } + return super.getCache(tenantId + ":" + name); + } + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/properties/TenantProperties.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/properties/TenantProperties.java new file mode 100644 index 00000000..4513a0a9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/properties/TenantProperties.java @@ -0,0 +1,27 @@ +package org.ruoyi.common.tenant.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * 租户 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "tenant") +public class TenantProperties { + + /** + * 是否启用 + */ + private Boolean enable; + + /** + * 排除表 + */ + private List excludes; + +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ba20df88..c21eee67 100644 --- a/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.xmzs.common.tenant.config.TenantConfig +org.ruoyi.common.tenant.config.TenantConfig diff --git a/ruoyi-common/ruoyi-common-translation/pom.xml b/ruoyi-common/ruoyi-common-translation/pom.xml index 5e49cdbf..eebe383f 100644 --- a/ruoyi-common/ruoyi-common-translation/pom.xml +++ b/ruoyi-common/ruoyi-common-translation/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -19,7 +19,7 @@ - com.xmzs + org.ruoyi ruoyi-common-json diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/Translation.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/Translation.java new file mode 100644 index 00000000..95687b2e --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/Translation.java @@ -0,0 +1,39 @@ +package org.ruoyi.common.translation.annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.ruoyi.common.translation.core.handler.TranslationHandler; + +import java.lang.annotation.*; + +/** + * 通用翻译注解 + * + * @author Lion Li + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Documented +@JacksonAnnotationsInside +@JsonSerialize(using = TranslationHandler.class) +public @interface Translation { + + /** + * 类型 (需与实现类上的 {@link TranslationType} 注解type对应) + *

    + * 默认取当前字段的值 如果设置了 @{@link Translation#mapper()} 则取映射字段的值 + */ + String type(); + + /** + * 映射字段 (如果不为空则取此字段的值) + */ + String mapper() default ""; + + /** + * 其他条件 例如: 字典type(sys_user_sex) + */ + String other() default ""; + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/TranslationType.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/TranslationType.java new file mode 100644 index 00000000..97b7f41c --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/annotation/TranslationType.java @@ -0,0 +1,23 @@ +package org.ruoyi.common.translation.annotation; + +import org.ruoyi.common.translation.core.TranslationInterface; + +import java.lang.annotation.*; + +/** + * 翻译类型注解 (标注到{@link TranslationInterface} 的实现类) + * + * @author Lion Li + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface TranslationType { + + /** + * 类型 + */ + String type(); + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/config/TranslationConfig.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/config/TranslationConfig.java new file mode 100644 index 00000000..bc564138 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/config/TranslationConfig.java @@ -0,0 +1,50 @@ +package org.ruoyi.common.translation.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.translation.annotation.TranslationType; +import org.ruoyi.common.translation.core.TranslationInterface; +import org.ruoyi.common.translation.core.handler.TranslationBeanSerializerModifier; +import org.ruoyi.common.translation.core.handler.TranslationHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 翻译模块配置类 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +public class TranslationConfig { + + @Autowired + private List> list; + + @Autowired + private ObjectMapper objectMapper; + + @PostConstruct + public void init() { + Map> map = new HashMap<>(list.size()); + for (TranslationInterface trans : list) { + if (trans.getClass().isAnnotationPresent(TranslationType.class)) { + TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class); + map.put(annotation.type(), trans); + } else { + log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!"); + } + } + TranslationHandler.TRANSLATION_MAPPER.putAll(map); + // 设置 Bean 序列化修改器 + objectMapper.setSerializerFactory( + objectMapper.getSerializerFactory() + .withSerializerModifier(new TranslationBeanSerializerModifier())); + } + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/constant/TransConstant.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/constant/TransConstant.java new file mode 100644 index 00000000..8a29ca31 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/constant/TransConstant.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.translation.constant; + +/** + * 翻译常量 + * + * @author Lion Li + */ +public interface TransConstant { + + /** + * 用户id转账号 + */ + String USER_ID_TO_NAME = "user_id_to_name"; + + /** + * 部门id转名称 + */ + String DEPT_ID_TO_NAME = "dept_id_to_name"; + + /** + * 字典type转label + */ + String DICT_TYPE_TO_LABEL = "dict_type_to_label"; + + /** + * ossId转url + */ + String OSS_ID_TO_URL = "oss_id_to_url"; + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/TranslationInterface.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/TranslationInterface.java new file mode 100644 index 00000000..56481d6a --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/TranslationInterface.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.translation.core; + +import org.ruoyi.common.translation.annotation.TranslationType; + +/** + * 翻译接口 (实现类需标注 {@link TranslationType} 注解标明翻译类型) + * + * @author Lion Li + */ +public interface TranslationInterface { + + /** + * 翻译 + * + * @param key 需要被翻译的键(不为空) + * @param other 其他参数 + * @return 返回键对应的值 + */ + T translation(Object key, String other); +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationBeanSerializerModifier.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationBeanSerializerModifier.java new file mode 100644 index 00000000..e7cbaa94 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationBeanSerializerModifier.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.translation.core.handler; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; + +import java.util.List; + +/** + * Bean 序列化修改器 解决 Null 被单独处理问题 + * + * @author Lion Li + */ +public class TranslationBeanSerializerModifier extends BeanSerializerModifier { + + @Override + public List changeProperties(SerializationConfig config, BeanDescription beanDesc, + List beanProperties) { + for (BeanPropertyWriter writer : beanProperties) { + // 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理 + if (writer.getSerializer() instanceof TranslationHandler serializer) { + writer.assignNullSerializer(serializer); + } + } + return beanProperties; + } + +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationHandler.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationHandler.java new file mode 100644 index 00000000..a047c26e --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/handler/TranslationHandler.java @@ -0,0 +1,65 @@ +package org.ruoyi.common.translation.core.handler; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.reflect.ReflectUtils; +import org.ruoyi.common.translation.annotation.Translation; +import org.ruoyi.common.translation.core.TranslationInterface; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 翻译处理器 + * + * @author Lion Li + */ +@Slf4j +public class TranslationHandler extends JsonSerializer implements ContextualSerializer { + + /** + * 全局翻译实现类映射器 + */ + public static final Map> TRANSLATION_MAPPER = new ConcurrentHashMap<>(); + + private Translation translation; + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + TranslationInterface trans = TRANSLATION_MAPPER.get(translation.type()); + if (ObjectUtil.isNotNull(trans)) { + // 如果映射字段不为空 则取映射字段的值 + if (StringUtils.isNotBlank(translation.mapper())) { + value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper()); + } + // 如果为 null 直接写出 + if (ObjectUtil.isNull(value)) { + gen.writeNull(); + return; + } + Object result = trans.translation(value, translation.other()); + gen.writeObject(result); + } else { + gen.writeObject(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + Translation translation = property.getAnnotation(Translation.class); + if (Objects.nonNull(translation)) { + this.translation = translation; + return this; + } + return prov.findValueSerializer(property.getType(), property); + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DeptNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DeptNameTranslationImpl.java new file mode 100644 index 00000000..e88ddd48 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DeptNameTranslationImpl.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.translation.core.impl; + +import lombok.AllArgsConstructor; +import org.ruoyi.common.core.service.DeptService; +import org.ruoyi.common.translation.annotation.TranslationType; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.common.translation.core.TranslationInterface; + +/** + * 部门翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.DEPT_ID_TO_NAME) +public class DeptNameTranslationImpl implements TranslationInterface { + + private final DeptService deptService; + + @Override + public String translation(Object key, String other) { + if (key instanceof String ids) { + return deptService.selectDeptNameByIds(ids); + } else if (key instanceof Long id) { + return deptService.selectDeptNameByIds(id.toString()); + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DictTypeTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DictTypeTranslationImpl.java new file mode 100644 index 00000000..57da4e90 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/DictTypeTranslationImpl.java @@ -0,0 +1,28 @@ +package org.ruoyi.common.translation.core.impl; + +import lombok.AllArgsConstructor; +import org.ruoyi.common.core.service.DictService; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.translation.annotation.TranslationType; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.common.translation.core.TranslationInterface; + +/** + * 字典翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL) +public class DictTypeTranslationImpl implements TranslationInterface { + + private final DictService dictService; + + @Override + public String translation(Object key, String other) { + if (key instanceof String dictValue && StringUtils.isNotBlank(other)) { + return dictService.getDictLabel(other, dictValue); + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/OssUrlTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/OssUrlTranslationImpl.java new file mode 100644 index 00000000..5bcb5c4b --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/OssUrlTranslationImpl.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.translation.core.impl; + +import lombok.AllArgsConstructor; +import org.ruoyi.common.core.service.OssService; +import org.ruoyi.common.translation.annotation.TranslationType; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.common.translation.core.TranslationInterface; + +/** + * OSS翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.OSS_ID_TO_URL) +public class OssUrlTranslationImpl implements TranslationInterface { + + private final OssService ossService; + + @Override + public String translation(Object key, String other) { + if (key instanceof String ids) { + return ossService.selectUrlByIds(ids); + } else if (key instanceof Long id) { + return ossService.selectUrlByIds(id.toString()); + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/UserNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/UserNameTranslationImpl.java new file mode 100644 index 00000000..ff921d74 --- /dev/null +++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/ruoyi/common/translation/core/impl/UserNameTranslationImpl.java @@ -0,0 +1,27 @@ +package org.ruoyi.common.translation.core.impl; + +import lombok.AllArgsConstructor; +import org.ruoyi.common.core.service.UserService; +import org.ruoyi.common.translation.annotation.TranslationType; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.common.translation.core.TranslationInterface; + +/** + * 用户名翻译实现 + * + * @author Lion Li + */ +@AllArgsConstructor +@TranslationType(type = TransConstant.USER_ID_TO_NAME) +public class UserNameTranslationImpl implements TranslationInterface { + + private final UserService userService; + + @Override + public String translation(Object key, String other) { + if (key instanceof Long id) { + return userService.selectUserNameById(id); + } + return null; + } +} diff --git a/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 378032bd..c5cc3b97 100644 --- a/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,5 @@ -com.xmzs.common.translation.config.TranslationConfig -com.xmzs.common.translation.core.impl.DeptNameTranslationImpl -com.xmzs.common.translation.core.impl.DictTypeTranslationImpl -com.xmzs.common.translation.core.impl.OssUrlTranslationImpl -com.xmzs.common.translation.core.impl.UserNameTranslationImpl +org.ruoyi.common.translation.config.TranslationConfig +org.ruoyi.common.translation.core.impl.DeptNameTranslationImpl +org.ruoyi.common.translation.core.impl.DictTypeTranslationImpl +org.ruoyi.common.translation.core.impl.OssUrlTranslationImpl +org.ruoyi.common.translation.core.impl.UserNameTranslationImpl diff --git a/ruoyi-common/ruoyi-common-web/pom.xml b/ruoyi-common/ruoyi-common-web/pom.xml index 7bac726b..04fdb563 100644 --- a/ruoyi-common/ruoyi-common-web/pom.xml +++ b/ruoyi-common/ruoyi-common-web/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-common ${revision} ../pom.xml @@ -18,12 +18,12 @@ - com.xmzs + org.ruoyi ruoyi-common-json - com.xmzs + org.ruoyi ruoyi-common-redis diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/CaptchaConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/CaptchaConfig.java new file mode 100644 index 00000000..137cbb5c --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/CaptchaConfig.java @@ -0,0 +1,65 @@ +package org.ruoyi.common.web.config; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.ShearCaptcha; +import org.ruoyi.common.web.config.properties.CaptchaProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; + +import java.awt.*; + +/** + * 验证码配置 + * + * @author Lion Li + */ +@AutoConfiguration +@EnableConfigurationProperties(CaptchaProperties.class) +public class CaptchaConfig { + + private static final int WIDTH = 160; + private static final int HEIGHT = 60; + private static final Color BACKGROUND = Color.PINK; + private static final Font FONT = new Font("Arial", Font.BOLD, 48); + + /** + * 圆圈干扰验证码 + */ + @Lazy + @Bean + public CircleCaptcha circleCaptcha() { + CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + + /** + * 线段干扰的验证码 + */ + @Lazy + @Bean + public LineCaptcha lineCaptcha() { + LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + + /** + * 扭曲干扰验证码 + */ + @Lazy + @Bean + public ShearCaptcha shearCaptcha() { + ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/FilterConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/FilterConfig.java new file mode 100644 index 00000000..1037e936 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/FilterConfig.java @@ -0,0 +1,53 @@ +package org.ruoyi.common.web.config; + +import jakarta.servlet.DispatcherType; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.web.config.properties.XssProperties; +import org.ruoyi.common.web.filter.RepeatableFilter; +import org.ruoyi.common.web.filter.XssFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +import java.util.HashMap; +import java.util.Map; + +/** + * Filter配置 + * + * @author Lion Li + */ +@AutoConfiguration +@EnableConfigurationProperties(XssProperties.class) +public class FilterConfig { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration(XssProperties xssProperties) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR)); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap<>(); + initParameters.put("excludes", xssProperties.getExcludes()); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/I18nConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/I18nConfig.java new file mode 100644 index 00000000..e5e35aec --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/I18nConfig.java @@ -0,0 +1,22 @@ +package org.ruoyi.common.web.config; + +import org.ruoyi.common.web.core.I18nLocaleResolver; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.LocaleResolver; + +/** + * 国际化配置 + * + * @author Lion Li + */ +@AutoConfiguration(before = WebMvcAutoConfiguration.class) +public class I18nConfig { + + @Bean + public LocaleResolver localeResolver() { + return new I18nLocaleResolver(); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/ResourcesConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/ResourcesConfig.java new file mode 100644 index 00000000..3acd4302 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/ResourcesConfig.java @@ -0,0 +1,52 @@ +package org.ruoyi.common.web.config; + +import org.ruoyi.common.web.interceptor.PlusWebInvokeTimeInterceptor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 通用配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class ResourcesConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 全局访问性能拦截 + registry.addInterceptor(new PlusWebInvokeTimeInterceptor()); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/UndertowConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/UndertowConfig.java new file mode 100644 index 00000000..3f421968 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/UndertowConfig.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.web.config; + +import io.undertow.server.DefaultByteBufferPool; +import io.undertow.websockets.jsr.WebSocketDeploymentInfo; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; + +/** + * Undertow 自定义配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class UndertowConfig implements WebServerFactoryCustomizer { + + /** + * 设置 Undertow 的 websocket 缓冲池 + */ + @Override + public void customize(UndertowServletWebServerFactory factory) { + // 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配 + factory.addDeploymentInfoCustomizers(deploymentInfo -> { + WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); + webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512)); + deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo); + }); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/CaptchaProperties.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/CaptchaProperties.java new file mode 100644 index 00000000..0f271488 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/CaptchaProperties.java @@ -0,0 +1,38 @@ +package org.ruoyi.common.web.config.properties; + +import lombok.Data; +import org.ruoyi.common.web.enums.CaptchaCategory; +import org.ruoyi.common.web.enums.CaptchaType; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 验证码 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "captcha") +public class CaptchaProperties { + + private Boolean enable; + + /** + * 验证码类型 + */ + private CaptchaType type; + + /** + * 验证码类别 + */ + private CaptchaCategory category; + + /** + * 数字验证码位数 + */ + private Integer numberLength; + + /** + * 字符验证码长度 + */ + private Integer charLength; +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/XssProperties.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/XssProperties.java new file mode 100644 index 00000000..9d38ab8c --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/config/properties/XssProperties.java @@ -0,0 +1,30 @@ +package org.ruoyi.common.web.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * xss过滤 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "xss") +public class XssProperties { + + /** + * 过滤开关 + */ + private String enabled; + + /** + * 排除链接(多个用逗号分隔) + */ + private String excludes; + + /** + * 匹配链接 + */ + private String urlPatterns; + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/BaseController.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/BaseController.java new file mode 100644 index 00000000..ee7d2dab --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/BaseController.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.web.core; + +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * web层通用数据处理 + * + * @author Lion Li + */ +public class BaseController { + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected R toAjax(int rows) { + return rows > 0 ? R.ok() : R.fail(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected R toAjax(boolean result) { + return result ? R.ok() : R.fail(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) { + return StringUtils.format("redirect:{}", url); + } + +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/I18nLocaleResolver.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/I18nLocaleResolver.java new file mode 100644 index 00000000..71a4e013 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/core/I18nLocaleResolver.java @@ -0,0 +1,31 @@ +package org.ruoyi.common.web.core; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.LocaleResolver; + +import java.util.Locale; + +/** + * 获取请求头国际化信息 + * + * @author Lion Li + */ +public class I18nLocaleResolver implements LocaleResolver { + + @Override + public Locale resolveLocale(HttpServletRequest httpServletRequest) { + String language = httpServletRequest.getHeader("content-language"); + Locale locale = Locale.getDefault(); + if (language != null && language.length() > 0) { + String[] split = language.split("_"); + locale = new Locale(split[0], split[1]); + } + return locale; + } + + @Override + public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { + + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaCategory.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaCategory.java new file mode 100644 index 00000000..68372cb9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaCategory.java @@ -0,0 +1,35 @@ +package org.ruoyi.common.web.enums; + +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.ShearCaptcha; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 验证码类别 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaCategory { + + /** + * 线段干扰 + */ + LINE(LineCaptcha.class), + + /** + * 圆圈干扰 + */ + CIRCLE(CircleCaptcha.class), + + /** + * 扭曲干扰 + */ + SHEAR(ShearCaptcha.class); + + private final Class clazz; +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaType.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaType.java new file mode 100644 index 00000000..12b47719 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/enums/CaptchaType.java @@ -0,0 +1,29 @@ +package org.ruoyi.common.web.enums; + +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.RandomGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.ruoyi.common.web.utils.UnsignedMathGenerator; + +/** + * 验证码类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaType { + + /** + * 数字 + */ + MATH(UnsignedMathGenerator.class), + + /** + * 字符 + */ + CHAR(RandomGenerator.class); + + private final Class clazz; +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatableFilter.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatableFilter.java new file mode 100644 index 00000000..ca68db44 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatableFilter.java @@ -0,0 +1,40 @@ +package org.ruoyi.common.web.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import org.ruoyi.common.core.utils.StringUtils; +import org.springframework.http.MediaType; + +import java.io.IOException; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatedlyRequestWrapper.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 00000000..44570b34 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,67 @@ +package org.ruoyi.common.web.filter; + +import cn.hutool.core.io.IoUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.ruoyi.common.core.constant.Constants; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = IoUtil.readBytes(request.getInputStream(), false); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() throws IOException { + return bais.read(); + } + + @Override + public int available() throws IOException { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssFilter.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssFilter.java new file mode 100644 index 00000000..edb604b8 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssFilter.java @@ -0,0 +1,62 @@ +package org.ruoyi.common.web.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.ruoyi.common.core.utils.StringUtils; +import org.springframework.http.HttpMethod; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter { + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) { + String[] url = tempExcludes.split(StringUtils.SEPARATOR); + for (int i = 0; url != null && i < url.length; i++) { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() { + + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssHttpServletRequestWrapper.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 00000000..84ebe4bb --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,97 @@ +package org.ruoyi.common.web.filter; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.ruoyi.common.core.utils.StringUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values != null) { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) { + // 防xss攻击和过滤前后空格 + escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 非json类型,直接返回 + if (!isJsonRequest()) { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(json)) { + return super.getInputStream(); + } + + // xss过滤 + json = HtmlUtil.cleanHtmlTag(json).trim(); + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public int available() throws IOException { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + */ + public boolean isJsonRequest() { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/interceptor/PlusWebInvokeTimeInterceptor.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/interceptor/PlusWebInvokeTimeInterceptor.java new file mode 100644 index 00000000..f728df30 --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/interceptor/PlusWebInvokeTimeInterceptor.java @@ -0,0 +1,113 @@ +package org.ruoyi.common.web.interceptor; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.web.filter.RepeatedlyRequestWrapper; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import java.io.BufferedReader; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * web的调用时间统计拦截器 + * dev环境有效 + * + * @author Lion Li + * @since 3.3.0 + */ +@Slf4j +public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor { + + private final TransmittableThreadLocal invokeTimeTL = new TransmittableThreadLocal<>(); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String url = request.getMethod() + " " + request.getRequestURI(); + String domainName = request.getServerName(); + log.info("域名信息:{}",domainName); + + String requestURI = request.getRequestURI(); + List urls = whitelistUrls(); + boolean isWhitelisted = urls.stream().anyMatch(requestURI::startsWith); + + if (!isWhitelisted){ + // 根据授权编号查询激活状态 +// ConfigService configService = SpringUtils.context().getBean(ConfigService.class); +// String authNo = configService.getConfigValue("sys", "authcode"); +// if(!configService.checkAuth(authNo,domainName)){ +// throw new BaseException("系统未激活,请联系管理员授权"); +// } + } + + // 打印请求参数 + if (isJsonRequest(request)) { + String jsonParam = ""; + if (request instanceof RepeatedlyRequestWrapper) { + BufferedReader reader = request.getReader(); + jsonParam = IoUtil.read(reader); + } + log.debug("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); + } else { + Map parameterMap = request.getParameterMap(); + if (MapUtil.isNotEmpty(parameterMap)) { + String parameters = JsonUtils.toJsonString(parameterMap); + log.debug("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters); + } else { + log.debug("[PLUS]开始请求 => URL[{}],无参数", url); + } + } + + StopWatch stopWatch = new StopWatch(); + invokeTimeTL.set(stopWatch); + stopWatch.start(); + + return true; + } + + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + log.debug("结束请求 => URL[{}]", request.getRequestURI()); + } + + /** + * 判断本次请求的数据类型是否为json + * + * @param request request + * @return boolean + */ + private boolean isJsonRequest(HttpServletRequest request) { + String contentType = request.getContentType(); + if (contentType != null) { + return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE); + } + return false; + } + + // 授权白名单 + public List whitelistUrls() { + return Arrays.asList( + "/chat/config", + "/pay", + "/weixin", + "/user/qrcode", + "/user/login/qrcode" + ); + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/utils/UnsignedMathGenerator.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/utils/UnsignedMathGenerator.java new file mode 100644 index 00000000..28e0d26e --- /dev/null +++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/utils/UnsignedMathGenerator.java @@ -0,0 +1,88 @@ +package org.ruoyi.common.web.utils; + +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.core.math.Calculator; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.RandomUtil; +import org.ruoyi.common.core.utils.StringUtils; + +import java.io.Serial; + +/** + * 无符号计算生成器 + * + * @author Lion Li + */ +public class UnsignedMathGenerator implements CodeGenerator { + + @Serial + private static final long serialVersionUID = -5514819971774091076L; + + private static final String OPERATORS = "+-*"; + + /** + * 参与计算数字最大长度 + */ + private final int numberLength; + + /** + * 构造 + */ + public UnsignedMathGenerator() { + this(2); + } + + /** + * 构造 + * + * @param numberLength 参与计算最大数字位数 + */ + public UnsignedMathGenerator(int numberLength) { + this.numberLength = numberLength; + } + + @Override + public String generate() { + final int limit = getLimit(); + int a = RandomUtil.randomInt(limit); + int b = RandomUtil.randomInt(limit); + String max = Integer.toString(Math.max(a,b)); + String min = Integer.toString(Math.min(a,b)); + max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE); + min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE); + + return max + RandomUtil.randomChar(OPERATORS) + min + '='; + } + + @Override + public boolean verify(String code, String userInputCode) { + int result; + try { + result = Integer.parseInt(userInputCode); + } catch (NumberFormatException e) { + // 用户输入非数字 + return false; + } + + final int calculateResult = (int) Calculator.conversion(code); + return result == calculateResult; + } + + /** + * 获取验证码长度 + * + * @return 验证码长度 + */ + public int getLength() { + return this.numberLength * 2 + 2; + } + + /** + * 根据长度获取参与计算数字最大值 + * + * @return 最大值 + */ + private int getLimit() { + return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength)); + } +} diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ae11f013..e1eb5138 100644 --- a/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,5 @@ -com.xmzs.common.web.config.CaptchaConfig -com.xmzs.common.web.config.FilterConfig -com.xmzs.common.web.config.I18nConfig -com.xmzs.common.web.config.ResourcesConfig -com.xmzs.common.web.config.UndertowConfig +org.ruoyi.common.web.config.CaptchaConfig +org.ruoyi.common.web.config.FilterConfig +org.ruoyi.common.web.config.I18nConfig +org.ruoyi.common.web.config.ResourcesConfig +org.ruoyi.common.web.config.UndertowConfig diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index a6d07b22..94d0df76 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ruoyi-ai - com.xmzs + org.ruoyi ${revision} ../pom.xml @@ -18,9 +18,7 @@ ruoyi-demo - ruoyi-generator - ruoyi-job - ruoyi-midjourney + ruoyi-fusion ruoyi-system diff --git a/ruoyi-modules/ruoyi-demo/pom.xml b/ruoyi-modules/ruoyi-demo/pom.xml index 877f8641..715c8b08 100644 --- a/ruoyi-modules/ruoyi-demo/pom.xml +++ b/ruoyi-modules/ruoyi-demo/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-modules ${revision} ../pom.xml @@ -20,82 +20,82 @@ - com.xmzs + org.ruoyi ruoyi-common-core - com.xmzs + org.ruoyi ruoyi-common-doc - com.xmzs + org.ruoyi ruoyi-common-sms - com.xmzs + org.ruoyi ruoyi-common-mail - com.xmzs + org.ruoyi ruoyi-common-redis - com.xmzs + org.ruoyi ruoyi-common-idempotent - com.xmzs + org.ruoyi ruoyi-common-mybatis - com.xmzs + org.ruoyi ruoyi-common-log - com.xmzs + org.ruoyi ruoyi-common-excel - com.xmzs + org.ruoyi ruoyi-common-web - com.xmzs + org.ruoyi ruoyi-common-ratelimiter - com.xmzs + org.ruoyi ruoyi-common-translation - com.xmzs + org.ruoyi ruoyi-common-sensitive - com.xmzs + org.ruoyi ruoyi-common-encrypt - com.xmzs + org.ruoyi ruoyi-common-tenant - com.xmzs + org.ruoyi ruoyi-common-pay diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailController.java new file mode 100644 index 00000000..4603ae39 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/MailController.java @@ -0,0 +1,52 @@ +package org.ruoyi.demo.controller; + +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.mail.utils.MailUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; + + +/** + * 邮件发送案例 + * + * @author Michelle.Chung + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/mail") +public class MailController { + + /** + * 发送邮件 + * + * @param to 接收人 + * @param subject 标题 + * @param text 内容 + */ + @GetMapping("/sendSimpleMessage") + public R sendSimpleMessage(String to, String subject, String text) { + MailUtils.sendText(to, subject, text); + return R.ok(); + } + + /** + * 发送邮件(带附件) + * + * @param to 接收人 + * @param subject 标题 + * @param text 内容 + * @param filePath 附件路径 + */ + @GetMapping("/sendMessageWithAttachment") + public R sendMessageWithAttachment(String to, String subject, String text, String filePath) { + MailUtils.sendText(to, subject, text, new File(filePath)); + return R.ok(); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java new file mode 100644 index 00000000..bd9e0830 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisCacheController.java @@ -0,0 +1,95 @@ +package org.ruoyi.demo.controller; + +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.Duration; + +/** + * spring-cache 演示案例 + * + * @author Lion Li + */ +// 类级别 缓存统一配置 +//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE) +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/cache") +public class RedisCacheController { + + /** + * 测试 @Cacheable + *

    + * 表示这个方法有了缓存的功能,方法的返回值会被缓存下来 + * 下一次调用该方法前,会去检查是否缓存中已经有值 + * 如果有就直接返回,不调用方法 + * 如果没有,就调用方法,然后把结果缓存起来 + * 这个注解「一般用在查询方法上」 + *

    + * 重点说明: 缓存注解严谨与其他筛选数据功能一起使用 + * 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题 + *

    + * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 + */ + @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null") + @GetMapping("/test1") + public R test1(String key, String value) { + return R.ok("操作成功", value); + } + + /** + * 测试 @CachePut + *

    + * 加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用 + * 它「通常用在新增或者实时更新方法上」 + *

    + * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 + */ + @CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") + @GetMapping("/test2") + public R test2(String key, String value) { + return R.ok("操作成功", value); + } + + /** + * 测试 @CacheEvict + *

    + * 使用了CacheEvict注解的方法,会清空指定缓存 + * 「一般用在删除的方法上」 + *

    + * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数 + */ + @CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null") + @GetMapping("/test3") + public R test3(String key, String value) { + return R.ok("操作成功", value); + } + + /** + * 测试设置过期时间 + * 手动设置过期时间10秒 + * 11秒后获取 判断是否相等 + */ + @GetMapping("/test6") + public R test6(String key, String value) { + RedisUtils.setCacheObject(key, value); + boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10)); + System.out.println("***********" + flag); + try { + Thread.sleep(11 * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Object obj = RedisUtils.getCacheObject(key); + return R.ok(value.equals(obj)); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java new file mode 100644 index 00000000..61154cfd --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisLockController.java @@ -0,0 +1,71 @@ +package org.ruoyi.demo.controller; + +import com.baomidou.lock.LockInfo; +import com.baomidou.lock.LockTemplate; +import com.baomidou.lock.annotation.Lock4j; +import com.baomidou.lock.executor.RedissonLockExecutor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalTime; + + +/** + * 测试分布式锁的样例 + * + * @author shenxinquan + */ +@Slf4j +@RestController +@RequestMapping("/demo/redisLock") +public class RedisLockController { + + @Autowired + private LockTemplate lockTemplate; + + /** + * 测试lock4j 注解 + */ + @Lock4j(keys = {"#key"}) + @GetMapping("/testLock4j") + public R testLock4j(String key, String value) { + System.out.println("start:" + key + ",time:" + LocalTime.now().toString()); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("end :" + key + ",time:" + LocalTime.now().toString()); + return R.ok("操作成功", value); + } + + /** + * 测试lock4j 工具 + */ + @GetMapping("/testLock4jLockTemplate") + public R testLock4jLockTemplate(String key, String value) { + final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class); + if (null == lockInfo) { + throw new RuntimeException("业务处理中,请稍后再试"); + } + // 获取锁成功,处理业务 + try { + try { + Thread.sleep(8000); + } catch (InterruptedException e) { + // + } + System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName()); + } finally { + //释放锁 + lockTemplate.releaseLock(lockInfo); + } + //结束 + return R.ok("操作成功", value); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java new file mode 100644 index 00000000..be6e270c --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisPubSubController.java @@ -0,0 +1,47 @@ +package org.ruoyi.demo.controller; + +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Redis 发布订阅 演示案例 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/redis/pubsub") +public class RedisPubSubController { + + /** + * 发布消息 + * + * @param key 通道Key + * @param value 发送内容 + */ + @GetMapping("/pub") + public R pub(String key, String value) { + RedisUtils.publish(key, value, consumer -> { + System.out.println("发布通道 => " + key + ", 发送值 => " + value); + }); + return R.ok("操作成功"); + } + + /** + * 订阅消息 + * + * @param key 通道Key + */ + @GetMapping("/sub") + public R sub(String key) { + RedisUtils.subscribe(key, String.class, msg -> { + System.out.println("订阅通道 => " + key + ", 接收值 => " + msg); + }); + return R.ok("操作成功"); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java new file mode 100644 index 00000000..915dcaa2 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/RedisRateLimiterController.java @@ -0,0 +1,64 @@ +package org.ruoyi.demo.controller; + +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.ratelimiter.annotation.RateLimiter; +import org.ruoyi.common.ratelimiter.enums.LimitType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * 测试分布式限流样例 + * + * @author Lion Li + */ +@Slf4j +@RestController +@RequestMapping("/demo/rateLimiter") +public class RedisRateLimiterController { + + /** + * 测试全局限流 + * 全局影响 + */ + @RateLimiter(count = 2, time = 10) + @GetMapping("/test") + public R test(String value) { + return R.ok("操作成功", value); + } + + /** + * 测试请求IP限流 + * 同一IP请求受影响 + */ + @RateLimiter(count = 2, time = 10, limitType = LimitType.IP) + @GetMapping("/testip") + public R testip(String value) { + return R.ok("操作成功", value); + } + + /** + * 测试集群实例限流 + * 启动两个后端服务互不影响 + */ + @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER) + @GetMapping("/testcluster") + public R testcluster(String value) { + return R.ok("操作成功", value); + } + + /** + * 测试请求IP限流(key基于参数获取) + * 同一IP请求受影响 + * + * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0} + */ + @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value") + @GetMapping("/testObj") + public R testObj(String value) { + return R.ok("操作成功", value); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java new file mode 100644 index 00000000..9f115040 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/SmsController.java @@ -0,0 +1,76 @@ +package org.ruoyi.demo.controller; + +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.sms.config.properties.SmsProperties; +import org.ruoyi.common.sms.core.SmsTemplate; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 短信演示案例 + * 请先阅读文档 否则无法使用 + * + * @author Lion Li + * @version 4.2.0 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/sms") +public class SmsController { + + private final SmsProperties smsProperties; +// private final SmsTemplate smsTemplate; // 可以使用spring注入 +// private final AliyunSmsTemplate smsTemplate; // 也可以注入某个厂家的模板工具 + + /** + * 发送短信Aliyun + * + * @param phones 电话号 + * @param templateId 模板ID + */ + @GetMapping("/sendAliyun") + public R sendAliyun(String phones, String templateId) { + if (!smsProperties.getEnabled()) { + return R.fail("当前系统没有开启短信功能!"); + } + if (!SpringUtils.containsBean("aliyunSmsTemplate")) { + return R.fail("阿里云依赖未引入!"); + } + SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class); + Map map = new HashMap<>(1); + map.put("code", "1234"); + Object send = smsTemplate.send(phones, templateId, map); + return R.ok(send); + } + + /** + * 发送短信Tencent + * + * @param phones 电话号 + * @param templateId 模板ID + */ + @GetMapping("/sendTencent") + public R sendTencent(String phones, String templateId) { + if (!smsProperties.getEnabled()) { + return R.fail("当前系统没有开启短信功能!"); + } + if (!SpringUtils.containsBean("tencentSmsTemplate")) { + return R.fail("腾讯云依赖未引入!"); + } + SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class); + Map map = new HashMap<>(1); +// map.put("2", "测试测试"); + map.put("1", "1234"); + Object send = smsTemplate.send(phones, templateId, map); + return R.ok(send); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java new file mode 100644 index 00000000..90068379 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/Swagger3DemoController.java @@ -0,0 +1,31 @@ +package org.ruoyi.demo.controller; + +import org.ruoyi.common.core.domain.R; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * swagger3 用法示例 + * + * @author Lion Li + */ +@RestController +@RequestMapping("/swagger/demo") +public class Swagger3DemoController { + + /** + * 上传请求 + * 必须使用 @RequestPart 注解标注为文件 + * + * @param file 文件 + */ + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R upload(@RequestPart("file") MultipartFile file) { + return R.ok("操作成功", file.getOriginalFilename()); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java new file mode 100644 index 00000000..aba105cb --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestBatchController.java @@ -0,0 +1,90 @@ +package org.ruoyi.demo.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.demo.domain.TestDemo; +import org.ruoyi.demo.mapper.TestDemoMapper; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * 测试批量方法 + * + * @author Lion Li + * @date 2021-05-30 + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/batch") +public class TestBatchController extends BaseController { + + /** + * 为了便于测试 直接引入mapper + */ + private final TestDemoMapper testDemoMapper; + + /** + * 新增批量方法 可完美替代 saveBatch 秒级插入上万数据 (对mysql负荷较大) + *

    + * 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度 + */ + @PostMapping("/add") +// @DS("slave") + public R add() { + List list = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + TestDemo testDemo = new TestDemo(); + testDemo.setOrderNum(-1); + testDemo.setTestKey("批量新增"); + testDemo.setValue("测试新增"); + list.add(testDemo); + } + return toAjax(testDemoMapper.insertBatch(list)); + } + + /** + * 新增或更新 可完美替代 saveOrUpdateBatch 高性能 + *

    + * 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度 + */ + @PostMapping("/addOrUpdate") +// @DS("slave") + public R addOrUpdate() { + List list = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + TestDemo testDemo = new TestDemo(); + testDemo.setOrderNum(-1); + testDemo.setTestKey("批量新增"); + testDemo.setValue("测试新增"); + list.add(testDemo); + } + testDemoMapper.insertBatch(list); + for (int i = 0; i < list.size(); i++) { + TestDemo testDemo = list.get(i); + testDemo.setTestKey("批量新增或修改"); + testDemo.setValue("批量新增或修改"); + if (i % 2 == 0) { + testDemo.setId(null); + } + } + return toAjax(testDemoMapper.insertOrUpdateBatch(list)); + } + + /** + * 删除批量方法 + */ + @DeleteMapping() +// @DS("slave") + public R remove() { + return toAjax(testDemoMapper.delete(new LambdaQueryWrapper() + .eq(TestDemo::getOrderNum, -1L))); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java new file mode 100644 index 00000000..6f8dd916 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestDemoController.java @@ -0,0 +1,147 @@ +package org.ruoyi.demo.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.ValidatorUtils; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.core.validate.QueryGroup; +import org.ruoyi.common.excel.core.ExcelResult; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.demo.domain.TestDemo; +import org.ruoyi.demo.domain.bo.TestDemoBo; +import org.ruoyi.demo.domain.bo.TestDemoImportVo; +import org.ruoyi.demo.domain.vo.TestDemoVo; +import org.ruoyi.demo.service.ITestDemoService; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 测试单表Controller + * + * @author Lion Li + * @date 2021-07-26 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/demo") +public class TestDemoController extends BaseController { + + private final ITestDemoService testDemoService; + + /** + * 查询测试单表列表 + */ + @SaCheckPermission("demo:demo:list") + @GetMapping("/list") + public TableDataInfo list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { + return testDemoService.queryPageList(bo, pageQuery); + } + + /** + * 自定义分页查询 + */ + @SaCheckPermission("demo:demo:list") + @GetMapping("/page") + public TableDataInfo page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { + return testDemoService.customPageList(bo, pageQuery); + } + + /** + * 导入数据 + * + * @param file 导入文件 + */ + @Log(title = "测试单表", businessType = BusinessType.IMPORT) + @SaCheckPermission("demo:demo:import") + @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importData(@RequestPart("file") MultipartFile file) throws Exception { + ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true); + List list = MapstructUtils.convert(excelResult.getList(), TestDemo.class); + testDemoService.saveBatch(list); + return R.ok(excelResult.getAnalysis()); + } + + /** + * 导出测试单表列表 + */ + @SaCheckPermission("demo:demo:export") + @Log(title = "测试单表", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(@Validated TestDemoBo bo, HttpServletResponse response) { + List list = testDemoService.queryList(bo); + // 测试雪花id导出 +// for (TestDemoVo vo : list) { +// vo.setId(1234567891234567893L); +// } + ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, response); + } + + /** + * 获取测试单表详细信息 + * + * @param id 测试ID + */ + @SaCheckPermission("demo:demo:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable("id") Long id) { + return R.ok(testDemoService.queryById(id)); + } + + /** + * 新增测试单表 + */ + @SaCheckPermission("demo:demo:add") + @Log(title = "测试单表", businessType = BusinessType.INSERT) + @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "{repeat.submit.message}") + @PostMapping() + public R add(@RequestBody TestDemoBo bo) { + // 使用校验工具对标 @Validated(AddGroup.class) 注解 + // 用于在非 Controller 的地方校验对象 + ValidatorUtils.validate(bo, AddGroup.class); + return toAjax(testDemoService.insertByBo(bo)); + } + + /** + * 修改测试单表 + */ + @SaCheckPermission("demo:demo:edit") + @Log(title = "测试单表", businessType = BusinessType.UPDATE) + @RepeatSubmit + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) { + return toAjax(testDemoService.updateByBo(bo)); + } + + /** + * 删除测试单表 + * + * @param ids 测试ID串 + */ + @SaCheckPermission("demo:demo:remove") + @Log(title = "测试单表", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java new file mode 100644 index 00000000..9897ce15 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestEncryptController.java @@ -0,0 +1,55 @@ +package org.ruoyi.demo.controller; + +import org.ruoyi.common.core.domain.R; +import org.ruoyi.demo.domain.TestDemoEncrypt; +import org.ruoyi.demo.mapper.TestDemoEncryptMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + + +/** + * 测试数据库加解密功能 + * + * @author Lion Li + */ +@Validated +@RestController +@RequestMapping("/demo/encrypt") +public class TestEncryptController { + + @Autowired + private TestDemoEncryptMapper mapper; + @Value("${mybatis-encryptor.enable}") + private Boolean encryptEnable; + + /** + * 测试数据库加解密 + * + * @param key 测试key + * @param value 测试value + */ + @GetMapping() + public R> test(String key, String value) { + if (!encryptEnable) { + throw new RuntimeException("加密功能未开启!"); + } + Map map = new HashMap<>(2); + TestDemoEncrypt demo = new TestDemoEncrypt(); + demo.setTestKey(key); + demo.setValue(value); + mapper.insert(demo); + map.put("加密", demo); + TestDemoEncrypt testDemo = mapper.selectById(demo.getId()); + map.put("解密", testDemo); + return R.ok(map); + } + + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java new file mode 100644 index 00000000..025c4b34 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestExcelController.java @@ -0,0 +1,97 @@ +package org.ruoyi.demo.controller; + +import cn.hutool.core.collection.CollUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 测试Excel功能 + * + * @author Lion Li + */ +@RestController +@RequestMapping("/demo/excel") +public class TestExcelController { + + /** + * 单列表多数据 + */ + @GetMapping("/exportTemplateOne") + public void exportTemplateOne(HttpServletResponse response) { + Map map = new HashMap<>(); + map.put("title", "单列表多数据"); + map.put("test1", "数据测试1"); + map.put("test2", "数据测试2"); + map.put("test3", "数据测试3"); + map.put("test4", "数据测试4"); + map.put("testTest", "666"); + List list = new ArrayList<>(); + list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4")); + list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8")); + list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12")); + ExcelUtil.exportTemplate(CollUtil.newArrayList(map, list), "单列表.xlsx", "excel/单列表.xlsx", response); + } + + /** + * 多列表多数据 + */ + @GetMapping("/exportTemplateMuliti") + public void exportTemplateMuliti(HttpServletResponse response) { + Map map = new HashMap<>(); + map.put("title1", "标题1"); + map.put("title2", "标题2"); + map.put("title3", "标题3"); + map.put("title4", "标题4"); + map.put("author", "Lion Li"); + List list1 = new ArrayList<>(); + list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3")); + list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6")); + list1.add(new TestObj1("list1测试7", "list1测试8", "list1测试9")); + List list2 = new ArrayList<>(); + list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3")); + list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6")); + List list3 = new ArrayList<>(); + list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3")); + List list4 = new ArrayList<>(); + list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3")); + list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6")); + list4.add(new TestObj1("list4测试7", "list4测试8", "list4测试9")); + list4.add(new TestObj1("list4测试10", "list4测试11", "list4测试12")); + Map multiListMap = new HashMap<>(); + multiListMap.put("map", map); + multiListMap.put("data1", list1); + multiListMap.put("data2", list2); + multiListMap.put("data3", list3); + multiListMap.put("data4", list4); + ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response); + } + + @Data + @AllArgsConstructor + static class TestObj1 { + private String test1; + private String test2; + private String test3; + } + + @Data + @AllArgsConstructor + static class TestObj { + private String name; + private String list1; + private String list2; + private String list3; + private String list4; + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java new file mode 100644 index 00000000..704d9b3a --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestI18nController.java @@ -0,0 +1,70 @@ +package org.ruoyi.demo.controller; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.MessageUtils; +import org.hibernate.validator.constraints.Range; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * 测试国际化 + * + * @author Lion Li + */ +@Validated +@RestController +@RequestMapping("/demo/i18n") +public class TestI18nController { + + /** + * 通过code获取国际化内容 + * code为 messages.properties 中的 key + *

    + * 测试使用 user.register.success + * + * @param code 国际化code + */ + @GetMapping() + public R get(String code) { + return R.ok(MessageUtils.message(code)); + } + + /** + * Validator 校验国际化 + * 不传值 分别查看异常返回 + *

    + * 测试使用 not.null + */ + @GetMapping("/test1") + public R test1(@NotBlank(message = "{not.null}") String str) { + return R.ok(str); + } + + /** + * Bean 校验国际化 + * 不传值 分别查看异常返回 + *

    + * 测试使用 not.null + */ + @GetMapping("/test2") + public R test2(@Validated TestI18nBo bo) { + return R.ok(bo); + } + + @Data + public static class TestI18nBo { + + @NotBlank(message = "{not.null}") + private String name; + + @NotNull(message = "{not.null}") + @Range(min = 0, max = 100, message = "{length.not.valid}") + private Integer age; + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java new file mode 100644 index 00000000..4acf1f59 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestSensitiveController.java @@ -0,0 +1,76 @@ +package org.ruoyi.demo.controller; + +import lombok.Data; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.sensitive.annotation.Sensitive; +import org.ruoyi.common.sensitive.core.SensitiveService; +import org.ruoyi.common.sensitive.core.SensitiveStrategy; +import org.ruoyi.common.web.core.BaseController; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 测试数据脱敏控制器 + *

    + * 默认管理员不过滤 + * 需自行根据业务重写实现 + * + * @author Lion Li + * @version 3.6.0 + * @see SensitiveService + */ +@RestController +@RequestMapping("/demo/sensitive") +public class TestSensitiveController extends BaseController { + + /** + * 测试数据脱敏 + */ + @GetMapping("/test") + public R test() { + TestSensitive testSensitive = new TestSensitive(); + testSensitive.setIdCard("210397198608215431"); + testSensitive.setPhone("17640125371"); + testSensitive.setAddress("北京市朝阳区某某四合院1203室"); + testSensitive.setEmail("17640125371@163.com"); + testSensitive.setBankCard("6226456952351452853"); + return R.ok(testSensitive); + } + + @Data + static class TestSensitive { + + /** + * 身份证 + */ + @Sensitive(strategy = SensitiveStrategy.ID_CARD) + private String idCard; + + /** + * 电话 + */ + @Sensitive(strategy = SensitiveStrategy.PHONE) + private String phone; + + /** + * 地址 + */ + @Sensitive(strategy = SensitiveStrategy.ADDRESS) + private String address; + + /** + * 邮箱 + */ + @Sensitive(strategy = SensitiveStrategy.EMAIL) + private String email; + + /** + * 银行卡 + */ + @Sensitive(strategy = SensitiveStrategy.BANK_CARD) + private String bankCard; + + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java new file mode 100644 index 00000000..b8123c8d --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/TestTreeController.java @@ -0,0 +1,107 @@ +package org.ruoyi.demo.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.core.validate.QueryGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.demo.domain.bo.TestTreeBo; +import org.ruoyi.demo.domain.vo.TestTreeVo; +import org.ruoyi.demo.service.ITestTreeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.List; + +/** + * 测试树表Controller + * + * @author Lion Li + * @date 2021-07-26 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/tree") +public class TestTreeController extends BaseController { + + private final ITestTreeService testTreeService; + + /** + * 查询测试树表列表 + */ + @SaCheckPermission("demo:tree:list") + @GetMapping("/list") + public R> list(@Validated(QueryGroup.class) TestTreeBo bo) { + List list = testTreeService.queryList(bo); + return R.ok(list); + } + + /** + * 导出测试树表列表 + */ + @SaCheckPermission("demo:tree:export") + @Log(title = "测试树表", businessType = BusinessType.EXPORT) + @GetMapping("/export") + public void export(@Validated TestTreeBo bo, HttpServletResponse response) { + List list = testTreeService.queryList(bo); + ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response); + } + + /** + * 获取测试树表详细信息 + * + * @param id 测试树ID + */ + @SaCheckPermission("demo:tree:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable("id") Long id) { + return R.ok(testTreeService.queryById(id)); + } + + /** + * 新增测试树表 + */ + @SaCheckPermission("demo:tree:add") + @Log(title = "测试树表", businessType = BusinessType.INSERT) + @RepeatSubmit + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) { + return toAjax(testTreeService.insertByBo(bo)); + } + + /** + * 修改测试树表 + */ + @SaCheckPermission("demo:tree:edit") + @Log(title = "测试树表", businessType = BusinessType.UPDATE) + @RepeatSubmit + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) { + return toAjax(testTreeService.updateByBo(bo)); + } + + /** + * 删除测试树表 + * + * @param ids 测试树ID串 + */ + @SaCheckPermission("demo:tree:remove") + @Log(title = "测试树表", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java new file mode 100644 index 00000000..e70bb39d --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/BoundedQueueController.java @@ -0,0 +1,90 @@ +package org.ruoyi.demo.controller.queue; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.redis.utils.QueueUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 有界队列 演示案例 + *

    + * 轻量级队列 重量级数据量 请使用 MQ + *

    + * 集群测试通过 同一个数据只会被消费一次 做好事务补偿 + * 集群测试流程 在其中一台发送数据 两端分别调用获取接口 一次获取一条 + * + * @author Lion Li + * @version 3.6.0 + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/queue/bounded") +public class BoundedQueueController { + + + /** + * 添加队列数据 + * + * @param queueName 队列名 + * @param capacity 容量 + */ + @GetMapping("/add") + public R add(String queueName, int capacity) { + // 用完了一定要销毁 否则会一直存在 + boolean b = QueueUtils.destroyQueue(queueName); + log.info("通道: {} , 删除: {}", queueName, b); + // 初始化设置一次即可 + if (QueueUtils.trySetBoundedQueueCapacity(queueName, capacity)) { + log.info("通道: {} , 设置容量: {}", queueName, capacity); + } else { + log.info("通道: {} , 设置容量失败", queueName); + return R.fail("操作失败"); + } + for (int i = 0; i < 11; i++) { + String data = "data-" + i; + boolean flag = QueueUtils.addBoundedQueueObject(queueName, data); + if (flag == false) { + log.info("通道: {} , 发送数据: {} 失败, 通道已满", queueName, data); + } else { + log.info("通道: {} , 发送数据: {}", queueName, data); + } + } + return R.ok("操作成功"); + } + + /** + * 删除队列数据 + * + * @param queueName 队列名 + */ + @GetMapping("/remove") + public R remove(String queueName) { + String data = "data-" + 5; + if (QueueUtils.removeQueueObject(queueName, data)) { + log.info("通道: {} , 删除数据: {}", queueName, data); + } else { + return R.fail("操作失败"); + } + return R.ok("操作成功"); + } + + /** + * 获取队列数据 + * + * @param queueName 队列名 + */ + @GetMapping("/get") + public R get(String queueName) { + String data; + do { + data = QueueUtils.getQueueObject(queueName); + log.info("通道: {} , 获取数据: {}", queueName, data); + } while (data != null); + return R.ok("操作成功"); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java new file mode 100644 index 00000000..74d3e4d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/DelayedQueueController.java @@ -0,0 +1,90 @@ +package org.ruoyi.demo.controller.queue; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.redis.utils.QueueUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.TimeUnit; + +/** + * 延迟队列 演示案例 + *

    + * 轻量级队列 重量级数据量 请使用 MQ + * 例如: 创建订单30分钟后过期处理 + *

    + * 集群测试通过 同一个数据只会被消费一次 做好事务补偿 + * 集群测试流程 两台集群分别开启订阅 在其中一台发送数据 观察接收消息的规律 + * + * @author Lion Li + * @version 3.6.0 + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/queue/delayed") +public class DelayedQueueController { + + /** + * 订阅队列 + * + * @param queueName 队列名 + */ + @GetMapping("/subscribe") + public R subscribe(String queueName) { + log.info("通道: {} 监听中......", queueName); + // 项目初始化设置一次即可 + QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> { + // 观察接收时间 + log.info("通道: {}, 收到数据: {}", queueName, orderNum); + }); + return R.ok("操作成功"); + } + + /** + * 添加队列数据 + * + * @param queueName 队列名 + * @param orderNum 订单号 + * @param time 延迟时间(秒) + */ + @GetMapping("/add") + public R add(String queueName, String orderNum, Long time) { + QueueUtils.addDelayedQueueObject(queueName, orderNum, time, TimeUnit.SECONDS); + // 观察发送时间 + log.info("通道: {} , 发送数据: {}", queueName, orderNum); + return R.ok("操作成功"); + } + + /** + * 删除队列数据 + * + * @param queueName 队列名 + * @param orderNum 订单号 + */ + @GetMapping("/remove") + public R remove(String queueName, String orderNum) { + if (QueueUtils.removeDelayedQueueObject(queueName, orderNum)) { + log.info("通道: {} , 删除数据: {}", queueName, orderNum); + } else { + return R.fail("操作失败"); + } + return R.ok("操作成功"); + } + + /** + * 销毁队列 + * + * @param queueName 队列名 + */ + @GetMapping("/destroy") + public R destroy(String queueName) { + // 用完了一定要销毁 否则会一直存在 + QueueUtils.destroyDelayedQueue(queueName); + return R.ok("操作成功"); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java new file mode 100644 index 00000000..bf1de73c --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityDemo.java @@ -0,0 +1,22 @@ +package org.ruoyi.demo.controller.queue; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 实体类 注意不允许使用内部类 否则会找不到类 + * + * @author Lion Li + * @version 3.6.0 + */ +@Data +@NoArgsConstructor +public class PriorityDemo implements Comparable { + private String name; + private Integer orderNum; + + @Override + public int compareTo(PriorityDemo other) { + return Integer.compare(getOrderNum(), other.getOrderNum()); + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java new file mode 100644 index 00000000..e0dbaed2 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/controller/queue/PriorityQueueController.java @@ -0,0 +1,89 @@ +package org.ruoyi.demo.controller.queue; + +import cn.hutool.core.util.RandomUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.redis.utils.QueueUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 优先队列 演示案例 + *

    + * 轻量级队列 重量级数据量 请使用 MQ + *

    + * 集群测试通过 同一个消息只会被消费一次 做好事务补偿 + * 集群测试流程 在其中一台发送数据 两端分别调用获取接口 一次获取一条 + * + * @author Lion Li + * @version 3.6.0 + */ +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/demo/queue/priority") +public class PriorityQueueController { + + /** + * 添加队列数据 + * + * @param queueName 队列名 + */ + @GetMapping("/add") + public R add(String queueName) { + // 用完了一定要销毁 否则会一直存在 + boolean b = QueueUtils.destroyQueue(queueName); + log.info("通道: {} , 删除: {}", queueName, b); + + for (int i = 0; i < 10; i++) { + int randomNum = RandomUtil.randomInt(10); + PriorityDemo data = new PriorityDemo(); + data.setName("data-" + i); + data.setOrderNum(randomNum); + if (QueueUtils.addPriorityQueueObject(queueName, data)) { + log.info("通道: {} , 发送数据: {}", queueName, data); + } else { + log.info("通道: {} , 发送数据: {}, 发送失败", queueName, data); + } + } + return R.ok("操作成功"); + } + + /** + * 删除队列数据 + * + * @param queueName 队列名 + * @param name 对象名 + * @param orderNum 排序号 + */ + @GetMapping("/remove") + public R remove(String queueName, String name, Integer orderNum) { + PriorityDemo data = new PriorityDemo(); + data.setName(name); + data.setOrderNum(orderNum); + if (QueueUtils.removeQueueObject(queueName, data)) { + log.info("通道: {} , 删除数据: {}", queueName, data); + } else { + return R.fail("操作失败"); + } + return R.ok("操作成功"); + } + + /** + * 获取队列数据 + * + * @param queueName 队列名 + */ + @GetMapping("/get") + public R get(String queueName) { + PriorityDemo data; + do { + data = QueueUtils.getQueueObject(queueName); + log.info("通道: {} , 获取数据: {}", queueName, data); + } while (data != null); + return R.ok("操作成功"); + } + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java new file mode 100644 index 00000000..901eadc7 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemo.java @@ -0,0 +1,68 @@ +package org.ruoyi.demo.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 测试单表对象 test_demo + * + * @author Lion Li + * @date 2021-07-26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("test_demo") +public class TestDemo extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 部门id + */ + private Long deptId; + + /** + * 用户id + */ + private Long userId; + + /** + * 排序号 + */ + @OrderBy(asc = false, sort = 1) + private Integer orderNum; + + /** + * key键 + */ + private String testKey; + + /** + * 值 + */ + private String value; + + /** + * 版本 + */ + @Version + private Long version; + + /** + * 删除标志 + */ + @TableLogic + private Long delFlag; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java new file mode 100644 index 00000000..d8f7181d --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestDemoEncrypt.java @@ -0,0 +1,29 @@ +package org.ruoyi.demo.domain; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.encrypt.annotation.EncryptField; +import org.ruoyi.common.encrypt.enumd.AlgorithmType; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("test_demo") +public class TestDemoEncrypt extends TestDemo { + + /** + * key键 + */ + // @EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==") + @EncryptField(algorithm = AlgorithmType.RSA, privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANBBEeueWlXlkkj2+WY5l+IWe42d8b5K28g+G/CFKC/yYAEHtqGlCsBOrb+YBkG9mPzmuYA/n9k0NFIc8E8yY5vZQaroyFBrTTWEzG9RY2f7Y3svVyybs6jpXSUs4xff8abo7wL1Y/wUaeatTViamxYnyTvdTmLm3d+JjRij68rxAgMBAAECgYAB0TnhXraSopwIVRfmboea1b0upl+BUdTJcmci412UjrKr5aE695ZLPkXbFXijVu7HJlyyv94NVUdaMACV7Ku/S2RuNB70M7YJm8rAjHFC3/i2ZeIM60h1Ziy4QKv0XM3pRATlDCDNhC1WUrtQCQSgU8kcp6eUUppruOqDzcY04QJBAPm9+sBP9CwDRgy3e5+V8aZtJkwDstb0lVVV/KY890cydVxiCwvX3fqVnxKMlb+x0YtH0sb9v+71xvK2lGobaRECQQDVePU6r/cCEfpc+nkWF6osAH1f8Mux3rYv2DoBGvaPzV2BGfsLed4neRfCwWNCKvGPCdW+L0xMJg8+RwaoBUPhAkAT5kViqXxFPYWJYd1h2+rDXhMdH3ZSlm6HvDBDdrwlWinr0Iwcx3iSjPV93uHXwm118aUj4fg3LDJMCKxOwBxhAkByrQXfvwOMYygBprRBf/j0plazoWFrbd6lGR0f1uI5IfNnFRPdeFw1DEINZ2Hw+6zEUF44SqRMC+4IYJNc02dBAkBCgy7RvfyV/A7N6kKXxTHauY0v6XwSSvpeKtRJkbIcRWOdIYvaHO9L7cklj3vIEdwjSUp9K4VTBYYlmAz1xh03", publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQQRHrnlpV5ZJI9vlmOZfiFnuNnfG+StvIPhvwhSgv8mABB7ahpQrATq2/mAZBvZj85rmAP5/ZNDRSHPBPMmOb2UGq6MhQa001hMxvUWNn+2N7L1csm7Oo6V0lLOMX3/Gm6O8C9WP8FGnmrU1YmpsWJ8k73U5i5t3fiY0Yo+vK8QIDAQAB") + private String testKey; + + /** + * 值 + */ + // @EncryptField // 什么也不写走默认yml配置 + // @EncryptField(algorithm = AlgorithmType.SM4, password = "10rfylhtccpuyke5") + @EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5") + private String value; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java new file mode 100644 index 00000000..9b9b34ce --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/TestTree.java @@ -0,0 +1,65 @@ +package org.ruoyi.demo.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.Version; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 测试树表对象 test_tree + * + * @author Lion Li + * @date 2021-07-26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("test_tree") +public class TestTree extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 父ID + */ + private Long parentId; + + /** + * 部门id + */ + private Long deptId; + + /** + * 用户id + */ + private Long userId; + + /** + * 树节点名 + */ + private String treeName; + + /** + * 版本 + */ + @Version + private Long version; + + /** + * 删除标志 + */ + @TableLogic + private Long delFlag; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java new file mode 100644 index 00000000..9da93626 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoBo.java @@ -0,0 +1,61 @@ +package org.ruoyi.demo.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.demo.domain.TestDemo; + +/** + * 测试单表业务对象 test_demo + * + * @author Lion Li + * @date 2021-07-26 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = TestDemo.class, reverseConvertGenerate = false) +public class TestDemoBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {EditGroup.class}) + private Long id; + + /** + * 部门id + */ + @NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long deptId; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long userId; + + /** + * 排序号 + */ + @NotNull(message = "排序号不能为空", groups = {AddGroup.class, EditGroup.class}) + private Integer orderNum; + + /** + * key键 + */ + @NotBlank(message = "key键不能为空", groups = {AddGroup.class, EditGroup.class}) + private String testKey; + + /** + * 值 + */ + @NotBlank(message = "值不能为空", groups = {AddGroup.class, EditGroup.class}) + private String value; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java new file mode 100644 index 00000000..7685cb79 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestDemoImportVo.java @@ -0,0 +1,52 @@ +package org.ruoyi.demo.domain.bo; + +import com.alibaba.excel.annotation.ExcelProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 测试单表业务对象 test_demo + * + * @author Lion Li + * @date 2021-07-26 + */ +@Data +public class TestDemoImportVo { + + /** + * 部门id + */ + @NotNull(message = "部门id不能为空") + @ExcelProperty(value = "部门id") + private Long deptId; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空") + @ExcelProperty(value = "用户id") + private Long userId; + + /** + * 排序号 + */ + @NotNull(message = "排序号不能为空") + @ExcelProperty(value = "排序号") + private Long orderNum; + + /** + * key键 + */ + @NotBlank(message = "key键不能为空") + @ExcelProperty(value = "key键") + private String testKey; + + /** + * 值 + */ + @NotBlank(message = "值不能为空") + @ExcelProperty(value = "值") + private String value; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java new file mode 100644 index 00000000..0621a77c --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/bo/TestTreeBo.java @@ -0,0 +1,54 @@ +package org.ruoyi.demo.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.demo.domain.TestTree; + +/** + * 测试树表业务对象 test_tree + * + * @author Lion Li + * @date 2021-07-26 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = TestTree.class, reverseConvertGenerate = false) +public class TestTreeBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {EditGroup.class}) + private Long id; + + /** + * 父ID + */ + private Long parentId; + + /** + * 部门id + */ + @NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long deptId; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long userId; + + /** + * 树节点名 + */ + @NotBlank(message = "树节点名不能为空", groups = {AddGroup.class, EditGroup.class}) + private String treeName; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java new file mode 100644 index 00000000..d7a73345 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestDemoVo.java @@ -0,0 +1,104 @@ +package org.ruoyi.demo.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.translation.annotation.Translation; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.demo.domain.TestDemo; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 测试单表视图对象 test_demo + * + * @author Lion Li + * @date 2021-07-26 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = TestDemo.class) +public class TestDemoVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 部门id + */ + @ExcelProperty(value = "部门id") + private Long deptId; + + /** + * 用户id + */ + @ExcelProperty(value = "用户id") + private Long userId; + + /** + * 排序号 + */ + @ExcelProperty(value = "排序号") + private Integer orderNum; + + /** + * key键 + */ + @ExcelProperty(value = "key键") + private String testKey; + + /** + * 值 + */ + @ExcelProperty(value = "值") + private String value; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 创建人 + */ + @ExcelProperty(value = "创建人") + private Long createBy; + + /** + * 创建人账号 + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + @ExcelProperty(value = "创建人账号") + private String createByName; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updateTime; + + /** + * 更新人 + */ + @ExcelProperty(value = "更新人") + private Long updateBy; + + /** + * 更新人账号 + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy") + @ExcelProperty(value = "更新人账号") + private String updateByName; + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java new file mode 100644 index 00000000..f9e3e1e7 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/domain/vo/TestTreeVo.java @@ -0,0 +1,64 @@ +package org.ruoyi.demo.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.demo.domain.TestTree; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 测试树表视图对象 test_tree + * + * @author Lion Li + * @date 2021-07-26 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = TestTree.class) +public class TestTreeVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + private Long id; + + /** + * 父id + */ + @ExcelProperty(value = "父id") + private Long parentId; + + /** + * 部门id + */ + @ExcelProperty(value = "部门id") + private Long deptId; + + /** + * 用户id + */ + @ExcelProperty(value = "用户id") + private Long userId; + + /** + * 树节点名 + */ + @ExcelProperty(value = "树节点名") + private String treeName; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java new file mode 100644 index 00000000..8b5eb968 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoEncryptMapper.java @@ -0,0 +1,13 @@ +package org.ruoyi.demo.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.demo.domain.TestDemoEncrypt; + +/** + * 测试加密功能 + * + * @author Lion Li + */ +public interface TestDemoEncryptMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java new file mode 100644 index 00000000..daf0a7f9 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestDemoMapper.java @@ -0,0 +1,58 @@ +package org.ruoyi.demo.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.demo.domain.TestDemo; +import org.ruoyi.demo.domain.vo.TestDemoVo; + +import java.util.Collection; +import java.util.List; + +/** + * 测试单表Mapper接口 + * + * @author Lion Li + * @date 2021-07-26 + */ +public interface TestDemoMapper extends BaseMapperPlus { + + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") + }) + Page customPageList(@Param("page") Page page, @Param("ew") Wrapper wrapper); + + @Override + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") + }) +

    > P selectPage(P page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + @Override + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") + }) + List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + @Override + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") + }) + int updateById(@Param(Constants.ENTITY) TestDemo entity); + + @Override + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") + }) + int deleteBatchIds(@Param(Constants.COLL) Collection idList); +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java new file mode 100644 index 00000000..d6b59b95 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/mapper/TestTreeMapper.java @@ -0,0 +1,21 @@ +package org.ruoyi.demo.mapper; + +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.demo.domain.TestTree; +import org.ruoyi.demo.domain.vo.TestTreeVo; + +/** + * 测试树表Mapper接口 + * + * @author Lion Li + * @date 2021-07-26 + */ +@DataPermission({ + @DataColumn(key = "deptName", value = "dept_id"), + @DataColumn(key = "userName", value = "user_id") +}) +public interface TestTreeMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java new file mode 100644 index 00000000..1a4d7154 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestDemoService.java @@ -0,0 +1,71 @@ +package org.ruoyi.demo.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.demo.domain.TestDemo; +import org.ruoyi.demo.domain.bo.TestDemoBo; +import org.ruoyi.demo.domain.vo.TestDemoVo; + +import java.util.Collection; +import java.util.List; + +/** + * 测试单表Service接口 + * + * @author Lion Li + * @date 2021-07-26 + */ +public interface ITestDemoService { + + /** + * 查询单个 + * + * @return + */ + TestDemoVo queryById(Long id); + + /** + * 查询列表 + */ + TableDataInfo queryPageList(TestDemoBo bo, PageQuery pageQuery); + + /** + * 自定义分页查询 + */ + TableDataInfo customPageList(TestDemoBo bo, PageQuery pageQuery); + + /** + * 查询列表 + */ + List queryList(TestDemoBo bo); + + /** + * 根据新增业务对象插入测试单表 + * + * @param bo 测试单表新增业务对象 + * @return + */ + Boolean insertByBo(TestDemoBo bo); + + /** + * 根据编辑业务对象修改测试单表 + * + * @param bo 测试单表编辑业务对象 + * @return + */ + Boolean updateByBo(TestDemoBo bo); + + /** + * 校验并删除数据 + * + * @param ids 主键集合 + * @param isValid 是否校验,true-删除前校验,false-不校验 + * @return + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 批量保存 + */ + Boolean saveBatch(List list); +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java new file mode 100644 index 00000000..6336fc18 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/ITestTreeService.java @@ -0,0 +1,52 @@ +package org.ruoyi.demo.service; + +import org.ruoyi.demo.domain.bo.TestTreeBo; +import org.ruoyi.demo.domain.vo.TestTreeVo; + +import java.util.Collection; +import java.util.List; + +/** + * 测试树表Service接口 + * + * @author Lion Li + * @date 2021-07-26 + */ +public interface ITestTreeService { + /** + * 查询单个 + * + * @return + */ + TestTreeVo queryById(Long id); + + /** + * 查询列表 + */ + List queryList(TestTreeBo bo); + + /** + * 根据新增业务对象插入测试树表 + * + * @param bo 测试树表新增业务对象 + * @return + */ + Boolean insertByBo(TestTreeBo bo); + + /** + * 根据编辑业务对象修改测试树表 + * + * @param bo 测试树表编辑业务对象 + * @return + */ + Boolean updateByBo(TestTreeBo bo); + + /** + * 校验并删除数据 + * + * @param ids 主键集合 + * @param isValid 是否校验,true-删除前校验,false-不校验 + * @return + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java new file mode 100644 index 00000000..af7588b8 --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestDemoServiceImpl.java @@ -0,0 +1,110 @@ +package org.ruoyi.demo.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.demo.domain.TestDemo; +import org.ruoyi.demo.domain.bo.TestDemoBo; +import org.ruoyi.demo.domain.vo.TestDemoVo; +import org.ruoyi.demo.mapper.TestDemoMapper; +import org.ruoyi.demo.service.ITestDemoService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 测试单表Service业务层处理 + * + * @author Lion Li + * @date 2021-07-26 + */ +@RequiredArgsConstructor +@Service +public class TestDemoServiceImpl implements ITestDemoService { + + private final TestDemoMapper baseMapper; + + @Override + public TestDemoVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + @Override + public TableDataInfo queryPageList(TestDemoBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 自定义分页查询 + */ + @Override + public TableDataInfo customPageList(TestDemoBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.customPageList(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + @Override + public List queryList(TestDemoBo bo) { + return baseMapper.selectVoList(buildQueryWrapper(bo)); + } + + private LambdaQueryWrapper buildQueryWrapper(TestDemoBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey()); + lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue()); + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + return lqw; + } + + @Override + public Boolean insertByBo(TestDemoBo bo) { + TestDemo add = MapstructUtils.convert(bo, TestDemo.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + @Override + public Boolean updateByBo(TestDemoBo bo) { + TestDemo update = MapstructUtils.convert(bo, TestDemo.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + * + * @param entity 实体类数据 + */ + private void validEntityBeforeSave(TestDemo entity) { + //TODO 做一些数据校验,如唯一约束 + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + @Override + public Boolean saveBatch(List list) { + return baseMapper.insertBatch(list); + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java new file mode 100644 index 00000000..07ee214a --- /dev/null +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/ruoyi/demo/service/impl/TestTreeServiceImpl.java @@ -0,0 +1,87 @@ +package org.ruoyi.demo.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.demo.domain.TestTree; +import org.ruoyi.demo.domain.bo.TestTreeBo; +import org.ruoyi.demo.domain.vo.TestTreeVo; +import org.ruoyi.demo.mapper.TestTreeMapper; +import org.ruoyi.demo.service.ITestTreeService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 测试树表Service业务层处理 + * + * @author Lion Li + * @date 2021-07-26 + */ +// @DS("slave") // 切换从库查询 +@RequiredArgsConstructor +@Service +public class TestTreeServiceImpl implements ITestTreeService { + + private final TestTreeMapper baseMapper; + + @Override + public TestTreeVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + // @DS("slave") // 切换从库查询 + @Override + public List queryList(TestTreeBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(TestTreeBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName()); + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + TestTree::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + return lqw; + } + + @Override + public Boolean insertByBo(TestTreeBo bo) { + TestTree add = MapstructUtils.convert(bo, TestTree.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + @Override + public Boolean updateByBo(TestTreeBo bo) { + TestTree update = MapstructUtils.convert(bo, TestTree.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + * + * @param entity 实体类数据 + */ + private void validEntityBeforeSave(TestTree entity) { + //TODO 做一些数据校验,如唯一约束 + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml index 8200a7f7..6d00b20e 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml +++ b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestDemoMapper.xml @@ -2,9 +2,9 @@ - + - SELECT * FROM test_demo ${ew.customSqlSegment} diff --git a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml index 2768fcf5..46b0e366 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml +++ b/ruoyi-modules/ruoyi-demo/src/main/resources/mapper/demo/TestTreeMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/ruoyi-modules/ruoyi-fusion/pom.xml b/ruoyi-modules/ruoyi-fusion/pom.xml new file mode 100644 index 00000000..a5fd1a9a --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/pom.xml @@ -0,0 +1,119 @@ + + + org.ruoyi + ruoyi-modules + ${revision} + ../pom.xml + + + 4.0.0 + ruoyi-fusion + + + AI绘画 + + + + 5.8.18 + 20220924 + 5.0.0-beta.9 + 1.1.2-beta0 + 2.0.0 + 4.1.0 + 1.21 + 4.5.14 + 17 + ${java.version} + ${java.version} + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-redis + + + + cn.hutool + hutool-core + ${hutool.version} + + + cn.hutool + hutool-cache + ${hutool.version} + + + cn.hutool + hutool-crypto + ${hutool.version} + + + org.json + json + ${org-json.version} + + + net.dv8tion + JDA + ${jda.version} + + + club.minnced + opus-java + + + + + com.unfbx + chatgpt-java + ${chatgpt-java.version} + + + slf4j-simple + org.slf4j + + + + + eu.maxschuster + dataurl + ${dataurl.version} + + + com.github.xiaoymin + knife4j-openapi2-spring-boot-starter + ${knife4j.verison} + + + eu.bitwalker + UserAgentUtils + ${user-agent-utils.verison} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + jakarta.servlet + jakarta.servlet-api + + + org.ruoyi + ruoyi-system + + + + diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/ChatController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/ChatController.java new file mode 100644 index 00000000..0f717edf --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/ChatController.java @@ -0,0 +1,114 @@ +package org.ruoyi.fusion.controller; + + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.chat.domain.request.ChatRequest; +import org.ruoyi.common.chat.domain.request.Dall3Request; +import org.ruoyi.common.chat.entity.Tts.TextToSpeech; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.images.Item; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.vo.ChatMessageVo; +import org.ruoyi.system.service.IChatMessageService; +import org.ruoyi.system.service.ISseService; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * 描述:聊天管理 + * + * @author ageerle@163.com + * @date 2023-03-01 + */ +@Controller +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/chat") +public class ChatController { + + private final ISseService ISseService; + + private final IChatMessageService chatMessageService; + + /** + * 聊天接口 + */ + @PostMapping("/send") + @ResponseBody + public SseEmitter sseChat(@RequestBody @Valid ChatRequest chatRequest, HttpServletRequest request) { + return ISseService.sseChat(chatRequest,request); + } + + + /** + * 上传文件 + */ + @PostMapping("/upload") + @ResponseBody + public UploadFileResponse upload(@RequestPart("file") MultipartFile file) { + return ISseService.upload(file); + } + + + /** + * 语音转文本 + * + * @param file + */ + @PostMapping("/audio") + @ResponseBody + public WhisperResponse audio(@RequestParam("file") MultipartFile file) { + WhisperResponse whisperResponse = ISseService.speechToTextTranscriptionsV2(file); + return whisperResponse; + } + + /** + * 文本转语音 + * + * @param textToSpeech + */ + @PostMapping("/speech") + @ResponseBody + public ResponseEntity speech(@RequestBody TextToSpeech textToSpeech) { + return ISseService.textToSpeed(textToSpeech); + } + + @PostMapping("/dall3") + @ResponseBody + public R> dall3(@RequestBody @Valid Dall3Request request) { + return R.ok(ISseService.dall3(request)); + } + + /** + * 聊天记录 + */ + @PostMapping("/chatList") + @ResponseBody + public R> list(@RequestBody @Valid ChatMessageBo chatRequest, @RequestBody PageQuery pageQuery) { + // 默认查询当前登录用户消息记录 + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new BaseException("用户未登录!"); + } + chatRequest.setUserId(loginUser.getUserId()); + TableDataInfo chatMessageVoTableDataInfo = chatMessageService.queryPageList(chatRequest, pageQuery); + return R.ok(chatMessageVoTableDataInfo); + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/FaceController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/FaceController.java new file mode 100644 index 00000000..89390238 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/FaceController.java @@ -0,0 +1,42 @@ +package org.ruoyi.fusion.controller; + +import cn.hutool.json.JSONUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.fusion.domain.InsightFace; +import org.ruoyi.fusion.util.MjOkHttpUtil; +import org.ruoyi.system.service.IChatCostService; +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; + + +@Api(tags = "任务查询") +@RestController +@RequestMapping("/mj") +@RequiredArgsConstructor +@Slf4j +public class FaceController { + + private final IChatCostService chatCostService; + + private final MjOkHttpUtil mjOkHttpUtil; + + @ApiOperation(value = "换脸") + @PostMapping("/insight-face/swap") + public String insightFace(@RequestBody InsightFace insightFace) { + // 扣除接口费用并且保存消息记录 + chatCostService.taskDeduct("mj","Face Changing", NumberUtils.toDouble(mjOkHttpUtil.getKey("faceSwapping"), 0.1)); + // 创建请求体(这里使用JSON作为媒体类型) + String insightFaceJson = JSONUtil.toJsonStr(insightFace); + String url = "mj/insight-face/swap"; + Request request = mjOkHttpUtil.createPostRequest(url, insightFaceJson); + return mjOkHttpUtil.executeRequest(request); + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/LumaController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/LumaController.java new file mode 100644 index 00000000..6e2bf1ce --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/LumaController.java @@ -0,0 +1,51 @@ +package org.ruoyi.fusion.controller; + +import cn.hutool.json.JSONUtil; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.common.core.utils.OkHttpUtil; +import org.ruoyi.system.cofing.OkHttpConfig; +import org.ruoyi.system.domain.GenerateLuma; +import org.ruoyi.system.service.IChatCostService; +import org.springframework.web.bind.annotation.*; + +/** + * 描述:文生视频 + * + * @author ageerle@163.com + * date 2024/6/27 + */ +@RestController +@RequestMapping("/luma") +@RequiredArgsConstructor +@Slf4j +public class LumaController { + + private final OkHttpConfig okHttpConfig; + private final IChatCostService chatCostService; + + + @ApiOperation(value = "文生视频") + @PostMapping("/generations/") + public String generateVideo(@RequestBody GenerateLuma generateLuma) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("luma"); + chatCostService.taskDeduct("luma", "文生视频", NumberUtils.toDouble(okHttpConfig.getGenerate(), 0.3)); + String generateJson = JSONUtil.toJsonStr(generateLuma); + String url = "luma/generations"; + Request request = okHttpUtil.createPostRequest(url, generateJson); + return okHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "文生视频任务查询") + @GetMapping("/generations/{taskId}") + public String getGenerationTask(@PathVariable String taskId) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("luma"); + String url = "luma/generations/" + taskId; + Request request = okHttpUtil.createGetRequest(url); + return okHttpUtil.executeRequest(request); + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SubmitController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SubmitController.java new file mode 100644 index 00000000..29f568cf --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SubmitController.java @@ -0,0 +1,134 @@ +package org.ruoyi.fusion.controller; + +import cn.hutool.json.JSONUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.fusion.dto.*; +import org.ruoyi.fusion.enums.ActionType; +import org.ruoyi.fusion.util.MjOkHttpUtil; +import org.ruoyi.system.service.IChatCostService; +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; + +import java.util.Optional; + +@Api(tags = "任务提交") +@RestController +@RequestMapping("/mj/submit") +@RequiredArgsConstructor +@Slf4j +public class SubmitController { + + private final IChatCostService chatCostService; + private final MjOkHttpUtil mjOkHttpUtil; + + @ApiOperation(value = "绘图变化") + @PostMapping("/change") + public String change(@RequestBody SubmitChangeDTO changeDTO) { + String jsonStr = JSONUtil.toJsonStr(changeDTO); + String url = "mj/submit/change"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "执行动作") + @PostMapping("/action") + public String action(@RequestBody SubmitActionDTO changeDTO) { + ActionType actionType = ActionType.fromCustomId(getAction(changeDTO.getCustomId())); + Optional.ofNullable(actionType).ifPresentOrElse( + type -> { + switch (type) { + case UP_SAMPLE: + chatCostService.taskDeduct("mj","enlarge", NumberUtils.toDouble(mjOkHttpUtil.getKey("upsample"), 0.3)); + break; + case IN_PAINT: + // 局部重绘已经扣费,不执行任何操作 + break; + default: + chatCostService.taskDeduct("mj","change", NumberUtils.toDouble(mjOkHttpUtil.getKey("change"), 0.3)); + break; + } + }, + () -> chatCostService.taskDeduct("mj","change", NumberUtils.toDouble(mjOkHttpUtil.getKey("change"), 0.3)) + ); + + String jsonStr = JSONUtil.toJsonStr(changeDTO); + String url = "mj/submit/action"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "绘图变化-simple") + @PostMapping("/simple-change") + public String simpleChange(@RequestBody SubmitSimpleChangeDTO simpleChangeDTO) { + String jsonStr = JSONUtil.toJsonStr(simpleChangeDTO); + String url = "mj/submit/simple-change"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交图生图、混图任务") + @PostMapping("/blend") + public String blend(@RequestBody SubmitBlendDTO blendDTO) { + chatCostService.taskDeduct("mj","blend", NumberUtils.toDouble(mjOkHttpUtil.getKey("blend"), 0.3)); + String jsonStr = JSONUtil.toJsonStr(blendDTO); + String url = "mj/submit/blend"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交图生文任务") + @PostMapping("/describe") + public String describe(@RequestBody SubmitDescribeDTO describeDTO) { + chatCostService.taskDeduct("mj","describe", NumberUtils.toDouble(mjOkHttpUtil.getKey("describe"), 0.1)); + String jsonStr = JSONUtil.toJsonStr(describeDTO); + String url = "mj/submit/describe"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交文生图任务") + @PostMapping("/imagine") + public String imagine(@RequestBody SubmitImagineDTO imagineDTO) { + chatCostService.taskDeduct("mj",imagineDTO.getPrompt(), NumberUtils.toDouble(mjOkHttpUtil.getKey("imagine"), 0.3)); + String jsonStr = JSONUtil.toJsonStr(imagineDTO); + String url = "mj/submit/imagine"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交局部重绘任务") + @PostMapping("/modal") + public String modal(@RequestBody SubmitModalDTO submitModalDTO) { + chatCostService.taskDeduct("mj","repaint ", NumberUtils.toDouble(mjOkHttpUtil.getKey("inpaint"), 0.1)); + String jsonStr = JSONUtil.toJsonStr(submitModalDTO); + String url = "mj/submit/modal"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "提交提示词分析任务") + @PostMapping("/shorten") + public String shorten(@RequestBody SubmitShortenDTO submitShortenDTO) { + chatCostService.taskDeduct("mj","shorten", NumberUtils.toDouble(mjOkHttpUtil.getKey("shorten"), 0.1)); + String jsonStr = JSONUtil.toJsonStr(submitShortenDTO); + String url = "mj/submit/shorten"; + Request request = mjOkHttpUtil.createPostRequest(url, jsonStr); + return mjOkHttpUtil.executeRequest(request); + } + + public String getAction(String customId) { + if (customId == null || customId.isEmpty()) { + return null; + } + String[] parts = customId.split("::"); + return customId.endsWith("SOLO") ? parts[1] : parts[2]; + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SunoController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SunoController.java new file mode 100644 index 00000000..7e6cd8c7 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/SunoController.java @@ -0,0 +1,68 @@ +package org.ruoyi.fusion.controller; + +import cn.hutool.json.JSONUtil; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.common.core.utils.OkHttpUtil; +import org.ruoyi.system.cofing.OkHttpConfig; +import org.ruoyi.system.domain.GenerateLyric; +import org.ruoyi.system.domain.GenerateSuno; +import org.ruoyi.system.service.IChatCostService; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/sunoapi") +@RequiredArgsConstructor +@Slf4j +public class SunoController { + + private final OkHttpConfig okHttpConfig; + private final IChatCostService chatCostService; + + @ApiOperation(value = "文生歌曲") + @PostMapping("/generate") + public String generate(@RequestBody GenerateSuno generateSuno) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("suno"); + // 扣除接口费用并且保存消息记录 + chatCostService.taskDeduct("suno","文生歌曲", NumberUtils.toDouble(okHttpConfig.getGenerate(), 0.3)); + // 创建请求体(这里使用JSON作为媒体类型) + String generateJson = JSONUtil.toJsonStr(generateSuno); + String url = "suno/generate"; + Request request = okHttpUtil.createPostRequest(url, generateJson); + return okHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "生成歌词") + @PostMapping("/generate/lyrics/") + public String generate(@RequestBody GenerateLyric generateLyric) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("suno"); + String generateJson = JSONUtil.toJsonStr(generateLyric); + String url = "task/suno/v1/submit/lyrics"; + Request request = okHttpUtil.createPostRequest(url, generateJson); + return okHttpUtil.executeRequest(request); + } + + + @ApiOperation(value = "查询歌词任务") + @GetMapping("/lyrics/{taskId}") + public String lyrics(@PathVariable String taskId) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("suno"); + String url = "task/suno/v1/fetch/"+taskId; + Request request = okHttpUtil.createGetRequest(url); + return okHttpUtil.executeRequest(request); + } + + + @ApiOperation(value = "查询歌曲任务") + @GetMapping("/feed/{taskId}") + public String feed(@PathVariable String taskId) { + OkHttpUtil okHttpUtil = okHttpConfig.getOkHttpUtil("suno"); + String url = "suno/feed/"+taskId; + Request request = okHttpUtil.createGetRequest(url); + return okHttpUtil.executeRequest(request); + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/TaskController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/TaskController.java new file mode 100644 index 00000000..74b8f071 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/TaskController.java @@ -0,0 +1,48 @@ +package org.ruoyi.fusion.controller; + +import cn.hutool.json.JSONUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import org.ruoyi.fusion.dto.TaskConditionDTO; +import org.ruoyi.fusion.util.MjOkHttpUtil; +import org.springframework.web.bind.annotation.*; + +@Api(tags = "任务查询") +@RestController +@RequestMapping("/mj/task") +@RequiredArgsConstructor +@Slf4j +public class TaskController { + + private final MjOkHttpUtil mjOkHttpUtil; + + @ApiOperation(value = "指定ID获取任务") + @GetMapping("/{id}/fetch") + public String fetch(@ApiParam(value = "任务ID") @PathVariable String id) { + String url = "mj/task/" + id + "/fetch"; + Request request = mjOkHttpUtil.createGetRequest(url); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "根据ID列表查询任务") + @PostMapping("/list-by-condition") + public String listByIds(@RequestBody TaskConditionDTO conditionDTO) { + String url = "mj/task/list-by-condition"; + String conditionJson = JSONUtil.toJsonStr(conditionDTO); + Request request = mjOkHttpUtil.createPostRequest(url,conditionJson); + return mjOkHttpUtil.executeRequest(request); + } + + @ApiOperation(value = "获取任务图片的seed") + @GetMapping("/{id}/image-seed") + public String getSeed(@ApiParam(value = "任务ID") @PathVariable String id) { + String url = "mj/task/" + id + "/image-seed"; + Request request = mjOkHttpUtil.createGetRequest(url); + return mjOkHttpUtil.executeRequest(request); + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/VoiceController.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/VoiceController.java new file mode 100644 index 00000000..0a6b16c0 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/controller/VoiceController.java @@ -0,0 +1,120 @@ +package org.ruoyi.fusion.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.VoiceRoleBo; +import org.ruoyi.system.domain.vo.VoiceRoleVo; +import org.ruoyi.system.request.RoleListDto; +import org.ruoyi.system.request.RoleRequest; +import org.ruoyi.system.request.SimpleGenerateRequest; +import org.ruoyi.system.response.SimpleGenerateDataResponse; +import org.ruoyi.system.response.rolelist.RoleListVO; +import org.ruoyi.system.service.IVoiceRoleService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 配音角色 + * + * @author Lion Li + * @date 2024-03-19 + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/voice") +public class VoiceController extends BaseController { + + private final IVoiceRoleService voiceRoleService; + + /** + * 查询配音角色列表 + */ + @GetMapping("/list") + public List list(VoiceRoleBo bo) { + if(LoginHelper.getUserId() == null){ + return new ArrayList<>(); + } + bo.setCreateBy(LoginHelper.getUserId()); + return voiceRoleService.queryList(bo); + } + + /** + * 获取配音角色详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:role:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(voiceRoleService.queryById(id)); + } + + + /** + * 新增配音角色 + */ + @Log(title = "配音角色", businessType = BusinessType.INSERT) + @PostMapping("/add") + public R add(@RequestBody RoleRequest roleRequest) { + return toAjax(voiceRoleService.insertByBo(roleRequest)); + } + + /** + * 修改配音角色 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "配音角色", businessType = BusinessType.UPDATE) + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody VoiceRoleBo bo) { + return toAjax(voiceRoleService.updateByBo(bo)); + } + + /** + * 删除配音角色 + * + * @param ids 主键串 + */ + @Log(title = "配音角色", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(voiceRoleService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 实时语音生成 + */ + @PostMapping("/simpleGenerate") + public R simpleGenerate(@RequestBody SimpleGenerateRequest simpleGenerateRequest) { + return R.ok(voiceRoleService.simpleGenerate(simpleGenerateRequest)); + } + + /** + * 角色市场 + */ + @GetMapping("/roleList") + public R> roleList() { + return R.ok(voiceRoleService.roleList()); + } + + /** + * 收藏角色 + */ + @PostMapping("/copyRole") + public R copyRole(@RequestBody RoleListDto roleListDto) { + voiceRoleService.copyRole(roleListDto); + return R.ok(); + } +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/DomainObject.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/DomainObject.java new file mode 100644 index 00000000..f67407a8 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/DomainObject.java @@ -0,0 +1,72 @@ +package org.ruoyi.fusion.domain; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + + +public class DomainObject implements Serializable { + @Getter + @Setter + @ApiModelProperty("ID") + protected String id; + + @Setter + protected Map properties; // 扩展属性,仅支持基本类型 + + @JsonIgnore + private final transient Object lock = new Object(); + + public void sleep() throws InterruptedException { + synchronized (this.lock) { + this.lock.wait(); + } + } + + public void awake() { + synchronized (this.lock) { + this.lock.notifyAll(); + } + } + + public DomainObject setProperty(String name, Object value) { + getProperties().put(name, value); + return this; + } + + public DomainObject removeProperty(String name) { + getProperties().remove(name); + return this; + } + + public Object getProperty(String name) { + return getProperties().get(name); + } + + @SuppressWarnings("unchecked") + public T getPropertyGeneric(String name) { + return (T) getProperty(name); + } + + public T getProperty(String name, Class clz) { + return getProperty(name, clz, null); + } + + public T getProperty(String name, Class clz, T defaultValue) { + Object value = getProperty(name); + return value == null ? defaultValue : clz.cast(value); + } + + public Map getProperties() { + if (this.properties == null) { + this.properties = new HashMap<>(); + } + return this.properties; + } +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/InsightFace.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/InsightFace.java new file mode 100644 index 00000000..91f111e5 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/domain/InsightFace.java @@ -0,0 +1,22 @@ +package org.ruoyi.fusion.domain; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author WangLe + */ +@Data +@ApiModel("Discord账号") +public class InsightFace implements Serializable { + /**本人头像json*/ + @ApiModelProperty("本人头像json") + private String sourceBase64; + + /**明星头像json*/ + @ApiModelProperty("明星头像json") + private String targetBase64; +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/BaseSubmitDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/BaseSubmitDTO.java new file mode 100644 index 00000000..5a17c3b3 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/BaseSubmitDTO.java @@ -0,0 +1,16 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class BaseSubmitDTO { + + @ApiModelProperty("自定义参数") + protected String state; + + @ApiModelProperty("回调地址, 为空时使用全局notifyHook") + protected String notifyHook; +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitActionDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitActionDTO.java new file mode 100644 index 00000000..f59e2d3f --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitActionDTO.java @@ -0,0 +1,18 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + + +@Data +@ApiModel("变化任务提交参数") +public class SubmitActionDTO { + + private String customId; + + private String taskId; + + private String state; + + private String notifyHook; +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitBlendDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitBlendDTO.java new file mode 100644 index 00000000..bcad1746 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitBlendDTO.java @@ -0,0 +1,21 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.fusion.enums.BlendDimensions; + +import java.util.List; + +@Data +@ApiModel("Blend提交参数") +@EqualsAndHashCode(callSuper = true) +public class SubmitBlendDTO extends BaseSubmitDTO { + + @ApiModelProperty(value = "图片base64数组", required = true, example = "[\"data:image/png;base64,xxx1\", \"data:image/png;base64,xxx2\"]") + private List base64Array; + + @ApiModelProperty(value = "比例: PORTRAIT(2:3); SQUARE(1:1); LANDSCAPE(3:2)", example = "SQUARE") + private BlendDimensions dimensions = BlendDimensions.SQUARE; +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitChangeDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitChangeDTO.java new file mode 100644 index 00000000..c146d18c --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitChangeDTO.java @@ -0,0 +1,25 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.fusion.enums.TaskAction; + + +@Data +@ApiModel("变化任务提交参数") +@EqualsAndHashCode(callSuper = true) +public class SubmitChangeDTO extends BaseSubmitDTO { + + @ApiModelProperty(value = "任务ID", required = true, example = "\"1320098173412546\"") + private String taskId; + + @ApiModelProperty(value = "UPSCALE(放大); VARIATION(变换); REROLL(重新生成)", required = true, + allowableValues = "UPSCALE, VARIATION, REROLL", example = "UPSCALE") + private TaskAction action; + + @ApiModelProperty(value = "序号(1~4), action为UPSCALE,VARIATION时必传", allowableValues = "range[1, 4]", example = "1") + private Integer index; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitDescribeDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitDescribeDTO.java new file mode 100644 index 00000000..8e5d31a9 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitDescribeDTO.java @@ -0,0 +1,15 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@ApiModel("Describe提交参数") +@EqualsAndHashCode(callSuper = true) +public class SubmitDescribeDTO extends BaseSubmitDTO { + + @ApiModelProperty(value = "图片base64", required = true, example = "data:image/png;base64,xxx") + private String base64; +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitImagineDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitImagineDTO.java new file mode 100644 index 00000000..92c4b9d4 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitImagineDTO.java @@ -0,0 +1,26 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + + +@Data +@ApiModel("Imagine提交参数") +@EqualsAndHashCode(callSuper = true) +public class SubmitImagineDTO extends BaseSubmitDTO { + + @ApiModelProperty(value = "提示词", required = true, example = "Cat") + private String prompt; + + @ApiModelProperty(value = "垫图base64数组") + private List base64Array; + + @ApiModelProperty(hidden = true) + @Deprecated(since = "3.0", forRemoval = true) + private String base64; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitModalDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitModalDTO.java new file mode 100644 index 00000000..7ec28a75 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitModalDTO.java @@ -0,0 +1,19 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("局部重绘提交参数") +public class SubmitModalDTO extends BaseSubmitDTO{ + + private String maskBase64; + + private String taskId; + + private String prompt; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitShortenDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitShortenDTO.java new file mode 100644 index 00000000..ce6d1463 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitShortenDTO.java @@ -0,0 +1,17 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("prompt分析提交参数") +public class SubmitShortenDTO extends BaseSubmitDTO{ + + private String botType; + + private String prompt; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitSimpleChangeDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitSimpleChangeDTO.java new file mode 100644 index 00000000..279d5664 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/SubmitSimpleChangeDTO.java @@ -0,0 +1,17 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@ApiModel("变化任务提交参数-simple") +@EqualsAndHashCode(callSuper = true) +public class SubmitSimpleChangeDTO extends BaseSubmitDTO { + + @ApiModelProperty(value = "变化描述: ID $action$index", required = true, example = "1320098173412546 U2") + private String content; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/TaskConditionDTO.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/TaskConditionDTO.java new file mode 100644 index 00000000..fbcf3f2f --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/dto/TaskConditionDTO.java @@ -0,0 +1,14 @@ +package org.ruoyi.fusion.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel("任务查询参数") +public class TaskConditionDTO { + + private List ids; + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/ActionType.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/ActionType.java new file mode 100644 index 00000000..6c8337f0 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/ActionType.java @@ -0,0 +1,32 @@ +package org.ruoyi.fusion.enums; + +import lombok.Getter; + +/** + * @author WangLe + */ +@Getter +public enum ActionType { + IN_PAINT("Inpaint"), // 局部重绘操作 + RE_ROLL("reroll"), // 重绘操作 + UP_SAMPLE("upsample"), // 放大操作 + ZOOM("zoom"), // 变焦操作 + UPSCALE("upscale"), // 高清放大操作 + VARIATION("variation"); // 变化操作 + + private final String action; + + ActionType(String action) { + this.action = action; + } + + public static ActionType fromCustomId(String customId) { + for (ActionType type : values()) { + if (type.getAction().equals(customId)) { + return type; + } + } + return null; + } +} + diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/BlendDimensions.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/BlendDimensions.java new file mode 100644 index 00000000..0a787e11 --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/BlendDimensions.java @@ -0,0 +1,21 @@ +package org.ruoyi.fusion.enums; + + +import lombok.Getter; + +@Getter +public enum BlendDimensions { + + PORTRAIT("2:3"), + + SQUARE("1:1"), + + LANDSCAPE("3:2"); + + private final String value; + + BlendDimensions(String value) { + this.value = value; + } + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/TaskAction.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/TaskAction.java new file mode 100644 index 00000000..d3f8f3ed --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/enums/TaskAction.java @@ -0,0 +1,30 @@ +package org.ruoyi.fusion.enums; + + +public enum TaskAction { + /** + * 生成图片. + */ + IMAGINE, + /** + * 选中放大. + */ + UPSCALE, + /** + * 选中其中的一张图,生成四张相似的. + */ + VARIATION, + /** + * 重新执行. + */ + REROLL, + /** + * 图转prompt. + */ + DESCRIBE, + /** + * 多图混合. + */ + BLEND + +} diff --git a/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/util/MjOkHttpUtil.java b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/util/MjOkHttpUtil.java new file mode 100644 index 00000000..695e8e3b --- /dev/null +++ b/ruoyi-modules/ruoyi-fusion/src/main/java/org/ruoyi/fusion/util/MjOkHttpUtil.java @@ -0,0 +1,87 @@ +package org.ruoyi.fusion.util; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.service.ISysModelService; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author WangLe + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class MjOkHttpUtil { + + private final ISysModelService sysModelService; + + private final ConfigService configService; + + private static final String API_SECRET_HEADER = "mj-api-secret"; + + private String apiKey; + + private String apiHost; + + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(300, TimeUnit.SECONDS) + .writeTimeout(300, TimeUnit.SECONDS) + .readTimeout(300, TimeUnit.SECONDS) + .build(); + + public String executeRequest(Request request) { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + return response.body() != null ? response.body().string() : null; + } catch (IOException e) { + // 这里应根据实际情况使用适当的日志记录方式 + log.error("请求失败: {}",e.getMessage()); + return null; + } + } + + public Request createPostRequest(String url, String json) { + MediaType JSON = MediaType.get("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(json, JSON); + return new Request.Builder() + .url(apiHost + url) + .post(body) + .header(API_SECRET_HEADER, apiKey) + .build(); + } + + public Request createGetRequest(String url) { + return new Request.Builder() + .url(apiHost + url) + .header(API_SECRET_HEADER, apiKey) + .build(); + } + + @PostConstruct + public void init() { + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName("midjourney"); + List sysModelList = sysModelService.queryList(sysModelBo); + if (!sysModelList.isEmpty()) { + SysModelVo model = sysModelList.get(0); + this.apiKey = model.getApiKey(); + this.apiHost = model.getApiHost(); + } + } + + public String getKey(String key) { + return configService.getConfigValue("mj", key); + } +} + diff --git a/ruoyi-modules/ruoyi-system/pom.xml b/ruoyi-modules/ruoyi-system/pom.xml index ab8d7cfa..8353449c 100644 --- a/ruoyi-modules/ruoyi-system/pom.xml +++ b/ruoyi-modules/ruoyi-system/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - com.xmzs + org.ruoyi ruoyi-modules ${revision} ../pom.xml @@ -19,82 +19,89 @@ - com.xmzs + org.ruoyi ruoyi-common-core - com.xmzs + org.ruoyi ruoyi-common-doc - com.xmzs + org.ruoyi ruoyi-common-mybatis - com.xmzs + org.ruoyi ruoyi-common-translation - com.xmzs + org.ruoyi ruoyi-common-oss - com.xmzs + org.ruoyi ruoyi-common-log - com.xmzs + org.ruoyi ruoyi-common-excel - com.xmzs + org.ruoyi ruoyi-common-sms - com.xmzs + org.ruoyi ruoyi-common-tenant - com.xmzs + org.ruoyi ruoyi-common-security - com.xmzs + org.ruoyi ruoyi-common-web - com.xmzs + org.ruoyi ruoyi-common-idempotent - com.xmzs + org.ruoyi ruoyi-common-sensitive - com.xmzs + org.ruoyi ruoyi-common-chat - com.xmzs - ruoyi-common-wechat + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.2 + + + + + org.ruoyi + ruoyi-common-pay diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/GptConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/GptConfig.java new file mode 100644 index 00000000..1200f51a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/GptConfig.java @@ -0,0 +1,31 @@ +package org.ruoyi.system.cofing; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * gpt配置 + * + * @author ashinnotfound + * @date 2023/03/04 + */ +@Data +@Component +@ConfigurationProperties("gpt") +public class GptConfig { + private String baseUrl; + private String model; + private Integer maxToken; + private Double temperature; + private List basicPrompt; + private List apiKey; + private Long ofSeconds; + private String imageQuality; + private String imageStyle; + private String audioModel; + private String audioVoice; + private Double audioSpeed; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/OkHttpConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/OkHttpConfig.java new file mode 100644 index 00000000..90127bfe --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/OkHttpConfig.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.cofing; + +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.OkHttpUtil; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.service.ISysModelService; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class OkHttpConfig { + + private final ISysModelService sysModelService; + private final Map okHttpUtilMap = new HashMap<>(); + @Getter + private String generate; + + @PostConstruct + public void init() { + initializeOkHttpUtil("suno"); + initializeOkHttpUtil("luma"); + } + + private void initializeOkHttpUtil(String modelName) { + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName(modelName); + List sysModelList = sysModelService.queryList(sysModelBo); + if (!sysModelList.isEmpty()) { + SysModelVo model = sysModelList.get(0); + OkHttpUtil okHttpUtil = new OkHttpUtil(); + okHttpUtil.setApiHost(model.getApiHost()); + okHttpUtil.setApiKey(model.getApiKey()); + generate = String.valueOf(model.getModelPrice()); + okHttpUtilMap.put(modelName, okHttpUtil); + } + } + + public OkHttpUtil getOkHttpUtil(String modelName) { + return okHttpUtilMap.get(modelName); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/QqConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/QqConfig.java new file mode 100644 index 00000000..34ccccdc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/QqConfig.java @@ -0,0 +1,21 @@ +package org.ruoyi.system.cofing; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * qq配置 + * + * @author ashinnotfound + * @date 2023/03/04 + */ +@Data +@Component +@ConfigurationProperties("qq") +public class QqConfig { + private Boolean enable; + private Long account; + private Boolean acceptNewFriend; + private Boolean acceptNewGroup; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaConfiguration.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaConfiguration.java new file mode 100644 index 00000000..5fdc06d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaConfiguration.java @@ -0,0 +1,132 @@ +package org.ruoyi.system.cofing; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; +import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Admin + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(WxMaProperties.class) +public class WxMaConfiguration { + private final WxMaProperties properties; + + @Autowired + public WxMaConfiguration(WxMaProperties properties) { + this.properties = properties; + } + + @Bean + public WxMaService wxMaService() { + List configs = this.properties.getConfigs(); + if (configs == null) { + throw new WxRuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + WxMaService maService = new WxMaServiceImpl(); + maService.setMultiConfigs( + configs.stream() + .map(a -> { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); +// WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool()); + // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常 + config.setAppid(a.getAppid()); + config.setSecret(a.getSecret()); + config.setToken(a.getToken()); + config.setAesKey(a.getAesKey()); + config.setMsgDataFormat(a.getMsgDataFormat()); + return config; + }).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o))); + return maService; + } + + @Bean + public WxMaMessageRouter wxMaMessageRouter(WxMaService wxMaService) { + final WxMaMessageRouter router = new WxMaMessageRouter(wxMaService); + router + .rule().handler(logHandler).next() + .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end() + .rule().async(false).content("文本").handler(textHandler).end() + .rule().async(false).content("图片").handler(picHandler).end() + .rule().async(false).content("二维码").handler(qrcodeHandler).end(); + return router; + } + + private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder() + .templateId("此处更换为自己的模板id") + .data(Lists.newArrayList( + new WxMaSubscribeMessage.MsgData("keyword1", "339208499"))) + .toUser(wxMessage.getFromUser()) + .build()); + return null; + }; + + private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到消息:" + wxMessage.toString()); + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson()) + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息") + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> { + try { + WxMediaUploadResult uploadResult = service.getMediaService() + .uploadMedia("image", "png", + ClassLoader.getSystemResourceAsStream("tmp.png")); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + + private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> { + try { + final File file = service.getQrcodeService().createQrcode("123", 430); + WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaProperties.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaProperties.java new file mode 100644 index 00000000..eb3ad063 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/cofing/WxMaProperties.java @@ -0,0 +1,52 @@ +package org.ruoyi.system.cofing; + +/** + * 微信小程序属性配置类 + * + * @author: wangle + * @date: 2023/5/18 + */ + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * @author Binary Wang + */ +@Data +@ConfigurationProperties(prefix = "wx.miniapp") +public class WxMaProperties { + + private List configs; + + @Data + public static class Config { + /** + * 设置微信小程序的appid + */ + private String appid; + + /** + * 设置微信小程序的Secret + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/CacheController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/CacheController.java new file mode 100644 index 00000000..1b562f86 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/CacheController.java @@ -0,0 +1,55 @@ +package org.ruoyi.system.controller.monitor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.system.domain.vo.CacheListInfoVo; +import lombok.RequiredArgsConstructor; +import org.redisson.spring.data.connection.RedissonConnectionFactory; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; + +/** + * 缓存监控 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/monitor/cache") +public class CacheController { + + private final RedissonConnectionFactory connectionFactory; + + /** + * 获取缓存监控列表 + */ + @SaCheckPermission("monitor:cache:list") + @GetMapping() + public R getInfo() throws Exception { + RedisConnection connection = connectionFactory.getConnection(); + Properties commandStats = connection.commands().info("commandstats"); + + List> pieList = new ArrayList<>(); + if (commandStats != null) { + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + } + + CacheListInfoVo infoVo = new CacheListInfoVo(); + infoVo.setInfo(connection.commands().info()); + infoVo.setDbSize(connection.commands().dbSize()); + infoVo.setCommandStats(pieList); + return R.ok(infoVo); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysLogininforController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysLogininforController.java new file mode 100644 index 00000000..c7e195e8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysLogininforController.java @@ -0,0 +1,89 @@ +package org.ruoyi.system.controller.monitor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysLogininforBo; +import org.ruoyi.system.domain.vo.SysLogininforVo; +import org.ruoyi.system.service.ISysLogininforService; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 系统访问记录 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController { + + private final ISysLogininforService logininforService; + + /** + * 获取系统访问记录列表 + */ + @SaCheckPermission("monitor:logininfor:list") + @GetMapping("/list") + public TableDataInfo list(SysLogininforBo logininfor, PageQuery pageQuery) { + return logininforService.selectPageLogininforList(logininfor, pageQuery); + } + + /** + * 导出系统访问记录列表 + */ + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @SaCheckPermission("monitor:logininfor:export") + @PostMapping("/export") + public void export(SysLogininforBo logininfor, HttpServletResponse response) { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil.exportExcel(list, "登录日志", SysLogininforVo.class, response); + } + + /** + * 批量删除登录日志 + * @param infoIds 日志ids + */ + @SaCheckPermission("monitor:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public R remove(@PathVariable Long[] infoIds) { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + /** + * 清理系统访问记录 + */ + @SaCheckPermission("monitor:logininfor:remove") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public R clean() { + logininforService.cleanLogininfor(); + return R.ok(); + } + + @SaCheckPermission("monitor:logininfor:unlock") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public R unlock(@PathVariable("userName") String userName) { + String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName; + if (RedisUtils.hasKey(loginName)) { + RedisUtils.deleteObject(loginName); + } + return R.ok(); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysOperlogController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysOperlogController.java new file mode 100644 index 00000000..cf9db255 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysOperlogController.java @@ -0,0 +1,75 @@ +package org.ruoyi.system.controller.monitor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysOperLogBo; +import org.ruoyi.system.domain.vo.SysOperLogVo; +import org.ruoyi.system.service.ISysOperLogService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 操作日志记录 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController { + + private final ISysOperLogService operLogService; + + /** + * 获取操作日志记录列表 + */ + @SaCheckPermission("monitor:operlog:list") + @GetMapping("/list") + public TableDataInfo list(SysOperLogBo operLog, PageQuery pageQuery) { + return operLogService.selectPageOperLogList(operLog, pageQuery); + } + + /** + * 导出操作日志记录列表 + */ + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @SaCheckPermission("monitor:operlog:export") + @PostMapping("/export") + public void export(SysOperLogBo operLog, HttpServletResponse response) { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil.exportExcel(list, "操作日志", SysOperLogVo.class, response); + } + + /** + * 批量删除操作日志记录 + * @param operIds 日志ids + */ + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @SaCheckPermission("monitor:operlog:remove") + @DeleteMapping("/{operIds}") + public R remove(@PathVariable Long[] operIds) { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + /** + * 清理操作日志记录 + */ + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @SaCheckPermission("monitor:operlog:remove") + @DeleteMapping("/clean") + public R clean() { + operLogService.cleanOperLog(); + return R.ok(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysUserOnlineController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysUserOnlineController.java new file mode 100644 index 00000000..15b1d8b1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,90 @@ +package org.ruoyi.system.controller.monitor; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.domain.dto.UserOnlineDTO; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.SysUserOnline; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 在线用户监控 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController { + + /** + * 获取在线用户监控列表 + * + * @param ipaddr IP地址 + * @param userName 用户名 + */ + @SaCheckPermission("monitor:online:list") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) { + // 获取所有未过期的 token + List keys = StpUtil.searchTokenValue("", 0, -1, false); + List userOnlineDTOList = new ArrayList<>(); + for (String key : keys) { + String token = StringUtils.substringAfterLast(key, ":"); + // 如果已经过期则跳过 + if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { + continue; + } + userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token)); + } + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(ipaddr, userOnline.getIpaddr()) && + StringUtils.equals(userName, userOnline.getUserName()) + ); + } else if (StringUtils.isNotEmpty(ipaddr)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(ipaddr, userOnline.getIpaddr()) + ); + } else if (StringUtils.isNotEmpty(userName)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(userName, userOnline.getUserName()) + ); + } + Collections.reverse(userOnlineDTOList); + userOnlineDTOList.removeAll(Collections.singleton(null)); + List userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class); + return TableDataInfo.build(userOnlineList); + } + + /** + * 强退用户 + * + * @param tokenId token值 + */ + @SaCheckPermission("monitor:online:forceLogout") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public R forceLogout(@PathVariable String tokenId) { + try { + StpUtil.kickoutByTokenValue(tokenId); + } catch (NotLoginException ignored) { + } + return R.ok(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatConfigController.java new file mode 100644 index 00000000..5e304c8c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatConfigController.java @@ -0,0 +1,107 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.ChatConfigBo; +import org.ruoyi.system.domain.vo.ChatConfigVo; +import org.ruoyi.system.service.IChatConfigService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 对话配置信息 + * + * @author Lion Li + * @date 2024-04-13 + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/chat/config") +public class ChatConfigController extends BaseController { + + private final IChatConfigService chatConfigService; + + private final ConfigService configService; + + /** + * 查询配置信息列表 + */ + @GetMapping("/list") + @SaCheckPermission("system:config:list") + public R> list(ChatConfigBo bo) { + return R.ok(chatConfigService.queryList(bo)); + } + + /** + * 获取对话配置信详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(chatConfigService.queryById(id)); + } + + /** + * 根据参数键名查询系统参数值 + * + * @param configKey 参数Key + */ + @GetMapping(value = "/configKey/{configKey}") + public R getConfigKey(@PathVariable String configKey) { + return R.ok(configService.getConfigValue("sys",configKey)); + } + + /** + * 查询系统参数 + * + */ + @GetMapping(value = "/sysConfigKey") + public R> getSysConfigKey() { + return R.ok(chatConfigService.getSysConfigValue("sys")); + } + + /** + * 新增对话配置信息 + */ + @PostMapping("/add") + public R add(@RequestBody List boList) { + for (ChatConfigBo chatConfigBo : boList) { + if(chatConfigBo.getId() == null){ + chatConfigService.insertByBo(chatConfigBo); + }else { + chatConfigService.updateByBo(chatConfigBo); + } + } + return toAjax(true); + } + + /** + * 修改对话配置信息 + */ + @PutMapping("/edit") + public R edit(@Validated(EditGroup.class) @RequestBody ChatConfigBo bo) { + return toAjax(chatConfigService.updateByBo(bo)); + } + + /** + * 删除对话配置信息 + * + * @param ids 主键串 + */ + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(chatConfigService.deleteWithValidByIds(List.of(ids), true)); + } + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatGptsController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatGptsController.java new file mode 100644 index 00000000..5610e868 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatGptsController.java @@ -0,0 +1,105 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.ChatGptsBo; +import org.ruoyi.system.domain.vo.ChatGptsVo; +import org.ruoyi.system.service.IChatGptsService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * gpts管理 + * + * @author Lion Li + * @date 2024-07-09 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/gpts") +public class ChatGptsController extends BaseController { + + private final IChatGptsService chatGptsService; + + /** + * 查询gpts管理列表 + */ + @GetMapping("/list") + public TableDataInfo list(ChatGptsBo bo, PageQuery pageQuery) { + return chatGptsService.queryPageList(bo, pageQuery); + } + + /** + * 导出gpts管理列表 + */ + @SaCheckPermission("system:gpts:export") + @Log(title = "gpts管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ChatGptsBo bo, HttpServletResponse response) { + List list = chatGptsService.queryList(bo); + ExcelUtil.exportExcel(list, "gpts管理", ChatGptsVo.class, response); + } + + /** + * 获取gpts管理详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:gpts:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(chatGptsService.queryById(id)); + } + + /** + * 新增gpts管理 + */ + @SaCheckPermission("system:gpts:add") + @Log(title = "gpts管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ChatGptsBo bo) { + return toAjax(chatGptsService.insertByBo(bo)); + } + + /** + * 修改gpts管理 + */ + @SaCheckPermission("system:gpts:edit") + @Log(title = "gpts管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ChatGptsBo bo) { + return toAjax(chatGptsService.updateByBo(bo)); + } + + /** + * 删除gpts管理 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:gpts:remove") + @Log(title = "gpts管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(chatGptsService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatMessageController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatMessageController.java new file mode 100644 index 00000000..8487e932 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatMessageController.java @@ -0,0 +1,120 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.vo.ChatMessageVo; +import org.ruoyi.system.service.IChatMessageService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 聊天消息 + * + * @author Lion Li + * @date 2024-04-16 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/message") +public class ChatMessageController extends BaseController { + + private final IChatMessageService chatMessageService; + + /** + * 查询聊天消息列表 + */ + @SaCheckPermission("system:message:list") + @GetMapping("/list") + public TableDataInfo list(ChatMessageBo bo, PageQuery pageQuery) { + pageQuery.setOrderByColumn("createTime"); + pageQuery.setIsAsc("desc"); + return chatMessageService.queryPageList(bo, pageQuery); + } + + /** + * 查询我的聊天消息列表 + */ + @GetMapping("/listByUser") + public R> listByUser(ChatMessageBo bo, PageQuery pageQuery) { + bo.setUserId(LoginHelper.getUserId()); + pageQuery.setOrderByColumn("createTime"); + pageQuery.setIsAsc("desc"); + return R.ok(chatMessageService.queryPageList(bo, pageQuery)); + } + + /** + * 导出聊天消息列表 + */ + @SaCheckPermission("system:message:export") + @Log(title = "聊天消息", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ChatMessageBo bo, HttpServletResponse response) { + List list = chatMessageService.queryList(bo); + ExcelUtil.exportExcel(list, "聊天消息", ChatMessageVo.class, response); + } + + /** + * 获取聊天消息详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:message:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(chatMessageService.queryById(id)); + } + + /** + * 新增聊天消息 + */ + @SaCheckPermission("system:message:add") + @Log(title = "聊天消息", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ChatMessageBo bo) { + return toAjax(chatMessageService.insertByBo(bo)); + } + + /** + * 修改聊天消息 + */ + @SaCheckPermission("system:message:edit") + @Log(title = "聊天消息", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ChatMessageBo bo) { + return toAjax(chatMessageService.updateByBo(bo)); + } + + /** + * 删除聊天消息 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:message:remove") + @Log(title = "聊天消息", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(chatMessageService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVisitorUsageController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVisitorUsageController.java new file mode 100644 index 00000000..5871128e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVisitorUsageController.java @@ -0,0 +1,106 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.ChatVisitorUsageBo; +import org.ruoyi.system.domain.vo.ChatVisitorUsageVo; +import org.ruoyi.system.service.IChatVisitorUsageService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 访客管理 + * + * @author Lion Li + * @date 2024-07-14 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/visitorUsage") +public class ChatVisitorUsageController extends BaseController { + + private final IChatVisitorUsageService chatVisitorUsageService; + + /** + * 查询访客管理列表 + */ + @SaCheckPermission("system:visitorUsage:list") + @GetMapping("/list") + public TableDataInfo list(ChatVisitorUsageBo bo, PageQuery pageQuery) { + return chatVisitorUsageService.queryPageList(bo, pageQuery); + } + + /** + * 导出访客管理列表 + */ + @SaCheckPermission("system:visitorUsage:export") + @Log(title = "访客管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ChatVisitorUsageBo bo, HttpServletResponse response) { + List list = chatVisitorUsageService.queryList(bo); + ExcelUtil.exportExcel(list, "访客管理", ChatVisitorUsageVo.class, response); + } + + /** + * 获取访客管理详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:visitorUsage:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(chatVisitorUsageService.queryById(id)); + } + + /** + * 新增访客管理 + */ + @SaCheckPermission("system:visitorUsage:add") + @Log(title = "访客管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ChatVisitorUsageBo bo) { + return toAjax(chatVisitorUsageService.insertByBo(bo)); + } + + /** + * 修改访客管理 + */ + @SaCheckPermission("system:visitorUsage:edit") + @Log(title = "访客管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ChatVisitorUsageBo bo) { + return toAjax(chatVisitorUsageService.updateByBo(bo)); + } + + /** + * 删除访客管理 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:visitorUsage:remove") + @Log(title = "访客管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(chatVisitorUsageService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVoucherController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVoucherController.java new file mode 100644 index 00000000..fcda82df --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/ChatVoucherController.java @@ -0,0 +1,123 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.ChatVoucherBo; +import org.ruoyi.system.domain.vo.ChatVoucherVo; +import org.ruoyi.system.service.IChatVoucherService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +/** + * 用户兑换记录 + * + * @author Lion Li + * @date 2024-05-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/voucher") +public class ChatVoucherController extends BaseController { + + private final IChatVoucherService chatVoucherService; + + /** + * 查询用户兑换记录列表 + */ + @SaCheckPermission("system:voucher:list") + @GetMapping("/list") + public TableDataInfo list(ChatVoucherBo bo, PageQuery pageQuery) { + return chatVoucherService.queryPageList(bo, pageQuery); + } + + /** + * 导出用户兑换记录列表 + */ + @SaCheckPermission("system:voucher:export") + @Log(title = "用户兑换记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(ChatVoucherBo bo, HttpServletResponse response) { + List list = chatVoucherService.queryList(bo); + ExcelUtil.exportExcel(list, "用户兑换记录", ChatVoucherVo.class, response); + } + + /** + * 获取用户兑换记录详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:voucher:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(chatVoucherService.queryById(id)); + } + + /** + * 新增用户兑换记录 + */ + @SaCheckPermission("system:voucher:add") + @Log(title = "用户兑换记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody ChatVoucherBo bo) { + bo.setCode(UUID.randomUUID().toString().replace("-", "")); + return toAjax(chatVoucherService.insertByBo(bo)); + } + + /** + * 兑换卡密 + * + * @param bo 卡密信息 + * @return 是否兑换成功 + */ + @PostMapping("/redeem") + public R redeem(@RequestBody ChatVoucherBo bo) { + if(chatVoucherService.redeem(bo)){ + return R.ok("兑换成功!"); + }else { + return R.fail("兑换失败,请联系管理员!"); + } + } + + /** + * 修改用户兑换记录 + */ + @SaCheckPermission("system:voucher:edit") + @Log(title = "用户兑换记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody ChatVoucherBo bo) { + return toAjax(chatVoucherService.updateByBo(bo)); + } + + /** + * 删除用户兑换记录 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:voucher:remove") + @Log(title = "用户兑换记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(chatVoucherService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PayController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PayController.java new file mode 100644 index 00000000..5e9d1930 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PayController.java @@ -0,0 +1,333 @@ +package org.ruoyi.system.controller.system; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; +import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; +import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; +import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.stripe.Stripe; +import com.stripe.exception.StripeException; +import com.stripe.model.Event; +import com.stripe.model.Price; +import com.stripe.model.Product; +import com.stripe.model.checkout.Session; +import com.stripe.net.Webhook; +import com.stripe.param.checkout.SessionCreateParams; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.config.PayConfig; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.oss.core.OssClient; +import org.ruoyi.common.oss.entity.UploadResult; +import org.ruoyi.common.oss.factory.OssFactory; +import org.ruoyi.common.response.PayResponse; +import org.ruoyi.common.service.PayService; +import org.ruoyi.common.utils.MD5Util; +import org.ruoyi.system.domain.bo.PaymentOrdersBo; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.request.OrderRequest; +import org.ruoyi.system.domain.vo.PaymentOrdersVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.service.IPaymentOrdersService; +import org.ruoyi.system.service.ISysUserService; +import org.springframework.web.bind.annotation.*; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +@RequiredArgsConstructor +@RestController +@RequestMapping("/pay") +@Slf4j +public class PayController { + + private final PayService payService; + + private final ISysUserService userService; + + private final IPaymentOrdersService paymentOrdersService; + + private final PayConfig payConfig; + + private final WxPayService wxService; + + private final ConfigService configService; + + /** + * 获取支付二维码 + * + * @Date 2023/7/3 + * @return void + **/ + @PostMapping("/payUrl") + public R payUrl(@RequestBody OrderRequest orderRequest) { + PaymentOrdersBo payOrder = paymentOrdersService.createPayOrder(orderRequest); + PaymentOrdersVo paymentOrdersVo = new PaymentOrdersVo(); + if(!Boolean.parseBoolean(getKey("enabled"))){ + String payUrl = payService.getPayUrl(payOrder.getOrderNo(), orderRequest.getName(), Double.parseDouble(orderRequest.getMoney()), "192.168.1.6"); + byte[] bytes = QrCodeUtil.generatePng(payUrl, 300, 300); + OssClient storage = OssFactory.instance(); + UploadResult upload=storage.upload(bytes, storage.getPath("qrCode",".png"), "image/png"); + BeanUtil.copyProperties(payOrder,paymentOrdersVo); + paymentOrdersVo.setUrl(upload.getUrl()); + }else { + WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); + request.setTradeType("NATIVE"); + request.setBody(orderRequest.getName()); + request.setOutTradeNo(payOrder.getOrderNo()); + request.setTotalFee(BaseWxPayRequest.yuanToFen(orderRequest.getMoney())); + request.setSpbillCreateIp("127.0.0.1"); + request.setNotifyUrl(getKey("notifyUrl")); + request.setProductId(payOrder.getId().toString()); + try { + WxPayNativeOrderResult order = wxService.createOrder(request); + byte[] bytes = QrCodeUtil.generatePng(order.getCodeUrl(), 300, 300); + OssClient storage = OssFactory.instance(); + UploadResult upload = storage.upload(bytes, storage.getPath("qrCode",".png"), "image/png"); + BeanUtil.copyProperties(payOrder,paymentOrdersVo); + paymentOrdersVo.setUrl(upload.getUrl()); + } catch (WxPayException e) { + throw new BaseException("获取微信支付二维码发生错误:{}"+e.getMessage()); + } + } + return R.ok(paymentOrdersVo); + } + + /** + * 回调通知地址 + * + * @Date 2023/7/3 + * @param + * @return void + **/ + @GetMapping("/notifyUrl") + public String notifyUrl(PayResponse payResponse) { + // 校验签名 + String mdString = "money=" + payResponse.getMoney() + "&name=" + payResponse.getName() + + "&out_trade_no=" + payResponse.getOut_trade_no() + "&pid=" + payConfig.getPid() + + "&trade_no=" + payResponse.getTrade_no() + "&trade_status=" + payResponse.getTrade_status() + + "&type=" + payResponse.getType() + payConfig.getKey(); + String sign = MD5Util.GetMD5Code(mdString); + if(!sign.equals(payResponse.getSign())){ + throw new BaseException("校验签名失败!"); + } + double money = Double.parseDouble(payResponse.getMoney()); + log.info("支付订单号{}",payResponse); + PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); + paymentOrdersBo.setOrderNo(payResponse.getOut_trade_no()); + List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); + if (CollectionUtil.isEmpty(paymentOrdersList)){ + throw new BaseException("订单不存在!"); + } + // 订单状态修改为已支付 + PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); + paymentOrdersVo.setPaymentStatus("2"); + paymentOrdersVo.setPaymentMethod(payResponse.getType()); + BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo); + paymentOrdersService.updateByBo(paymentOrdersBo); + + SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId()); + sysUserVo.setUserBalance(sysUserVo.getUserBalance() + money); + SysUserBo sysUserBo = new SysUserBo(); + BeanUtil.copyProperties(sysUserVo,sysUserBo); + // 设置为付费用户 + sysUserBo.setUserGrade("1"); + userService.updateUser(sysUserBo); + return "success"; + } + + /** + * 跳转通知地址 + * + * @Date 2023/7/3 + * @param + * @return void + **/ + @GetMapping("/return_url") + public void returnUrl() { + log.info("return_url==========="); + } + + + @PostMapping("/notify/wxOrder") + public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException { + WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData); + // TODO 根据自己业务场景需要构造返回对象 + PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); + paymentOrdersBo.setOrderNo(notifyResult.getOutTradeNo()); + List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); + PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); + paymentOrdersVo.setPaymentStatus("2"); + paymentOrdersVo.setPaymentMethod("wx"); + BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo); + paymentOrdersService.updateByBo(paymentOrdersBo); + SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId()); + sysUserVo.setUserBalance(sysUserVo.getUserBalance() + convertCentsToYuan(notifyResult.getTotalFee())); + SysUserBo sysUserBo = new SysUserBo(); + BeanUtil.copyProperties(sysUserVo,sysUserBo); + // 设置为付费用户 + sysUserBo.setUserGrade("1"); + userService.updateUser(sysUserBo); + return WxPayNotifyResponse.success("success"); + } + + /** + * 将分转换为元,并保留精度。 + * + * @param cents 分的金额,类型为Integer + * @return 转换后的元金额,类型为double + */ + public static double convertCentsToYuan(Integer cents) { + // 处理空输入 + if (cents == null) { + throw new IllegalArgumentException("输入的分金额不能为空"); + } + + // 100分 = 1元 + BigDecimal centsBigDecimal = new BigDecimal(cents); + BigDecimal yuan = centsBigDecimal.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); + // 转换为double并返回 + return yuan.doubleValue(); + } + + /** + * 获取订单信息 + * + */ + @PostMapping("/orderInfo") + public R orderInfo(@RequestBody OrderRequest orderRequest) { + if(StringUtils.isEmpty(orderRequest.getOrderNo())){ + throw new BaseException("订单号不能为空!"); + } + PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); + paymentOrdersBo.setOrderNo(orderRequest.getOrderNo()); + List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); + if (CollectionUtil.isEmpty(paymentOrdersList)){ + throw new BaseException("订单不存在!"); + } + PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); + return R.ok(paymentOrdersVo); + } + + // 获取支付链接 +// static { +// Stripe.apiKey = "sk_test_51PMMj2KcfX4oNioqXkoKpScTsgmR55xQki2tg8MEZJYc0gjhYV85t2FzDasE06eqZb0sqyYhOp3UXhcGGQLWI4A9008aq8SOnb"; +// } + + /** + * 去支付 + * 1、创建产品 + * 2、设置价格 + * 3、创建支付信息 得到url + * @return + */ + @PostMapping("/stripePay") + public String pay(@RequestBody OrderRequest orderRequest) throws StripeException { + + String enabled = configService.getConfigValue("stripe", "enabled"); + if(!Boolean.parseBoolean(enabled)){ + String prompt = configService.getConfigValue("stripe", "prompt"); + throw new BaseException(prompt); + } + + // 获取支付链接 + Stripe.apiKey = configService.getConfigValue("stripe", "key"); + + // 获取金额字符串并解析为 double + double moneyDouble = Double.parseDouble(orderRequest.getMoney()); + + // 将金额转换为以分为单位的整数 + int randMoney = (int) (moneyDouble * 100); + + Map params = new HashMap<>(); + params.put("name", orderRequest.getName()); + Product product = Product.create(params); + + Map recurring = new HashMap<>(); + recurring.put("interval", "month"); + Map params2 = new HashMap<>(); + params2.put("unit_amount", randMoney); + params2.put("currency", "usd"); + params2.put("recurring", recurring); + params2.put("product", product.getId()); + Price price = Price.create(params2); + + // 创建支付订单 + PaymentOrdersBo payOrder = paymentOrdersService.createPayOrder(orderRequest); + + //创建支付信息 得到url + SessionCreateParams params3 = SessionCreateParams.builder() + .setMode(SessionCreateParams.Mode.SUBSCRIPTION) + .setSuccessUrl(configService.getConfigValue("stripe", "success")) + .setCancelUrl(configService.getConfigValue("stripe", "cancel")) + .addLineItem( + SessionCreateParams.LineItem.builder() + .setQuantity(1L) + .setPrice(price.getId()) + .build()).putMetadata("orderId", payOrder.getOrderNo()) + .build(); + Session session = Session.create(params3); + return session.getUrl(); + } + + /** + * 支付回调 + * + */ + @PostMapping("/stripe_events") + public R stripeEvent(HttpServletRequest request) { + try { + String endpointSecret = configService.getConfigValue("stripe", "secret");//webhook秘钥签名 + InputStream inputStream = request.getInputStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024*4]; + int n = 0; + while (-1 != (n = inputStream.read(buffer))) { + output.write(buffer, 0, n); + } + byte[] bytes = output.toByteArray(); + String payload = new String(bytes, "UTF-8"); + String sigHeader = request.getHeader("Stripe-Signature"); + Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret);//验签,并获取事件 + if("checkout.session.completed".equals(event.getType())){ + // 解析 JSON 字符串为 JSONObject + JSONObject jsonObject = JSONUtil.parseObj(event); + // 获取 metadata 对象 + JSONObject metadata = jsonObject.getJSONObject("data") + .getJSONObject("object") + .getJSONObject("metadata"); + + OrderRequest orderRequest = new OrderRequest(); + orderRequest.setPayType("stripe"); + orderRequest.setOrderNo(metadata.getStr("orderId")); + paymentOrdersService.updatePayOrder(orderRequest); + } + } catch (Exception e) { + System.out.println("stripe异步通知(webhook事件)"+e); + } + return R.ok(); + } + + public String getKey(String key) { + return configService.getConfigValue("weixin", key); + } + +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PaymentOrdersController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PaymentOrdersController.java new file mode 100644 index 00000000..97855b30 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/PaymentOrdersController.java @@ -0,0 +1,108 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.PaymentOrdersBo; +import org.ruoyi.system.domain.vo.PaymentOrdersVo; +import org.ruoyi.system.service.IPaymentOrdersService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 支付订单 + * + * @author Lion Li + * @date 2024-04-16 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/orders") +public class PaymentOrdersController extends BaseController { + + private final IPaymentOrdersService paymentOrdersService; + + /** + * 查询支付订单列表 + */ + @SaCheckPermission("system:orders:list") + @GetMapping("/list") + public TableDataInfo list(PaymentOrdersBo bo, PageQuery pageQuery) { + pageQuery.setOrderByColumn("createTime"); + pageQuery.setIsAsc("desc"); + return paymentOrdersService.queryPageList(bo, pageQuery); + } + + /** + * 导出支付订单列表 + */ + @SaCheckPermission("system:orders:export") + @Log(title = "支付订单", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(PaymentOrdersBo bo, HttpServletResponse response) { + List list = paymentOrdersService.queryList(bo); + ExcelUtil.exportExcel(list, "支付订单", PaymentOrdersVo.class, response); + } + + /** + * 获取支付订单详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:orders:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(paymentOrdersService.queryById(id)); + } + + /** + * 新增支付订单 + */ + @SaCheckPermission("system:orders:add") + @Log(title = "支付订单", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody PaymentOrdersBo bo) { + return toAjax(paymentOrdersService.insertByBo(bo)); + } + + /** + * 修改支付订单 + */ + @SaCheckPermission("system:orders:edit") + @Log(title = "支付订单", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody PaymentOrdersBo bo) { + return toAjax(paymentOrdersService.updateByBo(bo)); + } + + /** + * 删除支付订单 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:orders:remove") + @Log(title = "支付订单", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(paymentOrdersService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysConfigController.java new file mode 100644 index 00000000..f7985276 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysConfigController.java @@ -0,0 +1,137 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysConfigBo; +import org.ruoyi.system.domain.vo.SysConfigVo; +import org.ruoyi.system.service.ISysConfigService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 参数配置 信息操作处理 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController { + + private final ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @SaCheckPermission("system:config:list") + @GetMapping("/list") + public TableDataInfo list(SysConfigBo config, PageQuery pageQuery) { + return configService.selectPageConfigList(config, pageQuery); + } + + /** + * 导出参数配置列表 + */ + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:config:export") + @PostMapping("/export") + public void export(SysConfigBo config, HttpServletResponse response) { + List list = configService.selectConfigList(config); + ExcelUtil.exportExcel(list, "参数数据", SysConfigVo.class, response); + } + + /** + * 根据参数编号获取详细信息 + * + * @param configId 参数ID + */ + @SaCheckPermission("system:config:query") + @GetMapping(value = "/{configId}") + public R getInfo(@PathVariable Long configId) { + return R.ok(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + * + * @param configKey 参数Key + */ + @GetMapping(value = "/configKey/{configKey}") + public R getConfigKey(@PathVariable String configKey) { + return R.ok(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @SaCheckPermission("system:config:add") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysConfigBo config) { + if (!configService.checkConfigKeyUnique(config)) { + return R.fail("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + configService.insertConfig(config); + return R.ok(); + } + + /** + * 修改参数配置 + */ + @SaCheckPermission("system:config:edit") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysConfigBo config) { + if (!configService.checkConfigKeyUnique(config)) { + return R.fail("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + configService.updateConfig(config); + return R.ok(); + } + + /** + * 根据参数键名修改参数配置 + */ + @SaCheckPermission("system:config:edit") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping("/updateByKey") + public R updateByKey(@RequestBody SysConfigBo config) { + configService.updateConfig(config); + return R.ok(); + } + + /** + * 删除参数配置 + * + * @param configIds 参数ID串 + */ + @SaCheckPermission("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public R remove(@PathVariable Long[] configIds) { + configService.deleteConfigByIds(configIds); + return R.ok(); + } + + /** + * 刷新参数缓存 + */ + @SaCheckPermission("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public R refreshCache() { + configService.resetConfigCache(); + return R.ok(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDeptController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDeptController.java new file mode 100644 index 00000000..7bf042b6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDeptController.java @@ -0,0 +1,120 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.convert.Convert; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysDeptBo; +import org.ruoyi.system.domain.vo.SysDeptVo; +import org.ruoyi.system.service.ISysDeptService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 部门信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController { + + private final ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @SaCheckPermission("system:dept:list") + @GetMapping("/list") + public R> list(SysDeptBo dept) { + List depts = deptService.selectDeptList(dept); + return R.ok(depts); + } + + /** + * 查询部门列表(排除节点) + * + * @param deptId 部门ID + */ + @SaCheckPermission("system:dept:list") + @GetMapping("/list/exclude/{deptId}") + public R> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) { + List depts = deptService.selectDeptList(new SysDeptBo()); + depts.removeIf(d -> d.getDeptId().equals(deptId) + || StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId))); + return R.ok(depts); + } + + /** + * 根据部门编号获取详细信息 + * + * @param deptId 部门ID + */ + @SaCheckPermission("system:dept:query") + @GetMapping(value = "/{deptId}") + public R getInfo(@PathVariable Long deptId) { + deptService.checkDeptDataScope(deptId); + return R.ok(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @SaCheckPermission("system:dept:add") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysDeptBo dept) { + if (!deptService.checkDeptNameUnique(dept)) { + return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @SaCheckPermission("system:dept:edit") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysDeptBo dept) { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) { + return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } else if (dept.getParentId().equals(deptId)) { + return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) + && deptService.selectNormalChildrenDeptById(deptId) > 0) { + return R.fail("该部门包含未停用的子部门!"); + } + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + * + * @param deptId 部门ID + */ + @SaCheckPermission("system:dept:remove") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public R remove(@PathVariable Long deptId) { + if (deptService.hasChildByDeptId(deptId)) { + return R.warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) { + return R.warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictDataController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictDataController.java new file mode 100644 index 00000000..3d30d9b5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictDataController.java @@ -0,0 +1,117 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.ObjectUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysDictDataBo; +import org.ruoyi.system.domain.vo.SysDictDataVo; +import org.ruoyi.system.service.ISysDictDataService; +import org.ruoyi.system.service.ISysDictTypeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 数据字典信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController { + + private final ISysDictDataService dictDataService; + private final ISysDictTypeService dictTypeService; + + /** + * 查询字典数据列表 + */ + @SaCheckPermission("system:dict:list") + @GetMapping("/list") + public TableDataInfo list(SysDictDataBo dictData, PageQuery pageQuery) { + return dictDataService.selectPageDictDataList(dictData, pageQuery); + } + + /** + * 导出字典数据列表 + */ + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:dict:export") + @PostMapping("/export") + public void export(SysDictDataBo dictData, HttpServletResponse response) { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil.exportExcel(list, "字典数据", SysDictDataVo.class, response); + } + + /** + * 查询字典数据详细 + * + * @param dictCode 字典code + */ + @SaCheckPermission("system:dict:query") + @GetMapping(value = "/{dictCode}") + public R getInfo(@PathVariable Long dictCode) { + return R.ok(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + * + * @param dictType 字典类型 + */ + @GetMapping(value = "/type/{dictType}") + public R> dictType(@PathVariable String dictType) { + List data = dictTypeService.selectDictDataByType(dictType); + if (ObjectUtil.isNull(data)) { + data = new ArrayList<>(); + } + return R.ok(data); + } + + /** + * 新增字典类型 + */ + @SaCheckPermission("system:dict:add") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysDictDataBo dict) { + dictDataService.insertDictData(dict); + return R.ok(); + } + + /** + * 修改保存字典类型 + */ + @SaCheckPermission("system:dict:edit") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysDictDataBo dict) { + dictDataService.updateDictData(dict); + return R.ok(); + } + + /** + * 删除字典类型 + * + * @param dictCodes 字典code串 + */ + @SaCheckPermission("system:dict:remove") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public R remove(@PathVariable Long[] dictCodes) { + dictDataService.deleteDictDataByIds(dictCodes); + return R.ok(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictTypeController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictTypeController.java new file mode 100644 index 00000000..35fdcff7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysDictTypeController.java @@ -0,0 +1,125 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysDictTypeBo; +import org.ruoyi.system.domain.vo.SysDictTypeVo; +import org.ruoyi.system.service.ISysDictTypeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 数据字典信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController { + + private final ISysDictTypeService dictTypeService; + + /** + * 查询字典类型列表 + */ + @SaCheckPermission("system:dict:list") + @GetMapping("/list") + public TableDataInfo list(SysDictTypeBo dictType, PageQuery pageQuery) { + return dictTypeService.selectPageDictTypeList(dictType, pageQuery); + } + + /** + * 导出字典类型列表 + */ + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:dict:export") + @PostMapping("/export") + public void export(SysDictTypeBo dictType, HttpServletResponse response) { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil.exportExcel(list, "字典类型", SysDictTypeVo.class, response); + } + + /** + * 查询字典类型详细 + * + * @param dictId 字典ID + */ + @SaCheckPermission("system:dict:query") + @GetMapping(value = "/{dictId}") + public R getInfo(@PathVariable Long dictId) { + return R.ok(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @SaCheckPermission("system:dict:add") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysDictTypeBo dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return R.fail("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dictTypeService.insertDictType(dict); + return R.ok(); + } + + /** + * 修改字典类型 + */ + @SaCheckPermission("system:dict:edit") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysDictTypeBo dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return R.fail("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dictTypeService.updateDictType(dict); + return R.ok(); + } + + /** + * 删除字典类型 + * + * @param dictIds 字典ID串 + */ + @SaCheckPermission("system:dict:remove") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public R remove(@PathVariable Long[] dictIds) { + dictTypeService.deleteDictTypeByIds(dictIds); + return R.ok(); + } + + /** + * 刷新字典缓存 + */ + @SaCheckPermission("system:dict:remove") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public R refreshCache() { + dictTypeService.resetDictCache(); + return R.ok(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public R> optionselect() { + List dictTypes = dictTypeService.selectDictTypeAll(); + return R.ok(dictTypes); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysMenuController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysMenuController.java new file mode 100644 index 00000000..2019aa3d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysMenuController.java @@ -0,0 +1,182 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.lang.tree.Tree; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.SysMenu; +import org.ruoyi.system.domain.bo.SysMenuBo; +import org.ruoyi.system.domain.vo.MenuTreeSelectVo; +import org.ruoyi.system.domain.vo.RouterVo; +import org.ruoyi.system.domain.vo.SysMenuVo; +import org.ruoyi.system.service.ISysMenuService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 菜单信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController { + + private final ISysMenuService menuService; + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("/getRouters") + public R> getRouters() { + List menus = menuService.selectMenuTreeByUserId(LoginHelper.getUserId()); + return R.ok(menuService.buildMenus(menus)); + } + + /** + * 获取菜单列表 + */ + @SaCheckRole(value = { + TenantConstants.SUPER_ADMIN_ROLE_KEY, + TenantConstants.TENANT_ADMIN_ROLE_KEY + }, mode = SaMode.OR) + @SaCheckPermission("system:menu:list") + @GetMapping("/list") + public R> list(SysMenuBo menu) { + List menus = menuService.selectMenuList(menu, LoginHelper.getUserId()); + return R.ok(menus); + } + + /** + * 根据菜单编号获取详细信息 + * + * @param menuId 菜单ID + */ + @SaCheckRole(value = { + TenantConstants.SUPER_ADMIN_ROLE_KEY, + TenantConstants.TENANT_ADMIN_ROLE_KEY + }, mode = SaMode.OR) + @SaCheckPermission("system:menu:query") + @GetMapping(value = "/{menuId}") + public R getInfo(@PathVariable Long menuId) { + return R.ok(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @SaCheckRole(value = { + TenantConstants.SUPER_ADMIN_ROLE_KEY, + TenantConstants.TENANT_ADMIN_ROLE_KEY + }, mode = SaMode.OR) + @SaCheckPermission("system:menu:query") + @GetMapping("/treeselect") + public R>> treeselect(SysMenuBo menu) { + List menus = menuService.selectMenuList(menu, LoginHelper.getUserId()); + return R.ok(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + * + * @param roleId 角色ID + */ + @SaCheckRole(value = { + TenantConstants.SUPER_ADMIN_ROLE_KEY, + TenantConstants.TENANT_ADMIN_ROLE_KEY + }, mode = SaMode.OR) + @SaCheckPermission("system:menu:query") + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public R roleMenuTreeselect(@PathVariable("roleId") Long roleId) { + List menus = menuService.selectMenuList(LoginHelper.getUserId()); + MenuTreeSelectVo selectVo = new MenuTreeSelectVo(); + selectVo.setCheckedKeys(menuService.selectMenuListByRoleId(roleId)); + selectVo.setMenus(menuService.buildMenuTreeSelect(menus)); + return R.ok(selectVo); + } + + /** + * 加载对应租户套餐菜单列表树 + * + * @param packageId 租户套餐ID + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:menu:query") + @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}") + public R tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) { + List menus = menuService.selectMenuList(LoginHelper.getUserId()); + MenuTreeSelectVo selectVo = new MenuTreeSelectVo(); + selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId)); + selectVo.setMenus(menuService.buildMenuTreeSelect(menus)); + return R.ok(selectVo); + } + + /** + * 新增菜单 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:menu:add") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysMenuBo menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return R.fail("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:menu:edit") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysMenuBo menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return R.fail("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return R.fail("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } else if (menu.getMenuId().equals(menu.getParentId())) { + return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + * + * @param menuId 菜单ID + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:menu:remove") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public R remove(@PathVariable("menuId") Long menuId) { + if (menuService.hasChildByMenuId(menuId)) { + return R.warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) { + return R.warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysModelController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysModelController.java new file mode 100644 index 00000000..dfe0284f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysModelController.java @@ -0,0 +1,141 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.stp.StpUtil; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.service.ISysModelService; +import org.ruoyi.system.service.ISysPackagePlanService; +import org.ruoyi.system.service.ISysUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 系统模型 + * + * @author Lion Li + * @date 2024-04-04 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/model") +public class SysModelController extends BaseController { + + private final ISysModelService sysModelService; + + private final ISysPackagePlanService sysPackagePlanService; + + private final ISysUserService userService; + + + /** + * 查询系统模型列表 - 全部 + */ + @GetMapping("/list") + public TableDataInfo list(SysModelBo bo, PageQuery pageQuery) { + return sysModelService.queryPageList(bo, pageQuery); + } + + /** + * 查询系统模型列表 + */ + @GetMapping("/modelList") + public R> modelList(SysModelBo bo) { + bo.setModelShow("0"); + List sysModelVos = sysModelService.queryList(bo); + SysPackagePlanBo sysPackagePlanBo = new SysPackagePlanBo(); + if (StpUtil.isLogin()) { + Long userId = LoginHelper.getLoginUser().getUserId(); + SysUserVo sysUserVo = userService.selectUserById(userId); + if ("0".equals(sysUserVo.getUserGrade())){ + sysPackagePlanBo.setName("Free"); + SysPackagePlanVo sysPackagePlanVo = sysPackagePlanService.queryList(sysPackagePlanBo).get(0); + List array = new ArrayList<>(Arrays.asList(sysPackagePlanVo.getPlanDetail().split(","))); + sysModelVos.removeIf(model -> !array.contains(model.getModelName())); + } + }else { + sysPackagePlanBo.setName("Visitor"); + SysPackagePlanVo sysPackagePlanVo = sysPackagePlanService.queryList(sysPackagePlanBo).get(0); + List array = new ArrayList<>(Arrays.asList(sysPackagePlanVo.getPlanDetail().split(","))); + sysModelVos.removeIf(model -> !array.contains(model.getModelName())); + } + return R.ok(sysModelVos); + } + + /** + * 导出系统模型列表 + */ + @Log(title = "系统模型", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysModelBo bo, HttpServletResponse response) { + List list = sysModelService.queryList(bo); + ExcelUtil.exportExcel(list, "系统模型", SysModelVo.class, response); + } + + /** + * 获取系统模型详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:model:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysModelService.queryById(id)); + } + + /** + * 新增系统模型 + */ + @Log(title = "系统模型", businessType = BusinessType.INSERT) + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysModelBo bo) { + return toAjax(sysModelService.insertByBo(bo)); + } + + /** + * 修改系统模型 + */ + @Log(title = "系统模型", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysModelBo bo) { + return toAjax(sysModelService.updateByBo(bo)); + } + + /** + * 删除系统模型 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:model:remove") + @Log(title = "系统模型", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysModelService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeController.java new file mode 100644 index 00000000..22ebae7d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeController.java @@ -0,0 +1,90 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.SysNotice; +import org.ruoyi.system.domain.bo.SysNoticeBo; +import org.ruoyi.system.domain.vo.SysNoticeVo; +import org.ruoyi.system.service.ISysNoticeService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 公告 信息操作处理 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController { + + private final ISysNoticeService noticeService; + + /** + * 获取公告列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysNoticeBo notice, PageQuery pageQuery) { + //公告类型(1通知 2公告) + notice.setNoticeType("2"); + return noticeService.selectPageNoticeList(notice, pageQuery); + } + + /** + * 获取通知信息 + */ + @GetMapping("/getNotice") + public R getNotice(SysNoticeBo notice) { + return R.ok(noticeService.getNotice(notice)); + } + + /** + * 根据通知公告编号获取详细信息 + * + * @param noticeId 公告ID + */ + @SaCheckPermission("system:notice:query") + @GetMapping(value = "/{noticeId}") + public R getInfo(@PathVariable Long noticeId) { + return R.ok(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @SaCheckPermission("system:notice:add") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysNoticeBo notice) { + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysNoticeBo notice) { + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + * + * @param noticeIds 公告ID串 + */ + @SaCheckPermission("system:notice:remove") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public R remove(@PathVariable Long[] noticeIds) { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeStateController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeStateController.java new file mode 100644 index 00000000..bd881515 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysNoticeStateController.java @@ -0,0 +1,98 @@ +package org.ruoyi.system.controller.system; + +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysNoticeStateBo; +import org.ruoyi.system.domain.vo.SysNoticeStateVo; +import org.ruoyi.system.service.ISysNoticeStateService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户阅读状态 + * + * @author Lion Li + * @date 2024-05-11 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/noticeState") +public class SysNoticeStateController extends BaseController { + + private final ISysNoticeStateService sysNoticeStateService; + + /** + * 查询用户阅读状态列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysNoticeStateBo bo, PageQuery pageQuery) { + return sysNoticeStateService.queryPageList(bo, pageQuery); + } + + /** + * 导出用户阅读状态列表 + */ + @Log(title = "用户阅读状态", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysNoticeStateBo bo, HttpServletResponse response) { + List list = sysNoticeStateService.queryList(bo); + ExcelUtil.exportExcel(list, "用户阅读状态", SysNoticeStateVo.class, response); + } + + /** + * 获取用户阅读状态详细信息 + * + * @param id 主键 + */ + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysNoticeStateService.queryById(id)); + } + + /** + * 新增用户阅读状态 + */ + @Log(title = "用户阅读状态", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysNoticeStateBo bo) { + return toAjax(sysNoticeStateService.insertByBo(bo)); + } + + /** + * 修改用户阅读状态 + */ + @PutMapping() + public R edit(@RequestBody SysNoticeStateBo bo) { + bo.setUserId(LoginHelper.getUserId()); + return toAjax(sysNoticeStateService.updateByBo(bo)); + } + + /** + * 删除用户阅读状态 + * + * @param ids 主键串 + */ + @Log(title = "用户阅读状态", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysNoticeStateService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssConfigController.java new file mode 100644 index 00000000..8857bebc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssConfigController.java @@ -0,0 +1,105 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.core.validate.QueryGroup; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysOssConfigBo; +import org.ruoyi.system.domain.vo.SysOssConfigVo; +import org.ruoyi.system.service.ISysOssConfigService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 对象存储配置 + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/resource/oss/config") +public class SysOssConfigController extends BaseController { + + private final ISysOssConfigService ossConfigService; + + /** + * 查询对象存储配置列表 + */ + @SaCheckPermission("system:oss:list") + @GetMapping("/list") + public TableDataInfo list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) { + return ossConfigService.queryPageList(bo, pageQuery); + } + + /** + * 获取对象存储配置详细信息 + * + * @param ossConfigId OSS配置ID + */ + @SaCheckPermission("system:oss:query") + @GetMapping("/{ossConfigId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long ossConfigId) { + return R.ok(ossConfigService.queryById(ossConfigId)); + } + + /** + * 新增对象存储配置 + */ + @SaCheckPermission("system:oss:add") + @Log(title = "对象存储配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) { + return toAjax(ossConfigService.insertByBo(bo)); + } + + /** + * 修改对象存储配置 + */ + @SaCheckPermission("system:oss:edit") + @Log(title = "对象存储配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) { + return toAjax(ossConfigService.updateByBo(bo)); + } + + /** + * 删除对象存储配置 + * + * @param ossConfigIds OSS配置ID串 + */ + @SaCheckPermission("system:oss:remove") + @Log(title = "对象存储配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ossConfigIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ossConfigIds) { + return toAjax(ossConfigService.deleteWithValidByIds(List.of(ossConfigIds), true)); + } + + /** + * 状态修改 + */ + @SaCheckPermission("system:oss:edit") + @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public R changeStatus(@RequestBody SysOssConfigBo bo) { + return toAjax(ossConfigService.updateOssConfigStatus(bo)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssController.java new file mode 100644 index 00000000..bb3dd362 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysOssController.java @@ -0,0 +1,108 @@ +package org.ruoyi.system.controller.system; + + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.ObjectUtil; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.QueryGroup; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysOssBo; +import org.ruoyi.system.domain.vo.SysOssUploadVo; +import org.ruoyi.system.domain.vo.SysOssVo; +import org.ruoyi.system.service.ISysOssService; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 文件上传 控制层 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/resource/oss") +public class SysOssController extends BaseController { + + private final ISysOssService ossService; + + /** + * 查询OSS对象存储列表 + */ + @SaCheckPermission("system:oss:list") + @GetMapping("/list") + public TableDataInfo list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) { + return ossService.queryPageList(bo, pageQuery); + } + + /** + * 查询OSS对象基于id串 + * + * @param ossIds OSS对象ID串 + */ + @SaCheckPermission("system:oss:list") + @GetMapping("/listByIds/{ossIds}") + public R> listByIds(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ossIds) { + List list = ossService.listByIds(Arrays.asList(ossIds)); + return R.ok(list); + } + + /** + * 上传OSS对象存储 + * + * @param file 文件 + */ + @SaCheckPermission("system:oss:upload") + @Log(title = "OSS对象存储", businessType = BusinessType.INSERT) + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R upload(@RequestPart("file") MultipartFile file) { + if (ObjectUtil.isNull(file)) { + return R.fail("上传文件不能为空"); + } + SysOssVo oss = ossService.upload(file); + SysOssUploadVo uploadVo = new SysOssUploadVo(); + uploadVo.setUrl(oss.getUrl()); + uploadVo.setFileName(oss.getOriginalName()); + uploadVo.setOssId(oss.getOssId().toString()); + return R.ok(uploadVo); + } + + /** + * 下载OSS对象 + * + * @param ossId OSS对象ID + */ + @SaCheckPermission("system:oss:download") + @GetMapping("/download/{ossId}") + public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException { + ossService.download(ossId, response); + } + + /** + * 删除OSS对象存储 + * + * @param ossIds OSS对象ID串 + */ + @SaCheckPermission("system:oss:remove") + @Log(title = "OSS对象存储", businessType = BusinessType.DELETE) + @DeleteMapping("/{ossIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ossIds) { + return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true)); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPackagePlanController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPackagePlanController.java new file mode 100644 index 00000000..068ffd6d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPackagePlanController.java @@ -0,0 +1,114 @@ +package org.ruoyi.system.controller.system; + +import java.util.List; + +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; + +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.log.enums.BusinessType; + +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.system.service.ISysPackagePlanService; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; + +/** + * 套餐管理 + * + * @author Lion Li + * @date 2024-05-05 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/packagePlan") +public class SysPackagePlanController extends BaseController { + + private final ISysPackagePlanService sysPackagePlanService; + + /** + * 查询套餐管理列表 + */ + @GetMapping("/list") + public TableDataInfo list(SysPackagePlanBo bo, PageQuery pageQuery) { + return sysPackagePlanService.queryPageList(bo, pageQuery); + } + + @GetMapping("/listPlan") + public R> listPlan() { + return R.ok(sysPackagePlanService.queryList(new SysPackagePlanBo())); + } + + /** + * 导出套餐管理列表 + */ + @SaCheckPermission("system:packagePlan:export") + @Log(title = "套餐管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysPackagePlanBo bo, HttpServletResponse response) { + List list = sysPackagePlanService.queryList(bo); + ExcelUtil.exportExcel(list, "套餐管理", SysPackagePlanVo.class, response); + } + + /** + * 获取套餐管理详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:packagePlan:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysPackagePlanService.queryById(id)); + } + + + + + /** + * 新增套餐管理 + */ + @SaCheckPermission("system:packagePlan:add") + @Log(title = "套餐管理", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysPackagePlanBo bo) { + return toAjax(sysPackagePlanService.insertByBo(bo)); + } + + /** + * 修改套餐管理 + */ + @SaCheckPermission("system:packagePlan:edit") + @Log(title = "套餐管理", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysPackagePlanBo bo) { + return toAjax(sysPackagePlanService.updateByBo(bo)); + } + + /** + * 删除套餐管理 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:packagePlan:remove") + @Log(title = "套餐管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysPackagePlanService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPostController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPostController.java new file mode 100644 index 00000000..374f7e6e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysPostController.java @@ -0,0 +1,115 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysPostBo; +import org.ruoyi.system.domain.vo.SysPostVo; +import org.ruoyi.system.service.ISysPostService; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 岗位信息操作处理 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController { + + private final ISysPostService postService; + + /** + * 获取岗位列表 + */ + @SaCheckPermission("system:post:list") + @GetMapping("/list") + public TableDataInfo list(SysPostBo post, PageQuery pageQuery) { + return postService.selectPagePostList(post, pageQuery); + } + + /** + * 导出岗位列表 + */ + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:post:export") + @PostMapping("/export") + public void export(SysPostBo post, HttpServletResponse response) { + List list = postService.selectPostList(post); + ExcelUtil.exportExcel(list, "岗位数据", SysPostVo.class, response); + } + + /** + * 根据岗位编号获取详细信息 + * + * @param postId 岗位ID + */ + @SaCheckPermission("system:post:query") + @GetMapping(value = "/{postId}") + public R getInfo(@PathVariable Long postId) { + return R.ok(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @SaCheckPermission("system:post:add") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysPostBo post) { + if (!postService.checkPostNameUnique(post)) { + return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return R.fail("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @SaCheckPermission("system:post:edit") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysPostBo post) { + if (!postService.checkPostNameUnique(post)) { + return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + * + * @param postIds 岗位ID串 + */ + @SaCheckPermission("system:post:remove") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public R remove(@PathVariable Long[] postIds) { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public R> optionselect() { + List posts = postService.selectPostAll(); + return R.ok(posts); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java new file mode 100644 index 00000000..aee284d0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java @@ -0,0 +1,123 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.secure.BCrypt; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.io.FileUtil; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.file.MimeTypeUtils; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.bo.SysUserProfileBo; +import org.ruoyi.system.domain.vo.AvatarVo; +import org.ruoyi.system.domain.vo.ProfileVo; +import org.ruoyi.system.domain.vo.SysOssVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.service.ISysOssService; +import org.ruoyi.system.service.ISysUserService; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; + +/** + * 个人信息 业务处理 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController { + + private final ISysUserService userService; + private final ISysOssService ossService; + + /** + * 个人信息 + */ + @GetMapping + public R profile() { + SysUserVo user = userService.selectUserById(LoginHelper.getUserId()); + ProfileVo profileVo = new ProfileVo(); + profileVo.setUser(user); + profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserName())); + profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserName())); + return R.ok(profileVo); + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public R updateProfile(@RequestBody SysUserProfileBo profile) { + SysUserBo user = BeanUtil.toBean(profile, SysUserBo.class); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(LoginHelper.getUserId()); + if (userService.updateUserProfile(user) > 0) { + return R.ok(); + } + return R.fail("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + * + * @param newPassword 旧密码 + * @param oldPassword 新密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public R updatePwd(String oldPassword, String newPassword) { + SysUserVo user = userService.selectUserById(LoginHelper.getUserId()); + String password = user.getPassword(); + if (!BCrypt.checkpw(oldPassword, password)) { + return R.fail("修改密码失败,旧密码错误"); + } + if (BCrypt.checkpw(newPassword, password)) { + return R.fail("新密码不能与旧密码相同"); + } + + if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(newPassword)) > 0) { + return R.ok(); + } + return R.fail("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + * + * @param avatarfile 用户头像 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R avatar(@RequestPart("avatarfile") MultipartFile avatarfile) { + if (!avatarfile.isEmpty()) { + String extension = FileUtil.extName(avatarfile.getOriginalFilename()); + if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) { + return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式"); + } + SysOssVo oss = ossService.upload(avatarfile); + String avatar = oss.getUrl(); + if (userService.updateUserAvatar(LoginHelper.getUserId(), oss.getUrl())) { + AvatarVo avatarVo = new AvatarVo(); + avatarVo.setImgUrl(avatar); + return R.ok(avatarVo); + } + } + return R.fail("上传图片异常,请联系管理员"); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysRoleController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysRoleController.java new file mode 100644 index 00000000..90785d73 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysRoleController.java @@ -0,0 +1,226 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.SysUserRole; +import org.ruoyi.system.domain.bo.SysDeptBo; +import org.ruoyi.system.domain.bo.SysRoleBo; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.DeptTreeSelectVo; +import org.ruoyi.system.domain.vo.SysRoleVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.service.ISysDeptService; +import org.ruoyi.system.service.ISysRoleService; +import org.ruoyi.system.service.ISysUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 角色信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController { + + private final ISysRoleService roleService; + private final ISysUserService userService; + private final ISysDeptService deptService; + + /** + * 获取角色信息列表 + */ + @SaCheckPermission("system:role:list") + @GetMapping("/list") + public TableDataInfo list(SysRoleBo role, PageQuery pageQuery) { + return roleService.selectPageRoleList(role, pageQuery); + } + + /** + * 导出角色信息列表 + */ + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:role:export") + @PostMapping("/export") + public void export(SysRoleBo role, HttpServletResponse response) { + List list = roleService.selectRoleList(role); + ExcelUtil.exportExcel(list, "角色数据", SysRoleVo.class, response); + } + + /** + * 根据角色编号获取详细信息 + * + * @param roleId 角色ID + */ + @SaCheckPermission("system:role:query") + @GetMapping(value = "/{roleId}") + public R getInfo(@PathVariable Long roleId) { + roleService.checkRoleDataScope(roleId); + return R.ok(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @SaCheckPermission("system:role:add") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysRoleBo role) { + if (!roleService.checkRoleNameUnique(role)) { + return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return R.fail("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysRoleBo role) { + roleService.checkRoleAllowed(role.getRoleId()); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) { + return R.fail("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return R.fail("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + + if (roleService.updateRole(role) > 0) { + roleService.cleanOnlineUserByRole(role.getRoleId()); + return R.ok(); + } + return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public R dataScope(@RequestBody SysRoleBo role) { + roleService.checkRoleAllowed(role.getRoleId()); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public R changeStatus(@RequestBody SysRoleBo role) { + roleService.checkRoleAllowed(role.getRoleId()); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.updateRoleStatus(role.getRoleId(), role.getStatus())); + } + + /** + * 删除角色 + * + * @param roleIds 角色ID串 + */ + @SaCheckPermission("system:role:remove") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public R remove(@PathVariable Long[] roleIds) { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @SaCheckPermission("system:role:query") + @GetMapping("/optionselect") + public R> optionselect() { + return R.ok(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @SaCheckPermission("system:role:list") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUserBo user, PageQuery pageQuery) { + return userService.selectAllocatedList(user, pageQuery); + } + + /** + * 查询未分配用户角色列表 + */ + @SaCheckPermission("system:role:list") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUserBo user, PageQuery pageQuery) { + return userService.selectUnallocatedList(user, pageQuery); + } + + /** + * 取消授权用户 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public R cancelAuthUser(@RequestBody SysUserRole userRole) { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + * + * @param roleId 角色ID + * @param userIds 用户ID串 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public R cancelAuthUserAll(Long roleId, Long[] userIds) { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + * + * @param roleId 角色ID + * @param userIds 用户ID串 + */ + @SaCheckPermission("system:role:edit") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public R selectAuthUserAll(Long roleId, Long[] userIds) { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + * + * @param roleId 角色ID + */ + @SaCheckPermission("system:role:list") + @GetMapping(value = "/deptTree/{roleId}") + public R roleDeptTreeselect(@PathVariable("roleId") Long roleId) { + DeptTreeSelectVo selectVo = new DeptTreeSelectVo(); + selectVo.setCheckedKeys(deptService.selectDeptListByRoleId(roleId)); + selectVo.setDepts(deptService.selectDeptTreeList(new SysDeptBo())); + return R.ok(selectVo); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantController.java new file mode 100644 index 00000000..f05f55ff --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantController.java @@ -0,0 +1,174 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import com.baomidou.lock.annotation.Lock4j; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysTenantBo; +import org.ruoyi.system.domain.vo.SysTenantVo; +import org.ruoyi.system.service.ISysTenantService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 租户管理 + * + * @author Michelle.Chung + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/tenant") +public class SysTenantController extends BaseController { + + private final ISysTenantService tenantService; + + /** + * 查询租户列表 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:list") + @GetMapping("/list") + public TableDataInfo list(SysTenantBo bo, PageQuery pageQuery) { + return tenantService.queryPageList(bo, pageQuery); + } + + /** + * 导出租户列表 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:export") + @Log(title = "租户", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysTenantBo bo, HttpServletResponse response) { + List list = tenantService.queryList(bo); + ExcelUtil.exportExcel(list, "租户", SysTenantVo.class, response); + } + + /** + * 获取租户详细信息 + * + * @param id 主键 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(tenantService.queryById(id)); + } + + /** + * 新增租户 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:add") + @Log(title = "租户", businessType = BusinessType.INSERT) + @Lock4j + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) { + if (!tenantService.checkCompanyNameUnique(bo)) { + return R.fail("新增租户'" + bo.getCompanyName() + "'失败,企业名称已存在"); + } + return toAjax(TenantHelper.ignore(() -> tenantService.insertByBo(bo))); + } + + /** + * 修改租户 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:edit") + @Log(title = "租户", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) { + tenantService.checkTenantAllowed(bo.getTenantId()); + if (!tenantService.checkCompanyNameUnique(bo)) { + return R.fail("修改租户'" + bo.getCompanyName() + "'失败,公司名称已存在"); + } + return toAjax(tenantService.updateByBo(bo)); + } + + /** + * 状态修改 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:edit") + @Log(title = "租户", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public R changeStatus(@RequestBody SysTenantBo bo) { + tenantService.checkTenantAllowed(bo.getTenantId()); + return toAjax(tenantService.updateTenantStatus(bo)); + } + + /** + * 删除租户 + * + * @param ids 主键串 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:remove") + @Log(title = "租户", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(tenantService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 动态切换租户 + * + * @param tenantId 租户ID + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @GetMapping("/dynamic/{tenantId}") + public R dynamicTenant(@NotBlank(message = "租户ID不能为空") @PathVariable String tenantId) { + TenantHelper.setDynamic(tenantId); + return R.ok(); + } + + /** + * 清除动态租户 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @GetMapping("/dynamic/clear") + public R dynamicClear() { + TenantHelper.clearDynamic(); + return R.ok(); + } + + + /** + * 同步租户套餐 + * + * @param tenantId 租户id + * @param packageId 套餐id + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenant:edit") + @Log(title = "租户", businessType = BusinessType.UPDATE) + @GetMapping("/syncTenantPackage") + public R syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId, @NotBlank(message = "套餐ID不能为空") String packageId) { + return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId))); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantPackageController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantPackageController.java new file mode 100644 index 00000000..cf021e44 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysTenantPackageController.java @@ -0,0 +1,134 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysTenantPackageBo; +import org.ruoyi.system.domain.vo.SysTenantPackageVo; +import org.ruoyi.system.service.ISysTenantPackageService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 租户套餐管理 + * + * @author Michelle.Chung + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/tenant/package") +public class SysTenantPackageController extends BaseController { + + private final ISysTenantPackageService tenantPackageService; + + /** + * 查询租户套餐列表 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:list") + @GetMapping("/list") + public TableDataInfo list(SysTenantPackageBo bo, PageQuery pageQuery) { + return tenantPackageService.queryPageList(bo, pageQuery); + } + + /** + * 查询租户套餐下拉选列表 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:list") + @GetMapping("/selectList") + public R> selectList() { + return R.ok(tenantPackageService.selectList()); + } + + /** + * 导出租户套餐列表 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:export") + @Log(title = "租户套餐", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysTenantPackageBo bo, HttpServletResponse response) { + List list = tenantPackageService.queryList(bo); + ExcelUtil.exportExcel(list, "租户套餐", SysTenantPackageVo.class, response); + } + + /** + * 获取租户套餐详细信息 + * + * @param packageId 主键 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:query") + @GetMapping("/{packageId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long packageId) { + return R.ok(tenantPackageService.queryById(packageId)); + } + + /** + * 新增租户套餐 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:add") + @Log(title = "租户套餐", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) { + return toAjax(tenantPackageService.insertByBo(bo)); + } + + /** + * 修改租户套餐 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:edit") + @Log(title = "租户套餐", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) { + return toAjax(tenantPackageService.updateByBo(bo)); + } + + /** + * 状态修改 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:edit") + @Log(title = "租户套餐", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public R changeStatus(@RequestBody SysTenantPackageBo bo) { + return toAjax(tenantPackageService.updatePackageStatus(bo)); + } + + /** + * 删除租户套餐 + * + * @param packageIds 主键串 + */ + @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) + @SaCheckPermission("system:tenantPackage:remove") + @Log(title = "租户套餐", businessType = BusinessType.DELETE) + @DeleteMapping("/{packageIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] packageIds) { + return toAjax(tenantPackageService.deleteWithValidByIds(List.of(packageIds), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java new file mode 100644 index 00000000..b5b7547d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserController.java @@ -0,0 +1,324 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.secure.BCrypt; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.excel.core.ExcelResult; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysDeptBo; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.request.UserRequest; +import org.ruoyi.system.domain.vo.*; +import org.ruoyi.system.listener.SysUserImportListener; +import org.ruoyi.system.service.*; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户信息 + * + * @author Lion Li + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController { + + private final ISysUserService userService; + private final ISysRoleService roleService; + private final ISysPostService postService; + private final ISysDeptService deptService; + private final ISysTenantService tenantService; + private final ISysOssService ossService; + /** + * 获取用户列表 + */ + @SaCheckPermission("system:user:list") + @GetMapping("/list") + public TableDataInfo list(SysUserBo user, PageQuery pageQuery) { + return userService.selectPageUserList(user, pageQuery); + } + + /** + * 获取用户列表 + */ + @GetMapping("/getUserOption") + public R> getUserOption() { + List sysUserVos = userService.selectUserList(new SysUserBo()); + List collect = sysUserVos.stream() + .map(this::convertToUserOptionVo) + .collect(Collectors.toList()); + return R.ok(collect); + } + + private SysUserOptionVo convertToUserOptionVo(SysUserVo sysUserVo) { + SysUserOptionVo sysUserOptionVo = new SysUserOptionVo(); + sysUserOptionVo.setUserId(sysUserVo.getUserId()); + sysUserOptionVo.setName(sysUserVo.getNickName()); + return sysUserOptionVo; + } + + /** + * 导出用户列表 + */ + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @SaCheckPermission("system:user:export") + @PostMapping("/export") + public void export(SysUserBo user, HttpServletResponse response) { + List list = userService.selectUserList(user); + List listVo = MapstructUtils.convert(list, SysUserExportVo.class); + ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response); + } + + /** + * 导入数据 + * + * @param file 导入文件 + * @param updateSupport 是否更新已存在数据 + */ + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @SaCheckPermission("system:user:import") + @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception { + ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport)); + return R.ok(result.getAnalysis()); + } + + /** + * 获取导入模板 + */ + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", SysUserImportVo.class, response); + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("/getInfo") + public R getInfo() { + UserInfoVo userInfoVo = new UserInfoVo(); + LoginUser loginUser = LoginHelper.getLoginUser(); + if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) { + // 超级管理员 如果重新加载用户信息需清除动态租户 + TenantHelper.clearDynamic(); + } + SysUserVo user = userService.selectUserById(loginUser.getUserId()); + userInfoVo.setUser(user); + userInfoVo.setPermissions(loginUser.getMenuPermission()); + userInfoVo.setRoles(loginUser.getRolePermission()); + return R.ok(userInfoVo); + } + + /** + * 根据用户编号获取详细信息 + * + * @param userId 用户ID + */ + @SaCheckPermission("system:user:query") + @GetMapping(value = {"/", "/{userId}"}) + public R getInfo(@PathVariable(value = "userId", required = false) Long userId) { + userService.checkUserDataScope(userId); + SysUserInfoVo userInfoVo = new SysUserInfoVo(); + List roles = roleService.selectRoleAll(); + userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin())); + userInfoVo.setPosts(postService.selectPostAll()); + if (ObjectUtil.isNotNull(userId)) { + SysUserVo sysUser = userService.selectUserById(userId); + userInfoVo.setUser(sysUser); + userInfoVo.setRoleIds(StreamUtils.toList(sysUser.getRoles(), SysRoleVo::getRoleId)); + userInfoVo.setPostIds(postService.selectPostListByUserId(userId)); + } + return R.ok(userInfoVo); + } + + /** + * 新增用户 + */ + @SaCheckPermission("system:user:add") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public R add(@Validated @RequestBody SysUserBo user) { + if (!userService.checkUserNameUnique(user)) { + return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + if (TenantHelper.isEnable()) { + if (!tenantService.checkAccountBalance(TenantHelper.getTenantId())) { + return R.fail("当前租户下用户名额不足,请联系管理员"); + } + } + if(StringUtils.isEmpty(user.getPassword())){ + user.setPassword("123456"); + } + if(StringUtils.isEmpty(user.getNickName())){ + user.setNickName(user.getUserName()); + } + user.setDeptId(103L); + user.setPassword(BCrypt.hashpw(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @SaCheckPermission("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public R edit(@Validated @RequestBody SysUserBo user) { + userService.checkUserAllowed(user.getUserId()); + userService.checkUserDataScope(user.getUserId()); + if (!userService.checkUserNameUnique(user)) { + return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + return toAjax(userService.updateUser(user)); + } + + /** + * 修改用户名称 + */ + @Log(title = "修改用户名称", businessType = BusinessType.UPDATE) + @PostMapping("/editName") + public R editName(@RequestBody @Validated UserRequest userRequest) { + LoginUser loginUser = LoginHelper.getLoginUser(); + userService.updateUserName(loginUser.getUserId(), userRequest.getNickName()); + return R.ok("操作成功!"); + } + + /** + * 修改用户头像 + */ + @Log(title = "修改用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/edit/avatar") + public R editAvatar(@RequestPart("file") MultipartFile file) { + if (ObjectUtil.isNull(file)) { + return R.fail("上传文件不能为空"); + } + LoginUser loginUser = LoginHelper.getLoginUser(); + // 获取当前登录用户 + SysOssVo oss = ossService.upload(file); + userService.updateUserAvatar(loginUser.getUserId(), oss.getUrl()); + return R.ok(oss.getUrl()); + } + + /** + * 小程序-修改用户 + */ + @PostMapping("/edit/xcxUser") + public R editXcxUser(@RequestBody SysUserBo user) { + return R.ok(userService.updateXcxUser(user)); + } + + /** + * 删除用户 + * + * @param userIds 角色ID串 + */ + @SaCheckPermission("system:user:remove") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public R remove(@PathVariable Long[] userIds) { + if (ArrayUtil.contains(userIds, LoginHelper.getUserId())) { + return R.fail("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @SaCheckPermission("system:user:resetPwd") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public R resetPwd(@RequestBody SysUserBo user) { + userService.checkUserAllowed(user.getUserId()); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(BCrypt.hashpw(user.getPassword())); + return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword())); + } + + /** + * 状态修改 + */ + @SaCheckPermission("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public R changeStatus(@RequestBody SysUserBo user) { + userService.checkUserAllowed(user.getUserId()); + userService.checkUserDataScope(user.getUserId()); + return toAjax(userService.updateUserStatus(user.getUserId(), user.getStatus())); + } + + /** + * 根据用户编号获取授权角色 + * + * @param userId 用户ID + */ + @SaCheckPermission("system:user:query") + @GetMapping("/authRole/{userId}") + public R authRole(@PathVariable Long userId) { + SysUserVo user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + SysUserInfoVo userInfoVo = new SysUserInfoVo(); + userInfoVo.setUser(user); + userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin())); + return R.ok(userInfoVo); + } + + /** + * 用户授权角色 + * + * @param userId 用户Id + * @param roleIds 角色ID串 + */ + @SaCheckPermission("system:user:edit") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public R insertAuthRole(Long userId, Long[] roleIds) { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return R.ok(); + } + + /** + * 获取部门树列表 + */ + @SaCheckPermission("system:user:list") + @GetMapping("/deptTree") + public R>> deptTree(SysDeptBo dept) { + return R.ok(deptService.selectDeptTreeList(dept)); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserGroupController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserGroupController.java new file mode 100644 index 00000000..abff4335 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserGroupController.java @@ -0,0 +1,106 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.SysUserGroupBo; +import org.ruoyi.system.domain.vo.SysUserGroupVo; +import org.ruoyi.system.service.ISysUserGroupService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 【请填写功能名称】 + * + * @author Lion Li + * @date 2024-08-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/userGroup") +public class SysUserGroupController extends BaseController { + + private final ISysUserGroupService sysUserGroupService; + + /** + * 查询【请填写功能名称】列表 + */ + @SaCheckPermission("system:userGroup:list") + @GetMapping("/list") + public TableDataInfo list(SysUserGroupBo bo, PageQuery pageQuery) { + return sysUserGroupService.queryPageList(bo, pageQuery); + } + + /** + * 导出【请填写功能名称】列表 + */ + @SaCheckPermission("system:userGroup:export") + @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysUserGroupBo bo, HttpServletResponse response) { + List list = sysUserGroupService.queryList(bo); + ExcelUtil.exportExcel(list, "【请填写功能名称】", SysUserGroupVo.class, response); + } + + /** + * 获取【请填写功能名称】详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:userGroup:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysUserGroupService.queryById(id)); + } + + /** + * 新增【请填写功能名称】 + */ + @SaCheckPermission("system:userGroup:add") + @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysUserGroupBo bo) { + return toAjax(sysUserGroupService.insertByBo(bo)); + } + + /** + * 修改【请填写功能名称】 + */ + @SaCheckPermission("system:userGroup:edit") + @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysUserGroupBo bo) { + return toAjax(sysUserGroupService.updateByBo(bo)); + } + + /** + * 删除【请填写功能名称】 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:userGroup:remove") + @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysUserGroupService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserModelController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserModelController.java new file mode 100644 index 00000000..3e97801c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysUserModelController.java @@ -0,0 +1,105 @@ +package org.ruoyi.system.controller.system; + +import java.util.List; + +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.system.domain.vo.SysUserModelVo; +import org.ruoyi.system.domain.bo.SysUserModelBo; +import org.ruoyi.system.service.ISysUserModelService; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; + +/** + * 【请填写功能名称】 + * + * @author Lion Li + * @date 2024-08-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/userModel") +public class SysUserModelController extends BaseController { + + private final ISysUserModelService sysUserModelService; + + /** + * 查询【请填写功能名称】列表 + */ + @SaCheckPermission("system:userModel:list") + @GetMapping("/list") + public TableDataInfo list(SysUserModelBo bo, PageQuery pageQuery) { + return sysUserModelService.queryPageList(bo, pageQuery); + } + + /** + * 导出【请填写功能名称】列表 + */ + @SaCheckPermission("system:userModel:export") + @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SysUserModelBo bo, HttpServletResponse response) { + List list = sysUserModelService.queryList(bo); + ExcelUtil.exportExcel(list, "【请填写功能名称】", SysUserModelVo.class, response); + } + + /** + * 获取【请填写功能名称】详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:userModel:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(sysUserModelService.queryById(id)); + } + + /** + * 新增【请填写功能名称】 + */ + @SaCheckPermission("system:userModel:add") + @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SysUserModelBo bo) { + return toAjax(sysUserModelService.insertByBo(bo)); + } + + /** + * 修改【请填写功能名称】 + */ + @SaCheckPermission("system:userModel:edit") + @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SysUserModelBo bo) { + return toAjax(sysUserModelService.updateByBo(bo)); + } + + /** + * 删除【请填写功能名称】 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:userModel:remove") + @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(sysUserModelService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinServerController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinServerController.java new file mode 100644 index 00000000..f0ac84f2 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinServerController.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.controller.system; + + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ruoyi.system.service.WeixinUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * @author https://www.wdbyte.com + */ +@Slf4j +@RestController +public class WeixinServerController { + + @Autowired + private WeixinUserService weixinUserService; + + @GetMapping(value = "/weixin/check") + public String weixinCheck(HttpServletRequest request) { + String signature = request.getParameter("signature"); + String timestamp = request.getParameter("timestamp"); + String nonce = request.getParameter("nonce"); + String echostr = request.getParameter("echostr"); + + if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) { + return ""; + } + weixinUserService.checkSignature(signature, timestamp, nonce); + return echostr; + } + + @PostMapping(value = "/weixin/check") + public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { + + log.debug("requestBody:{}", requestBody); + log.debug("signature:{}", signature); + log.debug("timestamp:{}", timestamp); + log.debug("nonce:{}", nonce); + + weixinUserService.checkSignature(signature, timestamp, nonce); + return weixinUserService.handleWeixinMsg(requestBody); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinUserController.java new file mode 100644 index 00000000..d006ba56 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WeixinUserController.java @@ -0,0 +1,55 @@ +package org.ruoyi.system.controller.system; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.system.domain.model.WeixinQrCode; +import org.ruoyi.system.domain.vo.LoginVo; +import org.ruoyi.system.service.SysLoginService; +import org.ruoyi.system.util.WeixinApiUtil; +import org.ruoyi.system.util.WeixinQrCodeCacheUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author https://www.wdbyte.com + */ +@Slf4j +@RestController +public class WeixinUserController { + + @Autowired + private WeixinApiUtil weixinApiUtil; + + @Autowired + private SysLoginService loginService; + + @GetMapping(value = "/user/qrcode") + public R getQrCode() { + WeixinQrCode qrCode = weixinApiUtil.getQrCode(); + qrCode.setUrl(null); + qrCode.setExpireSeconds(null); + return R.ok(qrCode); + } + + /** + * 校验是否扫描完成 + * 完成,返回 JWT + * 未完成,返回 check faild + * + * @param ticket + * @return + */ + @GetMapping(value = "/user/login/qrcode") + public R userLogin(String ticket) { + String openId = WeixinQrCodeCacheUtil.get(ticket); + if (StringUtils.isNotEmpty(openId)) { + log.info("login success,open id:{}", openId); + LoginVo loginVo = loginService.mpLogin(openId); + return R.ok(loginVo); + } + log.info("login error,ticket:{}", ticket); + return R.fail("check faild"); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WxRobKeywordController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WxRobKeywordController.java new file mode 100644 index 00000000..755c2dce --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/WxRobKeywordController.java @@ -0,0 +1,106 @@ +package org.ruoyi.system.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.R; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.excel.utils.ExcelUtil; +import org.ruoyi.common.idempotent.annotation.RepeatSubmit; +import org.ruoyi.common.log.annotation.Log; +import org.ruoyi.common.log.enums.BusinessType; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.web.core.BaseController; +import org.ruoyi.system.domain.bo.WxRobKeywordBo; +import org.ruoyi.system.domain.vo.WxRobKeywordVo; +import org.ruoyi.system.service.IWxRobKeywordService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 【请填写功能名称】 + * + * @author Lion Li + * @date 2024-05-01 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/robKeyword") +public class WxRobKeywordController extends BaseController { + + private final IWxRobKeywordService wxRobKeywordService; + + /** + * 查询【请填写功能名称】列表 + */ + @SaCheckPermission("system:robKeyword:list") + @GetMapping("/list") + public TableDataInfo list(WxRobKeywordBo bo, PageQuery pageQuery) { + return wxRobKeywordService.queryPageList(bo, pageQuery); + } + + /** + * 导出【请填写功能名称】列表 + */ + @SaCheckPermission("system:robKeyword:export") + @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(WxRobKeywordBo bo, HttpServletResponse response) { + List list = wxRobKeywordService.queryList(bo); + ExcelUtil.exportExcel(list, "【请填写功能名称】", WxRobKeywordVo.class, response); + } + + /** + * 获取【请填写功能名称】详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:robKeyword:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(wxRobKeywordService.queryById(id)); + } + + /** + * 新增【请填写功能名称】 + */ + @SaCheckPermission("system:robKeyword:add") + @Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody WxRobKeywordBo bo) { + return toAjax(wxRobKeywordService.insertByBo(bo)); + } + + /** + * 修改【请填写功能名称】 + */ + @SaCheckPermission("system:robKeyword:edit") + @Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody WxRobKeywordBo bo) { + return toAjax(wxRobKeywordService.updateByBo(bo)); + } + + /** + * 删除【请填写功能名称】 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:robKeyword:remove") + @Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(wxRobKeywordService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatConfig.java new file mode 100644 index 00000000..47174617 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatConfig.java @@ -0,0 +1,70 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 对话配置信息 +对象 chat_config + * + * @author Lion Li + * @date 2024-04-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_config") +public class ChatConfig extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 配置类型 + */ + private String category; + + /** + * 配置名称 + */ + private String configName; + + /** + * 配置值 + */ + private String configValue; + + /** + * 说明 + */ + private String configDict; + + /** + * 备注 + */ + private String remark; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 更新IP + */ + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatGpts.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatGpts.java new file mode 100644 index 00000000..79f27727 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatGpts.java @@ -0,0 +1,101 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.Version; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * gpts管理对象 chat_gpts + * + * @author Lion Li + * @date 2024-07-09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_gpts") +public class ChatGpts extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id") + private Long id; + + /** + * gpts应用id + */ + private String gid; + + /** + * gpts应用名称 + */ + private String name; + + /** + * gpts图标 + */ + private String logo; + + /** + * gpts描述 + */ + private String info; + + /** + * 作者id + */ + private String authorId; + + /** + * 作者名称 + */ + private String authorName; + + /** + * 点赞 + */ + private String useCnt; + + /** + * 差评 + */ + private String bad; + + /** + * 类型 + */ + private String type; + + /** + * 备注 + */ + private String remark; + + /** + * 版本 + */ + @Version + private Long version; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 更新IP + */ + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatMessage.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatMessage.java new file mode 100644 index 00000000..eb42845d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatMessage.java @@ -0,0 +1,70 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableName; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 聊天消息对象 chat_message + * + * @author Lion Li + * @date 2023-11-26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_message") +public class ChatMessage extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID", groups = { AddGroup.class, EditGroup.class }) + private Long UserId; + + /** + * 消息内容 + */ + @NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class }) + private String content; + + + /** + * 扣除费用 + */ + private Double deductCost; + + /** + * 累计 Tokens + */ + @NotNull(message = "累计 Tokens不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer totalTokens; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatToken.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatToken.java new file mode 100644 index 00000000..865762f7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatToken.java @@ -0,0 +1,49 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户token使用记录 + * + * @author Lion Li + * @date 2023-11-26 + */ +@Data +@TableName("chat_usage_token") +public class ChatToken implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID", groups = { AddGroup.class, EditGroup.class }) + private Long UserId; + + /** + * 待结算token + */ + private Integer token; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVisitorUsage.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVisitorUsage.java new file mode 100644 index 00000000..3b203811 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVisitorUsage.java @@ -0,0 +1,73 @@ +package org.ruoyi.system.domain; + + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.Version; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 访客管理对象 chat_visitor_usage + * + * @author Lion Li + * @date 2024-07-14 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_visitor_usage") +public class ChatVisitorUsage extends BaseEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id") + private Long id; + + /** + * 浏览器指纹 + */ + private String fingerprint; + + /** + * 使用次数 + */ + private String usageCount; + + /** + * ip地址 + */ + private String ipAddress; + + /** + * 备注 + */ + private String remark; + + /** + * 版本 + */ + @Version + private Long version; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 更新IP + */ + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVoucher.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVoucher.java new file mode 100644 index 00000000..ec4b6313 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatVoucher.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.math.BigDecimal; + +import java.io.Serial; + +/** + * 用户兑换记录对象 chat_voucher + * + * @author Lion Li + * @date 2024-05-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_voucher") +public class ChatVoucher extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 兑换码 + */ + private String code; + + /** + * 兑换金额 + */ + private BigDecimal amount; + + /** + * 兑换状态 + */ + private String status; + + /** + * 兑换前余额 + */ + private BigDecimal balanceBefore; + + /** + * 兑换后余额 + */ + private BigDecimal balanceAfter; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLuma.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLuma.java new file mode 100644 index 00000000..3897e86d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLuma.java @@ -0,0 +1,22 @@ +package org.ruoyi.system.domain; + +import lombok.Data; + +/** + * 描述:文生视频请求对象 + * + * @author ageerle@163.com + * date 2024/6/27 + */ +@Data +public class GenerateLuma { + + private String aspect_ratio; + + private boolean expand_prompt; + + private String image_url; + + private String user_prompt; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLyric.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLyric.java new file mode 100644 index 00000000..9a3f67d6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateLyric.java @@ -0,0 +1,23 @@ +package org.ruoyi.system.domain; + +import lombok.Data; + +/** + * 描述:生成歌词 + * + * @author ageerle@163.com + * date 2024/6/27 + */ +@Data +public class GenerateLyric { + + /** + * 歌词提示词 + */ + private String prompt; + + /** + * 回调地址 + */ + private String notify_hook; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateSuno.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateSuno.java new file mode 100644 index 00000000..83940106 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/GenerateSuno.java @@ -0,0 +1,64 @@ +package org.ruoyi.system.domain; + + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author WangLe + */ +@Data +public class GenerateSuno implements Serializable { + + /** + * 歌词 (自定义模式专用) + */ + private String prompt; + + /** + * mv模型,chirp-v3-0、chirp-v3-5。不写默认 chirp-v3-0 + */ + private String mv; + + /** + * 标题(自定义模式专用) + */ + private String title; + + /** + * 风格标签(自定义模式专用) + */ + private String tags; + + /** + * 是否生成纯音乐,true 为生成纯音乐 + */ + private boolean make_instrumental; + + /** + * 任务id,用于对之前的任务再操作 + */ + private String task_id; + + /** + * float,歌曲延长时间,单位秒 + */ + private int continue_at; + + /** + * 歌曲id,需要续写哪首歌 + */ + private String continue_clip_id; + + /** + * 灵感模式提示词(灵感模式专用) + */ + private String gpt_description_prompt; + + /** + * 回调地址 + */ + private String notify_hook; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/PaymentOrder.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/PaymentOrder.java new file mode 100644 index 00000000..6f50b36e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/PaymentOrder.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.math.BigDecimal; + +import java.io.Serial; + +/** + * 支付订单对象 payment_orders + * + * @author Lion Li + * @date 2024-04-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_pay_order") +public class PaymentOrder extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 订单编号 + */ + private String orderNo; + + /** + * 订单名称 + */ + private String orderName; + + /** + * 金额 + */ + private BigDecimal amount; + + /** + * 支付状态 + */ + private String paymentStatus; + + /** + * 支付方式 + */ + private String paymentMethod; + + /** + * 用户ID + */ + private Long userId; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysCache.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysCache.java new file mode 100644 index 00000000..ed16f150 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysCache.java @@ -0,0 +1,47 @@ +package org.ruoyi.system.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * 缓存信息 + * + * @author Lion Li + */ +@Data +@NoArgsConstructor +public class SysCache { + + /** + * 缓存名称 + */ + private String cacheName = ""; + + /** + * 缓存键名 + */ + private String cacheKey = ""; + + /** + * 缓存内容 + */ + private String cacheValue = ""; + + /** + * 备注 + */ + private String remark = ""; + + public SysCache(String cacheName, String remark) { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysConfig.java new file mode 100644 index 00000000..ddd85749 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysConfig.java @@ -0,0 +1,51 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 参数配置表 sys_config + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_config") +public class SysConfig extends TenantEntity { + + /** + * 参数主键 + */ + @TableId(value = "config_id") + private Long configId; + + /** + * 参数名称 + */ + private String configName; + + /** + * 参数键名 + */ + private String configKey; + + /** + * 参数键值 + */ + private String configValue; + + /** + * 系统内置(Y是 N否) + */ + private String configType; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDept.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDept.java new file mode 100644 index 00000000..ea00915d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDept.java @@ -0,0 +1,78 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +import java.io.Serial; + +/** + * 部门表 sys_dept + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dept") +public class SysDept extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门ID + */ + @TableId(value = "dept_id") + private Long deptId; + + /** + * 父部门ID + */ + private Long parentId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 显示顺序 + */ + private Integer orderNum; + + /** + * 负责人 + */ + private String leader; + + /** + * 联系电话 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 部门状态:0正常,1停用 + */ + private String status; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 祖级列表 + */ + private String ancestors; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictData.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictData.java new file mode 100644 index 00000000..ab6c3f8e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictData.java @@ -0,0 +1,76 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dict_data") +public class SysDictData extends TenantEntity { + + /** + * 字典编码 + */ + @TableId(value = "dict_code") + private Long dictCode; + + /** + * 字典排序 + */ + private Integer dictSort; + + /** + * 字典标签 + */ + private String dictLabel; + + /** + * 字典键值 + */ + private String dictValue; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 样式属性(其他样式扩展) + */ + private String cssClass; + + /** + * 表格字典样式 + */ + private String listClass; + + /** + * 是否默认(Y是 N否) + */ + private String isDefault; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + public boolean getDefault() { + return UserConstants.YES.equals(this.isDefault); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictType.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictType.java new file mode 100644 index 00000000..2621a617 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysDictType.java @@ -0,0 +1,46 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dict_type") +public class SysDictType extends TenantEntity { + + /** + * 字典主键 + */ + @TableId(value = "dict_id") + private Long dictId; + + /** + * 字典名称 + */ + private String dictName; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysLogininfor.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysLogininfor.java new file mode 100644 index 00000000..fceede4b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysLogininfor.java @@ -0,0 +1,75 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 系统访问记录表 sys_logininfor + * + * @author Lion Li + */ + +@Data +@TableName("sys_logininfor") +public class SysLogininfor implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "info_id") + private Long infoId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 用户账号 + */ + private String userName; + + /** + * 登录状态 0成功 1失败 + */ + private String status; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 提示消息 + */ + private String msg; + + /** + * 访问时间 + */ + private Date loginTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysMenu.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysMenu.java new file mode 100644 index 00000000..10fedb5d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysMenu.java @@ -0,0 +1,191 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.List; + +/** + * 菜单权限表 sys_menu + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_menu") +public class SysMenu extends BaseEntity { + + /** + * 菜单ID + */ + @TableId(value = "menu_id") + private Long menuId; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 菜单名称 + */ + private String menuName; + + /** + * 显示顺序 + */ + private Integer orderNum; + + /** + * 路由地址 + */ + private String path; + + /** + * 组件路径 + */ + private String component; + + /** + * 路由参数 + */ + private String queryParam; + + /** + * 是否为外链(0是 1否) + */ + private String isFrame; + + /** + * 是否缓存(0缓存 1不缓存) + */ + private String isCache; + + /** + * 类型(M目录 C菜单 F按钮) + */ + private String menuType; + + /** + * 显示状态(0显示 1隐藏) + */ + private String visible; + + /** + * 菜单状态(0正常 1停用) + */ + private String status; + + /** + * 权限字符串 + */ + private String perms; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 备注 + */ + private String remark; + + /** + * 父菜单名称 + */ + @TableField(exist = false) + private String parentName; + + /** + * 子菜单 + */ + @TableField(exist = false) + private List children = new ArrayList<>(); + + /** + * 获取路由名称 + */ + public String getRouteName() { + String routerName = StringUtils.capitalize(path); + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame()) { + routerName = StringUtils.EMPTY; + } + return routerName; + } + + /** + * 获取路由地址 + */ + public String getRouterPath() { + String routerPath = this.path; + // 内链打开外网方式 + if (getParentId() != 0L && isInnerLink()) { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0L == getParentId() && UserConstants.TYPE_DIR.equals(getMenuType()) + && UserConstants.NO_FRAME.equals(getIsFrame())) { + routerPath = "/" + this.path; + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame()) { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + */ + public String getComponentInfo() { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(this.component) && !isMenuFrame()) { + component = this.component; + } else if (StringUtils.isEmpty(this.component) && getParentId() != 0L && isInnerLink()) { + component = UserConstants.INNER_LINK; + } else if (StringUtils.isEmpty(this.component) && isParentView()) { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + */ + public boolean isMenuFrame() { + return getParentId() == 0L && UserConstants.TYPE_MENU.equals(menuType) && isFrame.equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + */ + public boolean isInnerLink() { + return isFrame.equals(UserConstants.NO_FRAME) && StringUtils.ishttp(path); + } + + /** + * 是否为parent_view组件 + */ + public boolean isParentView() { + return getParentId() != 0L && UserConstants.TYPE_DIR.equals(menuType); + } + + /** + * 内链域名特殊字符替换 + */ + public static String innerLinkReplaceEach(String path) { + return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, "."}, + new String[]{"", "", "", "/"}); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysModel.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysModel.java new file mode 100644 index 00000000..37b1412e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysModel.java @@ -0,0 +1,77 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 系统模型对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_model") +public class SysModel extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 模型名称 + */ + private String modelName; + + /** + * 模型描述 + */ + private String modelDescribe; + + /** + * 模型价格 + */ + private double modelPrice; + + /** + * 计费类型 + */ + private String modelType; + + /** + * 是否显示 + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 请求地址 + */ + private String apiHost; + + /** + * 密钥 + */ + private String apiKey; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNotice.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNotice.java new file mode 100644 index 00000000..3ba2c925 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNotice.java @@ -0,0 +1,51 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + + +/** + * 通知公告表 sys_notice + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_notice") +public class SysNotice extends TenantEntity { + + /** + * 公告ID + */ + @TableId(value = "notice_id") + private Long noticeId; + + /** + * 公告标题 + */ + private String noticeTitle; + + /** + * 公告类型(1通知 2公告) + */ + private String noticeType; + + /** + * 公告内容 + */ + private String noticeContent; + + /** + * 公告状态(0正常 1关闭) + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNoticeState.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNoticeState.java new file mode 100644 index 00000000..e0b7f945 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysNoticeState.java @@ -0,0 +1,52 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 用户阅读状态对象 sys_notice_state + * + * @author Lion Li + * @date 2024-05-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_notice_state") +public class SysNoticeState extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "id") + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 公告ID + */ + private Long noticeId; + + /** + * 阅读状态(0未读 1已读) + */ + private String readStatus; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOperLog.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOperLog.java new file mode 100644 index 00000000..118263f5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOperLog.java @@ -0,0 +1,115 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 操作日志记录表 oper_log + * + * @author Lion Li + */ + +@Data +@TableName("sys_oper_log") +public class SysOperLog implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日志主键 + */ + @TableId(value = "oper_id") + private Long operId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 操作模块 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求url + */ + private String operUrl; + + /** + * 操作地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 消耗时间 + */ + private Long costTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOss.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOss.java new file mode 100644 index 00000000..802ab084 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOss.java @@ -0,0 +1,50 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.tenant.core.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * OSS对象存储对象 + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_oss") +public class SysOss extends TenantEntity { + + /** + * 对象存储主键 + */ + @TableId(value = "oss_id") + private Long ossId; + + /** + * 文件名 + */ + private String fileName; + + /** + * 原名 + */ + private String originalName; + + /** + * 文件后缀名 + */ + private String fileSuffix; + + /** + * URL地址 + */ + private String url; + + /** + * 服务商 + */ + private String service; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOssConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOssConfig.java new file mode 100644 index 00000000..82f94efb --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysOssConfig.java @@ -0,0 +1,89 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.tenant.core.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 对象存储配置对象 sys_oss_config + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_oss_config") +public class SysOssConfig extends TenantEntity { + + /** + * 主建 + */ + @TableId(value = "oss_config_id") + private Long ossConfigId; + + /** + * 配置key + */ + private String configKey; + + /** + * accessKey + */ + private String accessKey; + + /** + * 秘钥 + */ + private String secretKey; + + /** + * 桶名称 + */ + private String bucketName; + + /** + * 前缀 + */ + private String prefix; + + /** + * 访问站点 + */ + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 是否https(0否 1是) + */ + private String isHttps; + + /** + * 域 + */ + private String region; + + /** + * 是否默认(0=是,1=否) + */ + private String status; + + /** + * 扩展字段 + */ + private String ext1; + + /** + * 备注 + */ + private String remark; + + /** + * 桶权限类型(0private 1public 2custom) + */ + private String accessPolicy; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPackagePlan.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPackagePlan.java new file mode 100644 index 00000000..5bfbe80f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPackagePlan.java @@ -0,0 +1,58 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.math.BigDecimal; + +/** + * 套餐管理对象 sys_package_plan + * + * @author Lion Li + * @date 2024-05-05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_package_plan") +public class SysPackagePlan extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 套餐名称 + */ + private String name; + + /** + * 套餐价格 + */ + private BigDecimal price; + + /** + * 有效时间 + */ + private Long duration; + + /** + * 计划详情 + */ + private String planDetail; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPost.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPost.java new file mode 100644 index 00000000..bb3b9fbf --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPost.java @@ -0,0 +1,51 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 岗位表 sys_post + * + * @author Lion Li + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_post") +public class SysPost extends TenantEntity { + + /** + * 岗位序号 + */ + @TableId(value = "post_id") + private Long postId; + + /** + * 岗位编码 + */ + private String postCode; + + /** + * 岗位名称 + */ + private String postName; + + /** + * 岗位排序 + */ + private Integer postSort; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRole.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRole.java new file mode 100644 index 00000000..f1dd70a4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRole.java @@ -0,0 +1,79 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.ruoyi.common.tenant.core.TenantEntity; + +/** + * 角色表 sys_role + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("sys_role") +public class SysRole extends TenantEntity { + + /** + * 角色ID + */ + @TableId(value = "role_id") + private Long roleId; + + /** + * 角色名称 + */ + private String roleName; + + /** + * 角色权限 + */ + private String roleKey; + + /** + * 角色排序 + */ + private Integer roleSort; + + /** + * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) + */ + private String dataScope; + + /** + * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) + */ + private Boolean menuCheckStrictly; + + /** + * 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) + */ + private Boolean deptCheckStrictly; + + /** + * 角色状态(0正常 1停用) + */ + private String status; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 备注 + */ + private String remark; + + public SysRole(Long roleId) { + this.roleId = roleId; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleDept.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleDept.java new file mode 100644 index 00000000..6753be99 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleDept.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 角色和部门关联 sys_role_dept + * + * @author Lion Li + */ + +@Data +@TableName("sys_role_dept") +public class SysRoleDept { + + /** + * 角色ID + */ + @TableId(type = IdType.INPUT) + private Long roleId; + + /** + * 部门ID + */ + private Long deptId; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleMenu.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleMenu.java new file mode 100644 index 00000000..2f73cbaf --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysRoleMenu.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author Lion Li + */ + +@Data +@TableName("sys_role_menu") +public class SysRoleMenu { + + /** + * 角色ID + */ + @TableId(type = IdType.INPUT) + private Long roleId; + + /** + * 菜单ID + */ + private Long menuId; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenant.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenant.java new file mode 100644 index 00000000..d0596f1e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenant.java @@ -0,0 +1,103 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; +import java.util.Date; + +/** + * 租户对象 sys_tenant + * + * @author Michelle.Chung + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_tenant") +public class SysTenant extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id") + private Long id; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 联系人 + */ + private String contactUserName; + + /** + * 联系电话 + */ + private String contactPhone; + + /** + * 企业名称 + */ + private String companyName; + + /** + * 统一社会信用代码 + */ + private String licenseNumber; + + /** + * 地址 + */ + private String address; + + /** + * 域名 + */ + private String domain; + + /** + * 企业简介 + */ + private String intro; + + /** + * 备注 + */ + private String remark; + + /** + * 租户套餐编号 + */ + private Long packageId; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 用户数量(-1不限制) + */ + private Long accountCount; + + /** + * 租户状态(0正常 1停用) + */ + private String status; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenantPackage.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenantPackage.java new file mode 100644 index 00000000..f7ce8cac --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysTenantPackage.java @@ -0,0 +1,56 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 租户套餐对象 sys_tenant_package + * + * @author Michelle.Chung + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_tenant_package") +public class SysTenantPackage extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 租户套餐id + */ + @TableId(value = "package_id") + private Long packageId; + /** + * 套餐名称 + */ + private String packageName; + /** + * 关联菜单id + */ + private String menuIds; + /** + * 备注 + */ + private String remark; + /** + * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) + */ + private Boolean menuCheckStrictly; + /** + * 状态(0正常 1停用) + */ + private String status; + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUser.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUser.java new file mode 100644 index 00000000..96e80db1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUser.java @@ -0,0 +1,134 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.tenant.core.TenantEntity; + +import java.util.Date; + +/** + * 用户对象 sys_user + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user") +public class SysUser extends TenantEntity { + + /** + * 用户ID + */ + @TableId(value = "user_id") + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户账号 + */ + private String userName; + + + /** + * 用户套餐 + */ + private String userPlan; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 用户类型(sys_user系统用户) + */ + private String userType; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phonenumber; + + /** + * 用户性别 + */ + private String sex; + + /** + * 用户头像 + */ + private String avatar; + + /** + * 密码 + */ + @TableField( + insertStrategy = FieldStrategy.NOT_EMPTY, + updateStrategy = FieldStrategy.NOT_EMPTY, + whereStrategy = FieldStrategy.NOT_EMPTY + ) + private String password; + + /** + * 帐号状态(0正常 1停用) + */ + private String status; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 最后登录IP + */ + private String loginIp; + + /** + * 注册域名 + */ + private String domainName; + + /** + * 最后登录时间 + */ + private Date loginDate; + + /** + * 备注 + */ + private String remark; + + /** 普通用户的标识,对当前开发者帐号唯一。一个openid对应一个公众号或小程序 */ + private String openId; + + /** 用户余额 */ + private Double userBalance; + + /** 用户等级 */ + private String userGrade; + + public SysUser(Long userId) { + this.userId = userId; + } + + public boolean isSuperAdmin() { + return UserConstants.SUPER_ADMIN_ID.equals(this.userId); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserGroup.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserGroup.java new file mode 100644 index 00000000..b75abf8f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserGroup.java @@ -0,0 +1,58 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import org.ruoyi.common.tenant.core.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 【请填写功能名称】对象 sys_user_group + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user_group") +public class SysUserGroup extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 用户组名称 + */ + private String groupName; + + /** + * 备注 + */ + private String remark; + + /** + * 版本 + */ + @Version + private Long version; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 更新IP + */ + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserModel.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserModel.java new file mode 100644 index 00000000..751a1e62 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserModel.java @@ -0,0 +1,42 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 【请填写功能名称】对象 sys_user_model + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user_model") +public class SysUserModel extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id") + private Long id; + + /** + * 模型id + */ + private Long mid; + + /** + * 用户组id + */ + private Long gid; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserOnline.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserOnline.java new file mode 100644 index 00000000..e7e65046 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserOnline.java @@ -0,0 +1,54 @@ +package org.ruoyi.system.domain; + +import lombok.Data; + +/** + * 当前在线会话 + * + * @author Lion Li + */ + +@Data +public class SysUserOnline { + + /** + * 会话编号 + */ + private String tokenId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 用户名称 + */ + private String userName; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地址 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录时间 + */ + private Long loginTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserPost.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserPost.java new file mode 100644 index 00000000..6ab0c25f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserPost.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 用户和岗位关联 sys_user_post + * + * @author Lion Li + */ + +@Data +@TableName("sys_user_post") +public class SysUserPost { + + /** + * 用户ID + */ + @TableId(type = IdType.INPUT) + private Long userId; + + /** + * 岗位ID + */ + private Long postId; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserRole.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserRole.java new file mode 100644 index 00000000..a2d5b259 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysUserRole.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 用户和角色关联 sys_user_role + * + * @author Lion Li + */ + +@Data +@TableName("sys_user_role") +public class SysUserRole { + + /** + * 用户ID + */ + @TableId(type = IdType.INPUT) + private Long userId; + + /** + * 角色ID + */ + private Long roleId; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/VoiceRole.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/VoiceRole.java new file mode 100644 index 00000000..a74ef975 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/VoiceRole.java @@ -0,0 +1,63 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 配音角色对象 voice_role + * + * @author Lion Li + * @date 2024-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_audio_role") +public class VoiceRole extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id") + private Long id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色描述 + */ + private String description; + + /** + * 头像 + */ + private String avatar; + + /** + * 角色id + */ + private String voiceId; + + /** + * 音频地址 + */ + private String fileUrl; + + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobConfig.java new file mode 100644 index 00000000..490a3e51 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobConfig.java @@ -0,0 +1,66 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 微信机器人对象 wx_rob_config + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("chat_rob_config") +public class WxRobConfig extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 机器人名称 + */ + private String botName; + + /** + * 机器唯一码 + */ + private String uniqueKey; + + /** + * 备注(微信号) + */ + private String remark; + + /** + * 默认好友回复开关 + */ + private String defaultFriend; + + /** + * 默认群回复开关 + */ + private String defaultGroup; + + + /** + * 机器启用1禁用0 + */ + private String enable; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobKeyword.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobKeyword.java new file mode 100644 index 00000000..5a4dbe6f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobKeyword.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 【请填写功能名称】对象 wx_rob_keyword + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wx_rob_keyword") +public class WxRobKeyword extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @TableId(value = "id") + private Long id; + + /** + * 机器唯一码 + */ + private String uniqueKey; + + /** + * 关键词 + */ + private String keyData; + + /** + * 回复内容 + */ + private String valueData; + + /** + * 回复类型 + */ + private String typeData; + + /** + * 目标昵称 + */ + private String nickName; + + /** + * 群1好友0 + */ + private Integer toGroup; + + /** + * 启用1禁用0 + */ + private Integer enable; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobRelation.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobRelation.java new file mode 100644 index 00000000..e67c40c9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobRelation.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 【请填写功能名称】对象 wx_rob_relation + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("wx_rob_relation") +public class WxRobRelation extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @TableId(value = "id") + private Long id; + + /** + * 外接唯一码 + */ + private String outKey; + + /** + * 机器唯一码 + */ + private String uniqueKey; + + /** + * 目标昵称 + */ + private String nickName; + + /** + * 群1好友0 + */ + private Integer toGroup; + + /** + * 启用1禁用0 + */ + private Integer enable; + + /** + * IP白名单 + */ + private String whiteList; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatConfigBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatConfigBo.java new file mode 100644 index 00000000..b91ea574 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatConfigBo.java @@ -0,0 +1,68 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.ChatConfig; + +/** + * 对话配置信息 +业务对象 chat_config + * + * @author Lion Li + * @date 2024-04-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ChatConfig.class, reverseConvertGenerate = false) +public class ChatConfigBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 配置类型 + */ + @NotBlank(message = "配置类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String category; + + /** + * 配置名称 + */ + @NotBlank(message = "配置名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String configName; + + /** + * 配置值 + */ + @NotBlank(message = "配置值不能为空", groups = { AddGroup.class, EditGroup.class }) + private String configValue; + + /** + * 说明 + */ + @NotBlank(message = "参数说明不能为空", groups = { AddGroup.class, EditGroup.class }) + private String configDict; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * 更新IP + */ + @NotBlank(message = "更新IP不能为空", groups = { AddGroup.class, EditGroup.class }) + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatGptsBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatGptsBo.java new file mode 100644 index 00000000..01920938 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatGptsBo.java @@ -0,0 +1,87 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.ChatGpts; + +/** + * gpts管理业务对象 chat_gpts + * + * @author Lion Li + * @date 2024-07-09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ChatGpts.class, reverseConvertGenerate = false) +public class ChatGptsBo extends BaseEntity { + + /** + * id + */ + @NotNull(message = "id不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * gpts应用id + */ + @NotBlank(message = "gpts应用id不能为空", groups = { AddGroup.class, EditGroup.class }) + private String gid; + + /** + * gpts应用名称 + */ + @NotBlank(message = "gpts应用名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String name; + + /** + * gpts图标 + */ + private String logo; + + /** + * gpts描述 + */ + private String info; + + /** + * 作者id + */ + private String authorId; + + /** + * 作者名称 + */ + private String authorName; + + /** + * 点赞 + */ + private String useCnt; + + /** + * 差评 + */ + private String bad; + + /** + * 类型 + */ + private String type; + + /** + * 备注 + */ + private String remark; + + /** + * 更新IP + */ + private String updateIp; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatMessageBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatMessageBo.java new file mode 100644 index 00000000..b105a592 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatMessageBo.java @@ -0,0 +1,70 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.ChatMessage; + +/** + * 聊天消息业务对象 chat_message + * + * @author Lion Li + * @date 2023-11-26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ChatMessage.class, reverseConvertGenerate = false) +public class ChatMessageBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID", groups = { AddGroup.class, EditGroup.class }) + private Long UserId; + + /** + * 用户名称 + */ + private String UserName; + + /** + * 消息内容 + */ + @NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class }) + private String content; + + /** + * 扣除费用 + */ + private Double deductCost; + + /** + * 累计 Tokens + */ + @NotNull(message = "累计 Tokens不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer totalTokens; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVisitorUsageBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVisitorUsageBo.java new file mode 100644 index 00000000..6518350d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVisitorUsageBo.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.ChatVisitorUsage; + +/** + * 访客管理业务对象 chat_visitor_usage + * + * @author Lion Li + * @date 2024-07-14 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ChatVisitorUsage.class, reverseConvertGenerate = false) +public class ChatVisitorUsageBo extends BaseEntity { + + /** + * id + */ + @NotNull(message = "id不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 浏览器指纹 + */ + @NotBlank(message = "浏览器指纹不能为空", groups = { AddGroup.class, EditGroup.class }) + private String fingerprint; + + /** + * 使用次数 + */ + @NotBlank(message = "使用次数不能为空", groups = { AddGroup.class, EditGroup.class }) + private String usageCount; + + /** + * ip地址 + */ + @NotBlank(message = "ip地址不能为空", groups = { AddGroup.class, EditGroup.class }) + private String ipAddress; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * 更新IP + */ + @NotBlank(message = "更新IP不能为空", groups = { AddGroup.class, EditGroup.class }) + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVoucherBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVoucherBo.java new file mode 100644 index 00000000..98ff3c72 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/ChatVoucherBo.java @@ -0,0 +1,68 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.ChatVoucher; + +import java.math.BigDecimal; + +/** + * 用户兑换记录业务对象 chat_voucher + * + * @author Lion Li + * @date 2024-05-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = ChatVoucher.class, reverseConvertGenerate = false) +public class ChatVoucherBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 兑换码 + */ + private String code; + + /** + * 兑换金额 + */ + @NotNull(message = "兑换金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal amount; + + /** + * 兑换状态 + */ + private String status; + + /** + * 兑换前余额 + */ + private Double balanceBefore; + + /** + * 兑换后余额 + */ + private Double balanceAfter; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/PaymentOrdersBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/PaymentOrdersBo.java new file mode 100644 index 00000000..e49de79a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/PaymentOrdersBo.java @@ -0,0 +1,76 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.PaymentOrder; + +import java.math.BigDecimal; + +/** + * 支付订单业务对象 payment_orders + * + * @author Lion Li + * @date 2024-04-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = PaymentOrder.class, reverseConvertGenerate = false) +public class PaymentOrdersBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 订单编号 + */ + @NotBlank(message = "订单编号不能为空", groups = { AddGroup.class, EditGroup.class }) + private String orderNo; + + /** + * 订单名称 + */ + @NotBlank(message = "订单名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String orderName; + + /** + * 金额 + */ + @NotNull(message = "金额不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal amount; + + /** + * 支付状态 + */ + @NotBlank(message = "支付状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private String paymentStatus; + + /** + * 支付方式 + */ + @NotBlank(message = "支付方式不能为空", groups = { AddGroup.class, EditGroup.class }) + private String paymentMethod; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysConfigBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysConfigBo.java new file mode 100644 index 00000000..271a96b6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysConfigBo.java @@ -0,0 +1,63 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysConfig; + +/** + * 参数配置业务对象 sys_config + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysConfig.class, reverseConvertGenerate = false) +public class SysConfigBo extends BaseEntity { + + /** + * 参数主键 + */ + @NotNull(message = "参数主键不能为空", groups = { EditGroup.class }) + private Long configId; + + /** + * 参数名称 + */ + @NotBlank(message = "参数名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "参数名称不能超过{max}个字符") + private String configName; + + /** + * 参数键名 + */ + @NotBlank(message = "参数键名不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "参数键名长度不能超过{max}个字符") + private String configKey; + + /** + * 参数键值 + */ + @NotBlank(message = "参数键值不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 500, message = "参数键值长度不能超过{max}个字符") + private String configValue; + + /** + * 系统内置(Y是 N否) + */ + private String configType; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDeptBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDeptBo.java new file mode 100644 index 00000000..0d188423 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDeptBo.java @@ -0,0 +1,73 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysDept; + +/** + * 部门业务对象 sys_dept + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysDept.class, reverseConvertGenerate = false) +public class SysDeptBo extends BaseEntity { + + /** + * 部门id + */ + @NotNull(message = "部门id不能为空", groups = { EditGroup.class }) + private Long deptId; + + /** + * 父部门ID + */ + private Long parentId; + + /** + * 部门名称 + */ + @NotBlank(message = "部门名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 30, message = "部门名称长度不能超过{max}个字符") + private String deptName; + + /** + * 显示顺序 + */ + @NotNull(message = "显示顺序不能为空") + private Integer orderNum; + + /** + * 负责人 + */ + private String leader; + + /** + * 联系电话 + */ + @Size(min = 0, max = 11, message = "联系电话长度不能超过{max}个字符") + private String phone; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符") + private String email; + + /** + * 部门状态(0正常 1停用) + */ + private String status; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictDataBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictDataBo.java new file mode 100644 index 00000000..78341e0b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictDataBo.java @@ -0,0 +1,88 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysDictData; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据业务对象 sys_dict_data + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysDictData.class, reverseConvertGenerate = false) +public class SysDictDataBo extends BaseEntity { + + /** + * 字典编码 + */ + @NotNull(message = "字典编码不能为空", groups = { EditGroup.class }) + private Long dictCode; + + /** + * 字典排序 + */ + private Integer dictSort; + + /** + * 字典标签 + */ + @NotBlank(message = "字典标签不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "字典标签长度不能超过{max}个字符") + private String dictLabel; + + /** + * 字典键值 + */ + @NotBlank(message = "字典键值不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "字典键值长度不能超过{max}个字符") + private String dictValue; + + /** + * 字典类型 + */ + @NotBlank(message = "字典类型不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "字典类型长度不能超过{max}个字符") + private String dictType; + + /** + * 样式属性(其他样式扩展) + */ + @Size(min = 0, max = 100, message = "样式属性长度不能超过{max}个字符") + private String cssClass; + + /** + * 表格回显样式 + */ + private String listClass; + + /** + * 是否默认(Y是 N否) + */ + private String isDefault; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictTypeBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictTypeBo.java new file mode 100644 index 00000000..4cdd1f65 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysDictTypeBo.java @@ -0,0 +1,58 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysDictType; + +/** + * 字典类型业务对象 sys_dict_type + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysDictType.class, reverseConvertGenerate = false) +public class SysDictTypeBo extends BaseEntity { + + /** + * 字典主键 + */ + @NotNull(message = "字典主键不能为空", groups = { EditGroup.class }) + private Long dictId; + + /** + * 字典名称 + */ + @NotBlank(message = "字典名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过{max}个字符") + private String dictName; + + /** + * 字典类型 + */ + @NotBlank(message = "字典类型不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过{max}个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + private String dictType; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysLogininforBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysLogininforBo.java new file mode 100644 index 00000000..63deec15 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysLogininforBo.java @@ -0,0 +1,77 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.SysLogininfor; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 系统访问记录业务对象 sys_logininfor + * + * @author Michelle.Chung + */ + +@Data +@AutoMapper(target = SysLogininfor.class, reverseConvertGenerate = false) +public class SysLogininforBo { + + /** + * 访问ID + */ + private Long infoId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 用户账号 + */ + private String userName; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录状态(0成功 1失败) + */ + private String status; + + /** + * 提示消息 + */ + private String msg; + + /** + * 访问时间 + */ + private Date loginTime; + + /** + * 请求参数 + */ + private Map params = new HashMap<>(); + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysMenuBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysMenuBo.java new file mode 100644 index 00000000..3bd07b35 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysMenuBo.java @@ -0,0 +1,111 @@ +package org.ruoyi.system.domain.bo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysMenu; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 菜单权限业务对象 sys_menu + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysMenu.class, reverseConvertGenerate = false) +public class SysMenuBo extends BaseEntity { + + /** + * 菜单ID + */ + @NotNull(message = "菜单ID不能为空", groups = { EditGroup.class }) + private Long menuId; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 菜单名称 + */ + @NotBlank(message = "菜单名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 50, message = "菜单名称长度不能超过{max}个字符") + private String menuName; + + /** + * 显示顺序 + */ + @NotNull(message = "显示顺序不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer orderNum; + + /** + * 路由地址 + */ + @Size(min = 0, max = 200, message = "路由地址不能超过{max}个字符") + private String path; + + /** + * 组件路径 + */ + @Size(min = 0, max = 200, message = "组件路径不能超过{max}个字符") + private String component; + + /** + * 路由参数 + */ + private String queryParam; + + /** + * 是否为外链(0是 1否) + */ + private String isFrame; + + /** + * 是否缓存(0缓存 1不缓存) + */ + private String isCache; + + /** + * 菜单类型(M目录 C菜单 F按钮) + */ + @NotBlank(message = "菜单类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String menuType; + + /** + * 显示状态(0显示 1隐藏) + */ + private String visible; + + /** + * 菜单状态(0正常 1停用) + */ + private String status; + + /** + * 权限标识 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @Size(min = 0, max = 100, message = "权限标识长度不能超过{max}个字符") + private String perms; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysModelBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysModelBo.java new file mode 100644 index 00000000..d4817ef5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysModelBo.java @@ -0,0 +1,82 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysModel; + +/** + * 系统模型业务对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysModel.class, reverseConvertGenerate = false) +public class SysModelBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + + /** + * 模型描述 + */ + @NotBlank(message = "模型描述不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelDescribe; + + /** + * 模型价格 + */ + @NotNull(message = "模型价格不能为空", groups = { AddGroup.class, EditGroup.class }) + private double modelPrice; + + /** + * 计费类型 (1 token扣费; 2 次数扣费 ) + */ + @NotBlank(message = "计费类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelType; + + /** + * 模型状态 (0 显示; 1 隐藏 ) + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 请求地址 + */ + private String apiHost; + + /** + * 请求密钥 + */ + private String apiKey; + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeBo.java new file mode 100644 index 00000000..fae20993 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeBo.java @@ -0,0 +1,65 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.core.xss.Xss; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysNotice; + +/** + * 通知公告业务对象 sys_notice + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysNotice.class, reverseConvertGenerate = false) +public class SysNoticeBo extends BaseEntity { + + /** + * 公告ID + */ + @NotNull(message = "公告ID不能为空", groups = { EditGroup.class }) + private Long noticeId; + + /** + * 公告标题 + */ + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 50, message = "公告标题不能超过{max}个字符") + private String noticeTitle; + + /** + * 公告类型(1通知 2公告) + */ + private String noticeType; + + /** + * 公告内容 + */ + private String noticeContent; + + /** + * 公告状态(0正常 1关闭) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 创建人名称 + */ + private String createByName; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeStateBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeStateBo.java new file mode 100644 index 00000000..f7a3a355 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysNoticeStateBo.java @@ -0,0 +1,54 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.system.domain.SysNoticeState; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +/** + * 用户阅读状态业务对象 sys_notice_state + * + * @author Lion Li + * @date 2024-05-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysNoticeState.class, reverseConvertGenerate = false) +public class SysNoticeStateBo extends BaseEntity { + + /** + * ID + */ + @NotNull(message = "ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 公告ID + */ + @NotNull(message = "公告ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long noticeId; + + /** + * 阅读状态(0未读 1已读) + */ + @NotBlank(message = "阅读状态(0未读 1已读)不能为空", groups = { AddGroup.class, EditGroup.class }) + private String readStatus; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOperLogBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOperLogBo.java new file mode 100644 index 00000000..620b58a8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOperLogBo.java @@ -0,0 +1,127 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMappers; +import lombok.Data; +import org.ruoyi.common.log.event.OperLogEvent; +import org.ruoyi.system.domain.SysOperLog; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 操作日志记录业务对象 sys_oper_log + * + * @author Michelle.Chung + * @date 2023-02-07 + */ + +@Data +@AutoMappers({ + @AutoMapper(target = SysOperLog.class, reverseConvertGenerate = false), + @AutoMapper(target = OperLogEvent.class) +}) +public class SysOperLogBo { + + /** + * 日志主键 + */ + private Long operId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 模块标题 + */ + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 方法名称 + */ + private String method; + + /** + * 请求方式 + */ + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + private Integer operatorType; + + /** + * 操作人员 + */ + private String operName; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 请求URL + */ + private String operUrl; + + /** + * 主机地址 + */ + private String operIp; + + /** + * 操作地点 + */ + private String operLocation; + + /** + * 请求参数 + */ + private String operParam; + + /** + * 返回参数 + */ + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + private Integer status; + + /** + * 错误消息 + */ + private String errorMsg; + + /** + * 操作时间 + */ + private Date operTime; + + /** + * 消耗时间 + */ + private Long costTime; + + /** + * 请求参数 + */ + private Map params = new HashMap<>(); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssBo.java new file mode 100644 index 00000000..c12e1838 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssBo.java @@ -0,0 +1,49 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysOss; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * OSS对象存储分页查询对象 sys_oss + * + * @author Lion Li + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysOss.class, reverseConvertGenerate = false) +public class SysOssBo extends BaseEntity { + + /** + * ossId + */ + private Long ossId; + + /** + * 文件名 + */ + private String fileName; + + /** + * 原名 + */ + private String originalName; + + /** + * 文件后缀名 + */ + private String fileSuffix; + + /** + * URL地址 + */ + private String url; + + /** + * 服务商 + */ + private String service; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssConfigBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssConfigBo.java new file mode 100644 index 00000000..79872266 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysOssConfigBo.java @@ -0,0 +1,109 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysOssConfig; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 对象存储配置业务对象 sys_oss_config + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysOssConfig.class, reverseConvertGenerate = false) +public class SysOssConfigBo extends BaseEntity { + + /** + * 主建 + */ + @NotNull(message = "主建不能为空", groups = {EditGroup.class}) + private Long ossConfigId; + + /** + * 配置key + */ + @NotBlank(message = "配置key不能为空", groups = {AddGroup.class, EditGroup.class}) + @Size(min = 2, max = 100, message = "configKey长度必须介于{min}和{max} 之间") + private String configKey; + + /** + * accessKey + */ + @NotBlank(message = "accessKey不能为空", groups = {AddGroup.class, EditGroup.class}) + @Size(min = 2, max = 100, message = "accessKey长度必须介于{min}和{max} 之间") + private String accessKey; + + /** + * 秘钥 + */ + @NotBlank(message = "secretKey不能为空", groups = {AddGroup.class, EditGroup.class}) + @Size(min = 2, max = 100, message = "secretKey长度必须介于{min}和{max} 之间") + private String secretKey; + + /** + * 桶名称 + */ + @NotBlank(message = "桶名称不能为空", groups = {AddGroup.class, EditGroup.class}) + @Size(min = 2, max = 100, message = "bucketName长度必须介于{min}和{max}之间") + private String bucketName; + + /** + * 前缀 + */ + private String prefix; + + /** + * 访问站点 + */ + @NotBlank(message = "访问站点不能为空", groups = {AddGroup.class, EditGroup.class}) + @Size(min = 2, max = 100, message = "endpoint长度必须介于{min}和{max}之间") + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 是否https(Y=是,N=否) + */ + private String isHttps; + + /** + * 是否默认(0=是,1=否) + */ + private String status; + + /** + * 域 + */ + private String region; + + /** + * 扩展字段 + */ + private String ext1; + + /** + * 备注 + */ + private String remark; + + /** + * 桶权限类型(0private 1public 2custom) + */ + @NotBlank(message = "桶权限类型不能为空", groups = {AddGroup.class, EditGroup.class}) + private String accessPolicy; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPackagePlanBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPackagePlanBo.java new file mode 100644 index 00000000..44f182b2 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPackagePlanBo.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.system.domain.SysPackagePlan; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; + +/** + * 套餐管理业务对象 sys_package_plan + * + * @author Lion Li + * @date 2024-05-05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysPackagePlan.class, reverseConvertGenerate = false) +public class SysPackagePlanBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 套餐名称 + */ + @NotBlank(message = "套餐名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String name; + + /** + * 套餐价格 + */ + @NotNull(message = "套餐价格不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal price; + + /** + * 有效时间 + */ + @NotNull(message = "有效时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long duration; + + /** + * 计划详情 + */ + @NotBlank(message = "计划详情不能为空", groups = { AddGroup.class, EditGroup.class }) + private String planDetail; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPostBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPostBo.java new file mode 100644 index 00000000..96270744 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysPostBo.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysPost; + +/** + * 岗位信息业务对象 sys_post + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysPost.class, reverseConvertGenerate = false) +public class SysPostBo extends BaseEntity { + + /** + * 岗位ID + */ + @NotNull(message = "岗位ID不能为空", groups = { EditGroup.class }) + private Long postId; + + /** + * 岗位编码 + */ + @NotBlank(message = "岗位编码不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 64, message = "岗位编码长度不能超过{max}个字符") + private String postCode; + + /** + * 岗位名称 + */ + @NotBlank(message = "岗位名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 50, message = "岗位名称长度不能超过{max}个字符") + private String postName; + + /** + * 显示顺序 + */ + @NotNull(message = "显示顺序不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer postSort; + + /** + * 状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysRoleBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysRoleBo.java new file mode 100644 index 00000000..cccaf7e9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysRoleBo.java @@ -0,0 +1,97 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysRole; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 角色信息业务对象 sys_role + * + * @author Michelle.Chung + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysRole.class, reverseConvertGenerate = false) +public class SysRoleBo extends BaseEntity { + + /** + * 角色ID + */ + @NotNull(message = "角色ID不能为空", groups = { EditGroup.class }) + private Long roleId; + + /** + * 角色名称 + */ + @NotBlank(message = "角色名称不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 30, message = "角色名称长度不能超过{max}个字符") + private String roleName; + + /** + * 角色权限字符串 + */ + @NotBlank(message = "角色权限字符串不能为空", groups = { AddGroup.class, EditGroup.class }) + @Size(min = 0, max = 100, message = "权限字符长度不能超过{max}个字符") + private String roleKey; + + /** + * 显示顺序 + */ + @NotNull(message = "显示顺序不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer roleSort; + + /** + * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限) + */ + private String dataScope; + + /** + * 菜单树选择项是否关联显示 + */ + private Boolean menuCheckStrictly; + + /** + * 部门树选择项是否关联显示 + */ + private Boolean deptCheckStrictly; + + /** + * 角色状态(0正常 1停用) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 菜单组 + */ + private Long[] menuIds; + + /** + * 部门组(数据权限) + */ + private Long[] deptIds; + + public SysRoleBo(Long roleId) { + this.roleId = roleId; + } + + public boolean isSuperAdmin() { + return UserConstants.SUPER_ADMIN_ID.equals(this.roleId); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantBo.java new file mode 100644 index 00000000..d4919c84 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantBo.java @@ -0,0 +1,114 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysTenant; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 租户业务对象 sys_tenant + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysTenant.class, reverseConvertGenerate = false) +public class SysTenantBo extends BaseEntity { + + /** + * id + */ + @NotNull(message = "id不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 联系人 + */ + @NotBlank(message = "联系人不能为空", groups = { AddGroup.class, EditGroup.class }) + private String contactUserName; + + /** + * 联系电话 + */ + @NotBlank(message = "联系电话不能为空", groups = { AddGroup.class, EditGroup.class }) + private String contactPhone; + + /** + * 企业名称 + */ + @NotBlank(message = "企业名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String companyName; + + /** + * 用户名(创建系统用户) + */ + @NotBlank(message = "用户名不能为空", groups = { AddGroup.class }) + private String username; + + /** + * 密码(创建系统用户) + */ + @NotBlank(message = "密码不能为空", groups = { AddGroup.class }) + private String password; + + /** + * 统一社会信用代码 + */ + private String licenseNumber; + + /** + * 地址 + */ + private String address; + + /** + * 域名 + */ + private String domain; + + /** + * 企业简介 + */ + private String intro; + + /** + * 备注 + */ + private String remark; + + /** + * 租户套餐编号 + */ + @NotNull(message = "租户套餐不能为空", groups = { AddGroup.class }) + private Long packageId; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 用户数量(-1不限制) + */ + private Long accountCount; + + /** + * 租户状态(0正常 1停用) + */ + private String status; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantPackageBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantPackageBo.java new file mode 100644 index 00000000..bb52240d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysTenantPackageBo.java @@ -0,0 +1,59 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysTenantPackage; + +/** + * 租户套餐业务对象 sys_tenant_package + * + * @author Michelle.Chung + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysTenantPackage.class, reverseConvertGenerate = false) +public class SysTenantPackageBo extends BaseEntity { + + /** + * 租户套餐id + */ + @NotNull(message = "租户套餐id不能为空", groups = { EditGroup.class }) + private Long packageId; + + /** + * 套餐名称 + */ + @NotBlank(message = "套餐名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String packageName; + + /** + * 关联菜单id + */ + @AutoMapping(target = "menuIds", expression = "java(org.ruoyi.common.core.utils.StringUtils.join(source.getMenuIds(), \",\"))") + private Long[] menuIds; + + /** + * 备注 + */ + private String remark; + + /** + * 菜单树选择项是否关联显示 + */ + private Boolean menuCheckStrictly; + + /** + * 状态(0正常 1停用) + */ + private String status; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserBo.java new file mode 100644 index 00000000..d91838a2 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserBo.java @@ -0,0 +1,140 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.xss.Xss; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.common.sensitive.annotation.Sensitive; +import org.ruoyi.common.sensitive.core.SensitiveStrategy; +import org.ruoyi.system.domain.SysUser; + +/** + * 用户信息业务对象 sys_user + * + * @author Michelle.Chung + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysUser.class, reverseConvertGenerate = false) +public class SysUserBo extends BaseEntity { + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户账号 + */ + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") + private String userName; + + /** + * 用户昵称 + */ + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符") + private String nickName; + + /** + * 用户类型(sys_user系统用户) + */ + private String userType; + + /** + * 用户邮箱 + */ + @Sensitive(strategy = SensitiveStrategy.EMAIL) + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符") + private String email; + + /** + * 手机号码 + */ + @Sensitive(strategy = SensitiveStrategy.PHONE) + private String phonenumber; + + /** + * 用户性别(0男 1女 2未知) + */ + private String sex; + + /** + * 密码 + */ + private String password; + + + /** + * 用户套餐 + */ + private String userPlan; + + /** + * 帐号状态(0正常 1停用) + */ + private String status; + + /** + * 微信头像 + */ + private String avatar; + + /** + * 备注 + */ + private String remark; + + /** + * 注册域名 + */ + private String domainName; + /** + * 角色组 + */ + private Long[] roleIds; + + /** + * 岗位组 + */ + private Long[] postIds; + + /** + * 数据权限 当前角色ID + */ + private Long roleId; + + /** 普通用户的标识,对当前开发者帐号唯一。一个openid对应一个公众号或小程序 */ + private String openId; + + /** 用户等级 */ + private String userGrade; + + /** 用户余额 */ + private Double userBalance; + + public SysUserBo(Long userId) { + this.userId = userId; + } + + public boolean isSuperAdmin() { + return UserConstants.SUPER_ADMIN_ID.equals(this.userId); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserGroupBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserGroupBo.java new file mode 100644 index 00000000..4d773559 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserGroupBo.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.system.domain.SysUserGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +/** + * 【请填写功能名称】业务对象 sys_user_group + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysUserGroup.class, reverseConvertGenerate = false) +public class SysUserGroupBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户组名称 + */ + @NotBlank(message = "用户组名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String groupName; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * 更新IP + */ + @NotBlank(message = "更新IP不能为空", groups = { AddGroup.class, EditGroup.class }) + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserModelBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserModelBo.java new file mode 100644 index 00000000..5c77edb2 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserModelBo.java @@ -0,0 +1,42 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.SysUserModel; + +/** + * 【请填写功能名称】业务对象 sys_user_model + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = SysUserModel.class, reverseConvertGenerate = false) +public class SysUserModelBo extends BaseEntity { + + /** + * id + */ + @NotNull(message = "id不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 模型id + */ + @NotNull(message = "模型id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long mid; + + /** + * 用户组id + */ + @NotNull(message = "用户组id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long gid; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserProfileBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserProfileBo.java new file mode 100644 index 00000000..27b0b979 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserProfileBo.java @@ -0,0 +1,55 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.core.xss.Xss; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.common.sensitive.annotation.Sensitive; +import org.ruoyi.common.sensitive.core.SensitiveStrategy; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 个人信息业务处理 + * + * @author Michelle.Chung + */ + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SysUserProfileBo extends BaseEntity { + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户昵称 + */ + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符") + private String nickName; + + /** + * 用户邮箱 + */ + @Sensitive(strategy = SensitiveStrategy.EMAIL) + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符") + private String email; + + /** + * 手机号码 + */ + @Sensitive(strategy = SensitiveStrategy.PHONE) + private String phonenumber; + + /** + * 用户性别(0男 1女 2未知) + */ + private String sex; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/VoiceRoleBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/VoiceRoleBo.java new file mode 100644 index 00000000..2243af68 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/VoiceRoleBo.java @@ -0,0 +1,66 @@ +package org.ruoyi.system.domain.bo; + +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.VoiceRole; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 配音角色业务对象 voice_role + * + * @author Lion Li + * @date 2024-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = VoiceRole.class, reverseConvertGenerate = false) +public class VoiceRoleBo extends BaseEntity { + + /** + * id + */ + @NotNull(message = "id不能为空") + private Long id; + + /** + * 角色名称 + */ + @NotBlank(message = "角色名称不能为空") + private String name; + + /** + * 角色描述 + */ + @NotBlank(message = "角色描述不能为空") + private String description; + + /** + * 头像 + */ + @NotBlank(message = "头像不能为空") + private String avatar; + + /** + * 角色id + */ + @NotBlank(message = "角色id不能为空") + private String voiceId; + + /** + * 音频地址 + */ + @NotBlank(message = "音频地址不能为空") + private String fileUrl; + + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空") + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobConfigBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobConfigBo.java new file mode 100644 index 00000000..ea8018a6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobConfigBo.java @@ -0,0 +1,63 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.WxRobConfig; + +/** + * 微信机器人业务对象 wx_rob_config + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = WxRobConfig.class, reverseConvertGenerate = false) +public class WxRobConfigBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 机器人名称 + */ + private String botName; + + /** + * 机器唯一码 + */ + private String uniqueKey; + + /** + * 默认好友回复开关 + */ + private String defaultFriend; + + /** + * 默认群回复开关 + */ + private String defaultGroup; + + /** + * 机器启用1禁用0 + */ + private String enable; + + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobKeywordBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobKeywordBo.java new file mode 100644 index 00000000..262bb8d0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobKeywordBo.java @@ -0,0 +1,73 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.WxRobKeyword; + +/** + * 【请填写功能名称】业务对象 wx_rob_keyword + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = WxRobKeyword.class, reverseConvertGenerate = false) +public class WxRobKeywordBo extends BaseEntity { + + /** + * + */ + @NotNull(message = "不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 机器唯一码 + */ + @NotBlank(message = "机器唯一码不能为空", groups = { AddGroup.class, EditGroup.class }) + private String uniqueKey; + + /** + * 关键词 + */ + @NotBlank(message = "关键词不能为空", groups = { AddGroup.class, EditGroup.class }) + private String keyData; + + /** + * 回复内容 + */ + @NotBlank(message = "回复内容不能为空", groups = { AddGroup.class, EditGroup.class }) + private String valueData; + + /** + * 回复类型 + */ + @NotBlank(message = "回复类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String typeData; + + /** + * 目标昵称 + */ + @NotBlank(message = "目标昵称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String nickName; + + /** + * 群1好友0 + */ + @NotNull(message = "群1好友0不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer toGroup; + + /** + * 启用1禁用0 + */ + @NotNull(message = "启用1禁用0不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer enable; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobRelationBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobRelationBo.java new file mode 100644 index 00000000..56e95693 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/WxRobRelationBo.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.domain.bo; + +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.common.mybatis.core.domain.BaseEntity; +import org.ruoyi.system.domain.WxRobRelation; + +/** + * 【请填写功能名称】业务对象 wx_rob_relation + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = WxRobRelation.class, reverseConvertGenerate = false) +public class WxRobRelationBo extends BaseEntity { + + /** + * + */ + @NotNull(message = "不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 外接唯一码 + */ + @NotBlank(message = "外接唯一码不能为空", groups = { AddGroup.class, EditGroup.class }) + private String outKey; + + /** + * 机器唯一码 + */ + @NotBlank(message = "机器唯一码不能为空", groups = { AddGroup.class, EditGroup.class }) + private String uniqueKey; + + /** + * 目标昵称 + */ + @NotBlank(message = "目标昵称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String nickName; + + /** + * 群1好友0 + */ + @NotNull(message = "群1好友0不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer toGroup; + + /** + * 启用1禁用0 + */ + @NotNull(message = "启用1禁用0不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer enable; + + /** + * IP白名单 + */ + @NotBlank(message = "IP白名单不能为空", groups = { AddGroup.class, EditGroup.class }) + private String whiteList; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ApiResult.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ApiResult.java new file mode 100644 index 00000000..1dc6bf6d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ApiResult.java @@ -0,0 +1,31 @@ +package org.ruoyi.system.domain.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * @author https://www.wdbyte.com + */ +@Slf4j +@Getter +@Setter +public class ApiResult { + private Integer code; + private String message; + private Object data; + + public ApiResult() { + } + + public ApiResult(Integer code, String message) { + this.code = code; + this.message = message; + } + + public ApiResult(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ReceiveMessage.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ReceiveMessage.java new file mode 100644 index 00000000..3cd2b896 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/ReceiveMessage.java @@ -0,0 +1,58 @@ +package org.ruoyi.system.domain.model; + +import lombok.Data; + +@Data +public class ReceiveMessage { + /** + * 开发者微信号 + */ + private String toUserName; + /** + * 发送方账号(一个openid) + */ + private String fromUserName; + /** + * 消息创建时间(整形) + */ + private String createTime; + /** + * 消息类型 + */ + private String msgType; + /** + * 文本消息内容 + */ + private String content; + /** + * 消息ID 64位 + */ + String msgId; + /** + * 消息的数据ID 消息来自文章才有 + */ + private String msgDataId; + /** + * 多图文时第几篇文章,从1开始 消息如果来自文章才有 + */ + private String idx; + /** + * 订阅事件 subscribe 订阅 unsbscribe 取消订阅 + */ + private String event; + /** + * 扫码 - ticket + */ + private String ticket; + + public String getReplyTextMsg(String msg) { + String xml = "\n" + + " \n" + + " \n" + + " " + System.currentTimeMillis() + "\n" + + " \n" + + " \n" + + " "; + return xml; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/WeixinQrCode.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/WeixinQrCode.java new file mode 100644 index 00000000..2ce61621 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/model/WeixinQrCode.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.domain.model; + +import lombok.Data; + +/** + * @author https://www.wdbyte.com + */ +@Data +public class WeixinQrCode { + + private String ticket; + private Long expireSeconds; + private String url; + private String qrCodeUrl; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/EmailRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/EmailRequest.java new file mode 100644 index 00000000..9a1b82c2 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/EmailRequest.java @@ -0,0 +1,18 @@ +package org.ruoyi.system.domain.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +/** + * 用户登录 + */ +@Data +public class EmailRequest { + /** + * 账号 + */ + @NotNull(message = "账号不能为空") + private String username; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/OrderRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/OrderRequest.java new file mode 100644 index 00000000..09315427 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/OrderRequest.java @@ -0,0 +1,32 @@ +package org.ruoyi.system.domain.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class OrderRequest { + + /** + * 商品金额 + */ + @NotNull(message = "商品金额") + private String money; + + /** + * 商品名称 + */ + @NotNull(message = "商品名称") + private String name; + + /** + * 订单编号 + */ + @NotNull(message = "订单编号") + private String orderNo; + + /** + * 支付方式 + */ + @NotNull(message = "支付方式") + private String payType; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/UserRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/UserRequest.java new file mode 100644 index 00000000..1fd35b89 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/request/UserRequest.java @@ -0,0 +1,18 @@ +package org.ruoyi.system.domain.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +/** + * 编辑用户 + */ +@Data +public class UserRequest { + /** + * 用户名称 + */ + @NotNull(message = "用户名称不能为空") + private String nickName; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/AvatarVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/AvatarVo.java new file mode 100644 index 00000000..398838b6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/AvatarVo.java @@ -0,0 +1,18 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +/** + * 用户头像信息 + * + * @author Michelle.Chung + */ +@Data +public class AvatarVo { + + /** + * 头像地址 + */ + private String imgUrl; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CacheListInfoVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CacheListInfoVo.java new file mode 100644 index 00000000..4012358e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CacheListInfoVo.java @@ -0,0 +1,23 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * 缓存监控列表信息 + * + * @author Michelle.Chung + */ +@Data +public class CacheListInfoVo { + + private Properties info; + + private Long dbSize; + + private List> commandStats; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CaptchaVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CaptchaVo.java new file mode 100644 index 00000000..b79462b3 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/CaptchaVo.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +/** + * 验证码信息 + * + * @author Michelle.Chung + */ +@Data +public class CaptchaVo { + + /** + * 是否开启验证码 + */ + private Boolean captchaEnabled = true; + + private String uuid; + + /** + * 验证码图片 + */ + private String img; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java new file mode 100644 index 00000000..5631b92a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatConfigVo.java @@ -0,0 +1,72 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.ChatConfig; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 对话配置信息 +视图对象 chat_config + * + * @author Lion Li + * @date 2024-04-13 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = ChatConfig.class) +public class ChatConfigVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 配置类型 + */ + @ExcelProperty(value = "配置类型") + private String category; + + /** + * 配置名称 + */ + @ExcelProperty(value = "配置名称") + private String configName; + + /** + * 配置值 + */ + @ExcelProperty(value = "配置值") + private String configValue; + + /** + * 说明 + */ + @ExcelProperty(value = "说明") + private String configDict; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 更新IP + */ + @ExcelProperty(value = "更新IP") + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatGptsVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatGptsVo.java new file mode 100644 index 00000000..9d732418 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatGptsVo.java @@ -0,0 +1,100 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.ChatGpts; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * gpts管理视图对象 chat_gpts + * + * @author Lion Li + * @date 2024-07-09 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = ChatGpts.class) +public class ChatGptsVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @ExcelProperty(value = "id") + private Long id; + + /** + * gpts应用id + */ + @ExcelProperty(value = "gpts应用id") + private String gid; + + /** + * gpts应用名称 + */ + @ExcelProperty(value = "gpts应用名称") + private String name; + + /** + * gpts图标 + */ + @ExcelProperty(value = "gpts图标") + private String logo; + + /** + * gpts描述 + */ + @ExcelProperty(value = "gpts描述") + private String info; + + /** + * 作者id + */ + @ExcelProperty(value = "作者id") + private String authorId; + + /** + * 作者名称 + */ + @ExcelProperty(value = "作者名称") + private String authorName; + + /** + * 点赞 + */ + @ExcelProperty(value = "点赞") + private String useCnt; + + /** + * 差评 + */ + @ExcelProperty(value = "差评") + private String bad; + + /** + * 类型 + */ + @ExcelProperty(value = "类型") + private String type; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 更新IP + */ + @ExcelProperty(value = "更新IP") + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatMessageVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatMessageVo.java new file mode 100644 index 00000000..3d4d06cc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatMessageVo.java @@ -0,0 +1,84 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.system.domain.ChatMessage; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 聊天消息视图对象 chat_message + * + * @author Lion Li + * @date 2023-11-26 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = ChatMessage.class) +public class ChatMessageVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 用户名称 + */ + @NotBlank(message = "用户名称") + private String userName; + + /** + * 消息内容 + */ + @NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class }) + private String content; + + + /** + * 扣除费用 + */ + private Double deductCost; + + /** + * 累计 Tokens + */ + @NotNull(message = "累计 Tokens不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer totalTokens; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** + * 创建时间 + */ + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatTokenVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatTokenVo.java new file mode 100644 index 00000000..ce7eff4a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatTokenVo.java @@ -0,0 +1,43 @@ +package org.ruoyi.system.domain.vo; + +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户token chat_token + * + * @author Lion Li + * @date 2023-11-26 + */ +@Data +public class ChatTokenVo implements Serializable { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID", groups = { AddGroup.class, EditGroup.class }) + private Long UserId; + + /** + * 待结算token + */ + private Integer token; + + /** + * 模型名称 + */ + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVisitorUsageVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVisitorUsageVo.java new file mode 100644 index 00000000..217ae449 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVisitorUsageVo.java @@ -0,0 +1,64 @@ +package org.ruoyi.system.domain.vo; + +import org.ruoyi.system.domain.ChatVisitorUsage; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 访客管理视图对象 chat_visitor_usage + * + * @author Lion Li + * @date 2024-07-14 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = ChatVisitorUsage.class) +public class ChatVisitorUsageVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @ExcelProperty(value = "id") + private Long id; + + /** + * 浏览器指纹 + */ + @ExcelProperty(value = "浏览器指纹") + private String fingerprint; + + /** + * 使用次数 + */ + @ExcelProperty(value = "使用次数") + private String usageCount; + + /** + * ip地址 + */ + @ExcelProperty(value = "ip地址") + private String ipAddress; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 更新IP + */ + @ExcelProperty(value = "更新IP") + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVoucherVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVoucherVo.java new file mode 100644 index 00000000..0b13b59c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ChatVoucherVo.java @@ -0,0 +1,87 @@ +package org.ruoyi.system.domain.vo; + +import org.ruoyi.system.domain.ChatVoucher; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 用户兑换记录视图对象 chat_voucher + * + * @author Lion Li + * @date 2024-05-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = ChatVoucher.class) +public class ChatVoucherVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 用户id + */ + @ExcelProperty(value = "用户id") + private Long userId; + + /** + * 用户名称 + */ + @ExcelProperty(value = "用户名称") + private String userName; + + /** + * 兑换码 + */ + @ExcelProperty(value = "兑换码") + private String code; + + /** + * 兑换金额 + */ + @ExcelProperty(value = "兑换金额") + private Double amount; + + /** + * 兑换状态 + */ + @ExcelProperty(value = "兑换状态") + private String status; + + /** + * 兑换前余额 + */ + @ExcelProperty(value = "兑换前余额") + private Double balanceBefore; + + /** + * 兑换后余额 + */ + @ExcelProperty(value = "兑换后余额") + private Double balanceAfter; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + @ExcelProperty(value = "创建时间") + private String createTime; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/DeptTreeSelectVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/DeptTreeSelectVo.java new file mode 100644 index 00000000..fd5b26ee --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/DeptTreeSelectVo.java @@ -0,0 +1,26 @@ +package org.ruoyi.system.domain.vo; + +import cn.hutool.core.lang.tree.Tree; +import lombok.Data; + +import java.util.List; + +/** + * 角色部门列表树信息 + * + * @author Michelle.Chung + */ +@Data +public class DeptTreeSelectVo { + + /** + * 选中部门列表 + */ + private List checkedKeys; + + /** + * 下拉树结构列表 + */ + private List> depts; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginTenantVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginTenantVo.java new file mode 100644 index 00000000..b21a4d9d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginTenantVo.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 登录租户对象 + * + * @author Michelle.Chung + */ +@Data +public class LoginTenantVo { + + /** + * 租户开关 + */ + private Boolean tenantEnabled; + + /** + * 租户对象列表 + */ + private List voList; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginVo.java new file mode 100644 index 00000000..c119136b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/LoginVo.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; +import org.ruoyi.common.core.domain.model.LoginUser; + +/** + * 登录返回信息 + * + * @author Michelle.Chung + */ +@Data +public class LoginVo { + private String token; + private LoginUser userInfo; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MenuTreeSelectVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MenuTreeSelectVo.java new file mode 100644 index 00000000..ace2d56d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MenuTreeSelectVo.java @@ -0,0 +1,26 @@ +package org.ruoyi.system.domain.vo; + +import cn.hutool.core.lang.tree.Tree; +import lombok.Data; + +import java.util.List; + +/** + * 角色菜单列表树信息 + * + * @author Michelle.Chung + */ +@Data +public class MenuTreeSelectVo { + + /** + * 选中菜单列表 + */ + private List checkedKeys; + + /** + * 菜单下拉树结构列表 + */ + private List> menus; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MetaVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MetaVo.java new file mode 100644 index 00000000..a35a5c53 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/MetaVo.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; +import org.ruoyi.common.core.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author ruoyi + */ + +@Data +public class MetaVo { + + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo(String title, String icon) { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) { + this.link = link; + } + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/PaymentOrdersVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/PaymentOrdersVo.java new file mode 100644 index 00000000..ae4a4beb --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/PaymentOrdersVo.java @@ -0,0 +1,93 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.PaymentOrder; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + + +/** + * 支付订单视图对象 payment_orders + * + * @author Lion Li + * @date 2024-04-16 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = PaymentOrder.class) +public class PaymentOrdersVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 订单编号 + */ + @ExcelProperty(value = "订单编号") + private String orderNo; + + /** + * 订单名称 + */ + @ExcelProperty(value = "订单名称") + private String orderName; + + /** + * 金额 + */ + @ExcelProperty(value = "金额") + private BigDecimal amount; + + /** + * 支付状态 + */ + @ExcelProperty(value = "支付状态") + private String paymentStatus; + + /** + * 支付方式 + */ + @ExcelProperty(value = "支付方式") + private String paymentMethod; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户名称") + private String userName; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 二维码网络地址 + */ + private String url; + + /** + * 创建时间 + */ + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ProfileVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ProfileVo.java new file mode 100644 index 00000000..fd40e328 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/ProfileVo.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +/** + * 用户个人信息 + * + * @author Michelle.Chung + */ +@Data +public class ProfileVo { + + /** + * 用户信息 + */ + private SysUserVo user; + + /** + * 用户所属角色组 + */ + private String roleGroup; + + /** + * 用户所属岗位组 + */ + private String postGroup; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/RouterVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/RouterVo.java new file mode 100644 index 00000000..c48ad83b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/RouterVo.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * 路由配置信息 + * + * @author Lion Li + */ +@Data +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo { + + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysConfigVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysConfigVo.java new file mode 100644 index 00000000..1974c05d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysConfigVo.java @@ -0,0 +1,72 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysConfig; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 参数配置视图对象 sys_config + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysConfig.class) +public class SysConfigVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 参数主键 + */ + @ExcelProperty(value = "参数主键") + private Long configId; + + /** + * 参数名称 + */ + @ExcelProperty(value = "参数名称") + private String configName; + + /** + * 参数键名 + */ + @ExcelProperty(value = "参数键名") + private String configKey; + + /** + * 参数键值 + */ + @ExcelProperty(value = "参数键值") + private String configValue; + + /** + * 系统内置(Y是 N否) + */ + @ExcelProperty(value = "系统内置", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_yes_no") + private String configType; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDeptVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDeptVo.java new file mode 100644 index 00000000..49764a7d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDeptVo.java @@ -0,0 +1,91 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysDept; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 部门视图对象 sys_dept + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysDept.class) +public class SysDeptVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 部门id + */ + @ExcelProperty(value = "部门id") + private Long deptId; + + /** + * 父部门id + */ + private Long parentId; + + /** + * 父部门名称 + */ + private String parentName; + + /** + * 祖级列表 + */ + private String ancestors; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 显示顺序 + */ + private Integer orderNum; + + /** + * 负责人 + */ + @ExcelProperty(value = "负责人") + private String leader; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String phone; + + /** + * 邮箱 + */ + @ExcelProperty(value = "邮箱") + private String email; + + /** + * 部门状态(0正常 1停用) + */ + @ExcelProperty(value = "部门状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictDataVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictDataVo.java new file mode 100644 index 00000000..4d5edff8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictDataVo.java @@ -0,0 +1,95 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysDictData; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 字典数据视图对象 sys_dict_data + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysDictData.class) +public class SysDictDataVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字典编码 + */ + @ExcelProperty(value = "字典编码") + private Long dictCode; + + /** + * 字典排序 + */ + @ExcelProperty(value = "字典排序") + private Integer dictSort; + + /** + * 字典标签 + */ + @ExcelProperty(value = "字典标签") + private String dictLabel; + + /** + * 字典键值 + */ + @ExcelProperty(value = "字典键值") + private String dictValue; + + /** + * 字典类型 + */ + @ExcelProperty(value = "字典类型") + private String dictType; + + /** + * 样式属性(其他样式扩展) + */ + private String cssClass; + + /** + * 表格回显样式 + */ + private String listClass; + + /** + * 是否默认(Y是 N否) + */ + @ExcelProperty(value = "是否默认", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_yes_no") + private String isDefault; + + /** + * 状态(0正常 1停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictTypeVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictTypeVo.java new file mode 100644 index 00000000..6763612a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysDictTypeVo.java @@ -0,0 +1,66 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysDictType; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 字典类型视图对象 sys_dict_type + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysDictType.class) +public class SysDictTypeVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字典主键 + */ + @ExcelProperty(value = "字典主键") + private Long dictId; + + /** + * 字典名称 + */ + @ExcelProperty(value = "字典名称") + private String dictName; + + /** + * 字典类型 + */ + @ExcelProperty(value = "字典类型") + private String dictType; + + /** + * 状态(0正常 1停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysLogininforVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysLogininforVo.java new file mode 100644 index 00000000..6ae05ea0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysLogininforVo.java @@ -0,0 +1,93 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysLogininfor; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 系统访问记录视图对象 sys_logininfor + * + * @author Michelle.Chung + * @date 2023-02-07 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysLogininfor.class) +public class SysLogininforVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 访问ID + */ + @ExcelProperty(value = "序号") + private Long infoId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 用户账号 + */ + @ExcelProperty(value = "用户账号") + private String userName; + + /** + * 登录状态(0成功 1失败) + */ + @ExcelProperty(value = "登录状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_common_status") + private String status; + + /** + * 登录IP地址 + */ + @ExcelProperty(value = "登录地址") + private String ipaddr; + + /** + * 登录地点 + */ + @ExcelProperty(value = "登录地点") + private String loginLocation; + + /** + * 浏览器类型 + */ + @ExcelProperty(value = "浏览器") + private String browser; + + /** + * 操作系统 + */ + @ExcelProperty(value = "操作系统") + private String os; + + + /** + * 提示消息 + */ + @ExcelProperty(value = "提示消息") + private String msg; + + /** + * 访问时间 + */ + @ExcelProperty(value = "访问时间") + private Date loginTime; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysMenuVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysMenuVo.java new file mode 100644 index 00000000..4a835e97 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysMenuVo.java @@ -0,0 +1,116 @@ +package org.ruoyi.system.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.SysMenu; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + +/** + * 菜单权限视图对象 sys_menu + * + * @author Michelle.Chung + */ +@Data +@AutoMapper(target = SysMenu.class) +public class SysMenuVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 菜单ID + */ + private Long menuId; + + /** + * 菜单名称 + */ + private String menuName; + + /** + * 父菜单ID + */ + private Long parentId; + + /** + * 显示顺序 + */ + private Integer orderNum; + + /** + * 路由地址 + */ + private String path; + + /** + * 组件路径 + */ + private String component; + + /** + * 路由参数 + */ + private String queryParam; + + /** + * 是否为外链(0是 1否) + */ + private String isFrame; + + /** + * 是否缓存(0缓存 1不缓存) + */ + private String isCache; + + /** + * 菜单类型(M目录 C菜单 F按钮) + */ + private String menuType; + + /** + * 显示状态(0显示 1隐藏) + */ + private String visible; + + /** + * 菜单状态(0正常 1停用) + */ + private String status; + + /** + * 权限标识 + */ + private String perms; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 子菜单 + */ + private List children = new ArrayList<>(); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysModelVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysModelVo.java new file mode 100644 index 00000000..8741522e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysModelVo.java @@ -0,0 +1,85 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.SysModel; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 系统模型视图对象 sys_model + * + * @author Lion Li + * @date 2024-04-04 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysModel.class) +public class SysModelVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 模型名称 + */ + @ExcelProperty(value = "模型名称") + private String modelName; + + + /** + * 模型描述 + */ + @ExcelProperty(value = "模型描述") + private String modelDescribe; + + /** + * 模型价格 + */ + @ExcelProperty(value = "模型价格") + private double modelPrice; + + /** + * 计费类型 + */ + @ExcelProperty(value = "计费类型") + private String modelType; + + /** + * 是否显示 + */ + private String modelShow; + + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 请求地址 + */ + private String apiHost; + + /** + * 模型名称 + */ + private String apiKey; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeStateVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeStateVo.java new file mode 100644 index 00000000..184425e4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeStateVo.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysNoticeState; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 用户阅读状态视图对象 sys_notice_state + * + * @author Lion Li + * @date 2024-05-11 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysNoticeState.class) +public class SysNoticeStateVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @ExcelProperty(value = "ID") + private Long id; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 公告ID + */ + @ExcelProperty(value = "公告ID") + private Long noticeId; + + /** + * 阅读状态(0未读 1已读) + */ + @ExcelProperty(value = "阅读状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=未读,1=已读") + private String readStatus; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeVo.java new file mode 100644 index 00000000..d9c82294 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysNoticeVo.java @@ -0,0 +1,73 @@ +package org.ruoyi.system.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.translation.annotation.Translation; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.system.domain.SysNotice; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 通知公告视图对象 sys_notice + * + * @author Michelle.Chung + */ +@Data +@AutoMapper(target = SysNotice.class) +public class SysNoticeVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 公告ID + */ + private Long noticeId; + + /** + * 公告标题 + */ + private String noticeTitle; + + /** + * 公告类型(1通知 2公告) + */ + private String noticeType; + + /** + * 公告内容 + */ + private String noticeContent; + + /** + * 公告状态(0正常 1关闭) + */ + private String status; + + /** + * 备注 + */ + private String remark; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建人名称 + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + /** + * 创建时间 + */ + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOperLogVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOperLogVo.java new file mode 100644 index 00000000..9ae168cc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOperLogVo.java @@ -0,0 +1,144 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysOperLog; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 操作日志记录视图对象 sys_oper_log + * + * @author Michelle.Chung + * @date 2023-02-07 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysOperLog.class) +public class SysOperLogVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 日志主键 + */ + @ExcelProperty(value = "日志主键") + private Long operId; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 模块标题 + */ + @ExcelProperty(value = "操作模块") + private String title; + + /** + * 业务类型(0其它 1新增 2修改 3删除) + */ + @ExcelProperty(value = "业务类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_oper_type") + private Integer businessType; + + /** + * 业务类型数组 + */ + private Integer[] businessTypes; + + /** + * 方法名称 + */ + @ExcelProperty(value = "请求方法") + private String method; + + /** + * 请求方式 + */ + @ExcelProperty(value = "请求方式") + private String requestMethod; + + /** + * 操作类别(0其它 1后台用户 2手机端用户) + */ + @ExcelProperty(value = "操作类别", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** + * 操作人员 + */ + @ExcelProperty(value = "操作人员") + private String operName; + + /** + * 部门名称 + */ + @ExcelProperty(value = "部门名称") + private String deptName; + + /** + * 请求URL + */ + @ExcelProperty(value = "请求地址") + private String operUrl; + + /** + * 主机地址 + */ + @ExcelProperty(value = "操作地址") + private String operIp; + + /** + * 操作地点 + */ + @ExcelProperty(value = "操作地点") + private String operLocation; + + /** + * 请求参数 + */ + @ExcelProperty(value = "请求参数") + private String operParam; + + /** + * 返回参数 + */ + @ExcelProperty(value = "返回参数") + private String jsonResult; + + /** + * 操作状态(0正常 1异常) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_common_status") + private Integer status; + + /** + * 错误消息 + */ + @ExcelProperty(value = "错误消息") + private String errorMsg; + + /** + * 操作时间 + */ + @ExcelProperty(value = "操作时间") + private Date operTime; + + /** + * 消耗时间 + */ + @ExcelProperty(value = "消耗时间") + private Long costTime; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssConfigVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssConfigVo.java new file mode 100644 index 00000000..2c1cf033 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssConfigVo.java @@ -0,0 +1,97 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import org.ruoyi.system.domain.SysOssConfig; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 对象存储配置视图对象 sys_oss_config + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysOssConfig.class) +public class SysOssConfigVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主建 + */ + private Long ossConfigId; + + /** + * 配置key + */ + private String configKey; + + /** + * accessKey + */ + private String accessKey; + + /** + * 秘钥 + */ + private String secretKey; + + /** + * 桶名称 + */ + private String bucketName; + + /** + * 前缀 + */ + private String prefix; + + /** + * 访问站点 + */ + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 是否https(Y=是,N=否) + */ + private String isHttps; + + /** + * 域 + */ + private String region; + + /** + * 是否默认(0=是,1=否) + */ + private String status; + + /** + * 扩展字段 + */ + private String ext1; + + /** + * 备注 + */ + private String remark; + + /** + * 桶权限类型(0private 1public 2custom) + */ + private String accessPolicy; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssUploadVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssUploadVo.java new file mode 100644 index 00000000..dbfdb171 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssUploadVo.java @@ -0,0 +1,28 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +/** + * 上传对象信息 + * + * @author Michelle.Chung + */ +@Data +public class SysOssUploadVo { + + /** + * URL地址 + */ + private String url; + + /** + * 文件名 + */ + private String fileName; + + /** + * 对象存储主键 + */ + private String ossId; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssVo.java new file mode 100644 index 00000000..cc2c8ee0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysOssVo.java @@ -0,0 +1,72 @@ +package org.ruoyi.system.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.translation.annotation.Translation; +import org.ruoyi.common.translation.constant.TransConstant; +import org.ruoyi.system.domain.SysOss; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * OSS对象存储视图对象 sys_oss + * + * @author Lion Li + */ +@Data +@AutoMapper(target = SysOss.class) +public class SysOssVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 对象存储主键 + */ + private Long ossId; + + /** + * 文件名 + */ + private String fileName; + + /** + * 原名 + */ + private String originalName; + + /** + * 文件后缀名 + */ + private String fileSuffix; + + /** + * URL地址 + */ + private String url; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 上传人 + */ + private Long createBy; + + /** + * 上传人名称 + */ + @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy") + private String createByName; + + /** + * 服务商 + */ + private String service; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPackagePlanVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPackagePlanVo.java new file mode 100644 index 00000000..5ae53f6b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPackagePlanVo.java @@ -0,0 +1,65 @@ +package org.ruoyi.system.domain.vo; + +import java.math.BigDecimal; +import org.ruoyi.system.domain.SysPackagePlan; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 套餐管理视图对象 sys_package_plan + * + * @author Lion Li + * @date 2024-05-05 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysPackagePlan.class) +public class SysPackagePlanVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 套餐名称 + */ + @ExcelProperty(value = "套餐名称") + private String name; + + /** + * 套餐价格 + */ + @ExcelProperty(value = "套餐价格") + private BigDecimal price; + + /** + * 有效时间 + */ + @ExcelProperty(value = "有效时间") + private Long duration; + + /** + * 计划详情 + */ + @ExcelProperty(value = "计划详情") + private String planDetail; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPostVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPostVo.java new file mode 100644 index 00000000..f811e081 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysPostVo.java @@ -0,0 +1,73 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysPost; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 岗位信息视图对象 sys_post + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysPost.class) +public class SysPostVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 岗位ID + */ + @ExcelProperty(value = "岗位序号") + private Long postId; + + /** + * 岗位编码 + */ + @ExcelProperty(value = "岗位编码") + private String postCode; + + /** + * 岗位名称 + */ + @ExcelProperty(value = "岗位名称") + private String postName; + + /** + * 显示顺序 + */ + @ExcelProperty(value = "岗位排序") + private Integer postSort; + + /** + * 状态(0正常 1停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysRoleVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysRoleVo.java new file mode 100644 index 00000000..811d2bcb --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysRoleVo.java @@ -0,0 +1,100 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysRole; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 角色信息视图对象 sys_role + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysRole.class) +public class SysRoleVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色ID + */ + @ExcelProperty(value = "角色序号") + private Long roleId; + + /** + * 角色名称 + */ + @ExcelProperty(value = "角色名称") + private String roleName; + + /** + * 角色权限字符串 + */ + @ExcelProperty(value = "角色权限") + private String roleKey; + + /** + * 显示顺序 + */ + @ExcelProperty(value = "角色排序") + private Integer roleSort; + + /** + * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限) + */ + @ExcelProperty(value = "数据范围", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** + * 菜单树选择项是否关联显示 + */ + @ExcelProperty(value = "菜单树选择项是否关联显示") + private Boolean menuCheckStrictly; + + /** + * 部门树选择项是否关联显示 + */ + @ExcelProperty(value = "部门树选择项是否关联显示") + private Boolean deptCheckStrictly; + + /** + * 角色状态(0正常 1停用) + */ + @ExcelProperty(value = "角色状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 用户是否存在此角色标识 默认不存在 + */ + private boolean flag = false; + + public boolean isSuperAdmin() { + return UserConstants.SUPER_ADMIN_ID.equals(this.roleId); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantPackageVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantPackageVo.java new file mode 100644 index 00000000..36322fe9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantPackageVo.java @@ -0,0 +1,66 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysTenantPackage; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 租户套餐视图对象 sys_tenant_package + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysTenantPackage.class) +public class SysTenantPackageVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 租户套餐id + */ + @ExcelProperty(value = "租户套餐id") + private Long packageId; + + /** + * 套餐名称 + */ + @ExcelProperty(value = "套餐名称") + private String packageName; + + /** + * 关联菜单id + */ + @ExcelProperty(value = "关联菜单id") + private String menuIds; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 菜单树选择项是否关联显示 + */ + @ExcelProperty(value = "菜单树选择项是否关联显示") + private Boolean menuCheckStrictly; + + /** + * 状态(0正常 1停用) + */ + @ExcelProperty(value = "状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=正常,1=停用") + private String status; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantVo.java new file mode 100644 index 00000000..82c33a28 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysTenantVo.java @@ -0,0 +1,115 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.SysTenant; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + +/** + * 租户视图对象 sys_tenant + * + * @author Michelle.Chung + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysTenant.class) +public class SysTenantVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @ExcelProperty(value = "id") + private Long id; + + /** + * 租户编号 + */ + @ExcelProperty(value = "租户编号") + private String tenantId; + + /** + * 联系人 + */ + @ExcelProperty(value = "联系人") + private String contactUserName; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String contactPhone; + + /** + * 企业名称 + */ + @ExcelProperty(value = "企业名称") + private String companyName; + + /** + * 统一社会信用代码 + */ + @ExcelProperty(value = "统一社会信用代码") + private String licenseNumber; + + /** + * 地址 + */ + @ExcelProperty(value = "地址") + private String address; + + /** + * 域名 + */ + @ExcelProperty(value = "域名") + private String domain; + + /** + * 企业简介 + */ + @ExcelProperty(value = "企业简介") + private String intro; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 租户套餐编号 + */ + @ExcelProperty(value = "租户套餐编号") + private Long packageId; + + /** + * 过期时间 + */ + @ExcelProperty(value = "过期时间") + private Date expireTime; + + /** + * 用户数量(-1不限制) + */ + @ExcelProperty(value = "用户数量") + private Long accountCount; + + /** + * 租户状态(0正常 1停用) + */ + @ExcelProperty(value = "租户状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=正常,1=停用") + private String status; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserExportVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserExportVo.java new file mode 100644 index 00000000..4f7ca5f9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserExportVo.java @@ -0,0 +1,72 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 用户对象导出VO + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +@AutoMapper(target = SysUserVo.class, convertGenerate = false) +public class SysUserExportVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户序号") + private Long userId; + + /** + * 用户账号 + */ + @ExcelProperty(value = "用户账号") + private String userName; + + /** + * 用户昵称 + */ + @ExcelProperty(value = "用户名称") + private String nickName; + + /** + * 帐号状态(0正常 1停用) + */ + @ExcelProperty(value = "帐号状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 用户余额 + */ + @ExcelProperty(value = "用户余额") + private Double userBalance; + + /** + * 用户等级 + */ + @ExcelProperty(value = "用户等级") + @ExcelDictFormat(dictType = "sys_user_grade") + private String userGrade; + + /** + * 最后登录时间 + */ + @ExcelProperty(value = "最后登录时间") + private Date loginDate; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserGroupVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserGroupVo.java new file mode 100644 index 00000000..5dd8dd2f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserGroupVo.java @@ -0,0 +1,52 @@ +package org.ruoyi.system.domain.vo; + +import org.ruoyi.system.domain.SysUserGroup; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 【请填写功能名称】视图对象 sys_user_group + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysUserGroup.class) +public class SysUserGroupVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 用户组名称 + */ + @ExcelProperty(value = "用户组名称") + private String groupName; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 更新IP + */ + @ExcelProperty(value = "更新IP") + private String updateIp; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserImportVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserImportVo.java new file mode 100644 index 00000000..4995ea84 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserImportVo.java @@ -0,0 +1,41 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 用户对象导入VO + * + * @author Lion Li + */ + +@Data +@NoArgsConstructor +public class SysUserImportVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户名称 + */ + @ExcelProperty(value = "用户名称") + private String userName; + + /** + * 用户余额 + */ + @ExcelProperty(value = "用户余额") + private Double userBalance; + + /** + * 用户等级 + */ + @ExcelProperty(value = "用户等级(0免费用户 1付费用户)") + private String userGrade; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserInfoVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserInfoVo.java new file mode 100644 index 00000000..f827ef84 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserInfoVo.java @@ -0,0 +1,40 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 用户信息 + * + * @author Michelle.Chung + */ +@Data +public class SysUserInfoVo { + + /** + * 用户信息 + */ + private SysUserVo user; + + /** + * 角色ID列表 + */ + private List roleIds; + + /** + * 角色列表 + */ + private List roles; + + /** + * 岗位ID列表 + */ + private List postIds; + + /** + * 岗位列表 + */ + private List posts; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserModelVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserModelVo.java new file mode 100644 index 00000000..1e95de2b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserModelVo.java @@ -0,0 +1,46 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.SysUserModel; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 【请填写功能名称】视图对象 sys_user_model + * + * @author Lion Li + * @date 2024-08-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = SysUserModel.class) +public class SysUserModelVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @ExcelProperty(value = "id") + private Long id; + + /** + * 模型id + */ + @ExcelProperty(value = "模型id") + private Long mid; + + /** + * 用户组id + */ + @ExcelProperty(value = "用户组id") + private Long gid; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserOptionVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserOptionVo.java new file mode 100644 index 00000000..0397a2c4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserOptionVo.java @@ -0,0 +1,30 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + +/** + * 用户信息视图对象 sys_user + * + * @author Michelle.Chung + */ +@Data +public class SysUserOptionVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户账号 + */ + private String name; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserVo.java new file mode 100644 index 00000000..fa8eec8b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/SysUserVo.java @@ -0,0 +1,155 @@ +package org.ruoyi.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.SysUser; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + + +/** + * 用户信息视图对象 sys_user + * + * @author Michelle.Chung + */ +@Data +@AutoMapper(target = SysUser.class) +public class SysUserVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户账号 + */ + private String userName; + + /** + * 用户套餐 + */ + private String userPlan; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 用户类型(sys_user系统用户) + */ + private String userType; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String phonenumber; + + /** + * 用户性别(0男 1女 2未知) + */ + private String sex; + + /** + * 头像地址 + */ + private String avatar; + + /** + * 微信头像地址 + */ + private String wxAvatar; + + + /** + * 密码 + */ + @JsonIgnore + @JsonProperty + private String password; + + /** + * 帐号状态(0正常 1停用) + */ + private String status; + + /** + * 最后登录IP + */ + private String loginIp; + + /** + * 最后登录时间 + */ + private Date loginDate; + + /** + * 备注 + */ + private String remark; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 部门对象 + */ + private SysDeptVo dept; + + /** + * 注册域名 + */ + private String domainName; + + /** + * 角色对象 + */ + private List roles; + + /** + * 角色组 + */ + private Long[] roleIds; + + /** + * 岗位组 + */ + private Long[] postIds; + + /** + * 数据权限 当前角色ID + */ + private Long roleId; + + /** 用户等级 */ + private String userGrade; + + /** 用户余额 */ + private Double userBalance; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/TenantListVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/TenantListVo.java new file mode 100644 index 00000000..f9981733 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/TenantListVo.java @@ -0,0 +1,21 @@ +package org.ruoyi.system.domain.vo; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +/** + * 租户列表 + * + * @author Lion Li + */ +@Data +@AutoMapper(target = SysTenantVo.class) +public class TenantListVo { + + private String tenantId; + + private String companyName; + + private String domain; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/UserInfoVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/UserInfoVo.java new file mode 100644 index 00000000..b41c9b64 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/UserInfoVo.java @@ -0,0 +1,30 @@ +package org.ruoyi.system.domain.vo; + +import lombok.Data; + +import java.util.Set; + +/** + * 登录用户信息 + * + * @author Michelle.Chung + */ +@Data +public class UserInfoVo { + + /** + * 用户基本信息 + */ + private SysUserVo user; + + /** + * 菜单权限 + */ + private Set permissions; + + /** + * 角色权限 + */ + private Set roles; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/VoiceRoleVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/VoiceRoleVo.java new file mode 100644 index 00000000..d8430850 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/VoiceRoleVo.java @@ -0,0 +1,77 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.VoiceRole; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 配音角色视图对象 voice_role + * + * @author Lion Li + * @date 2024-03-19 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = VoiceRole.class) +public class VoiceRoleVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + @ExcelProperty(value = "id") + private Long id; + + /** + * 角色名称 + */ + @ExcelProperty(value = "角色名称") + private String name; + + /** + * 角色描述 + */ + @ExcelProperty(value = "角色描述") + private String description; + + /** + * 头像 + */ + @ExcelProperty(value = "头像") + private String avatar; + + /** + * 角色id + */ + @ExcelProperty(value = "角色id") + private String voiceId; + + /** + * 音频地址 + */ + @ExcelProperty(value = "音频地址") + private String fileUrl; + + /** + * 音频预处理(实验性) + */ + @ExcelProperty(value = "音频预处理") + private String preProcess; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobConfigVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobConfigVo.java new file mode 100644 index 00000000..a3102f47 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobConfigVo.java @@ -0,0 +1,84 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; +import org.ruoyi.system.domain.WxRobConfig; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 微信机器人视图对象 wx_rob_config + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = WxRobConfig.class) +public class WxRobConfigVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户名称 + */ + private String userName; + + /** + * 机器人名称 + */ + private String botName; + + /** + * 机器唯一码 + */ + @ExcelProperty(value = "机器唯一码") + private String uniqueKey; + + /** + * 备注 + */ + @ExcelProperty(value = "备注", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "备注") + private String remark; + + /** + * 默认好友回复开关 + */ + @ExcelProperty(value = "默认好友回复开关") + private String defaultFriend; + + /** + * 默认群回复开关 + */ + @ExcelProperty(value = "默认群回复开关") + private String defaultGroup; + + + /** + * 机器启用1禁用0 + */ + @ExcelProperty(value = "机器启用1禁用0") + private String enable; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobKeywordVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobKeywordVo.java new file mode 100644 index 00000000..58d1b429 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobKeywordVo.java @@ -0,0 +1,77 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.WxRobKeyword; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 【请填写功能名称】视图对象 wx_rob_keyword + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = WxRobKeyword.class) +public class WxRobKeywordVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @ExcelProperty(value = "") + private Long id; + + /** + * 机器唯一码 + */ + @ExcelProperty(value = "机器唯一码") + private String uniqueKey; + + /** + * 关键词 + */ + @ExcelProperty(value = "关键词") + private String keyData; + + /** + * 回复内容 + */ + @ExcelProperty(value = "回复内容") + private String valueData; + + /** + * 回复类型 + */ + @ExcelProperty(value = "回复类型") + private String typeData; + + /** + * 目标昵称 + */ + @ExcelProperty(value = "目标昵称") + private String nickName; + + /** + * 群1好友0 + */ + @ExcelProperty(value = "群1好友0") + private Integer toGroup; + + /** + * 启用1禁用0 + */ + @ExcelProperty(value = "启用1禁用0") + private Integer enable; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobRelationVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobRelationVo.java new file mode 100644 index 00000000..c71a0e6c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/vo/WxRobRelationVo.java @@ -0,0 +1,71 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.ruoyi.system.domain.WxRobRelation; + +import java.io.Serial; +import java.io.Serializable; + + + +/** + * 【请填写功能名称】视图对象 wx_rob_relation + * + * @author Lion Li + * @date 2024-05-01 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = WxRobRelation.class) +public class WxRobRelationVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * + */ + @ExcelProperty(value = "") + private Long id; + + /** + * 外接唯一码 + */ + @ExcelProperty(value = "外接唯一码") + private String outKey; + + /** + * 机器唯一码 + */ + @ExcelProperty(value = "机器唯一码") + private String uniqueKey; + + /** + * 目标昵称 + */ + @ExcelProperty(value = "目标昵称") + private String nickName; + + /** + * 群1好友0 + */ + @ExcelProperty(value = "群1好友0") + private Integer toGroup; + + /** + * 启用1禁用0 + */ + @ExcelProperty(value = "启用1禁用0") + private Integer enable; + + /** + * IP白名单 + */ + @ExcelProperty(value = "IP白名单") + private String whiteList; + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/handler/CustomerBigDecimalSerialize.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/handler/CustomerBigDecimalSerialize.java new file mode 100644 index 00000000..d6996e43 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/handler/CustomerBigDecimalSerialize.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.handler; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Objects; + +public class CustomerBigDecimalSerialize extends JsonSerializer { + @Override + public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if(Objects.nonNull(value)) { + //返回到前端的数据为数字类型,前端接收有可能丢失精度 + //gen.writeNumber(value.stripTrailingZeros()); + //返回到前端的数据为字符串类型 + gen.writeString(value.stripTrailingZeros().toPlainString()); + //去除0后缀,如果想统一进行保留精度,也可以采用类似处理 + }else {//如果为null的话,就写null + gen.writeNull(); + } + } +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java new file mode 100644 index 00000000..7c35d578 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java @@ -0,0 +1,142 @@ +package org.ruoyi.system.listener; + + +import cn.hutool.core.collection.CollectionUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.ruoyi.common.chat.config.LocalCache; +import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; +import org.ruoyi.common.chat.utils.TikTokensUtil; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.service.IChatCostService; +import org.ruoyi.system.service.IChatMessageService; +import org.ruoyi.system.service.ISysModelService; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; + +import java.util.List; +import java.util.Objects; + +/** + * 描述:OpenAIEventSourceListener + * + * @author https:www.unfbx.com + * @date 2023-02-22 + */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SSEEventSourceListener extends EventSourceListener { + + private ResponseBodyEmitter emitter; + + private StringBuilder stringBuffer = new StringBuilder(); + + @Autowired(required = false) + public SSEEventSourceListener(ResponseBodyEmitter emitter) { + this.emitter = emitter; + } + private static final ISysModelService sysModelService = SpringUtils.getBean(ISysModelService.class); + private String modelName; + /** + * {@inheritDoc} + */ + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("OpenAI建立sse连接..."); + } + + /** + * {@inheritDoc} + */ + @SneakyThrows + @Override + public void onEvent(@NotNull EventSource eventSource, String id, String type, String data) { + try { + if (data.equals("[DONE]")) { + //成功响应 + emitter.complete(); + if(StringUtils.isNotEmpty(modelName)){ + IChatCostService IChatCostService = SpringUtils.context().getBean(IChatCostService.class); + IChatMessageService chatMessageService = SpringUtils.context().getBean(IChatMessageService.class); + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setModelName(modelName); + chatMessageBo.setContent(stringBuffer.toString()); + Long userId = (Long)LocalCache.CACHE.get("userId"); + if(userId == null){ + return; + } + chatMessageBo.setUserId(userId); + //查询按次数扣费的模型 + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelType("2"); + sysModelBo.setModelName(modelName); + List sysModelList = sysModelService.queryList(sysModelBo); + if (CollectionUtil.isNotEmpty(sysModelList)){ + chatMessageBo.setDeductCost(0d); + chatMessageBo.setRemark("提问时扣费"); + // 保存消息记录 + chatMessageService.insertByBo(chatMessageBo); + }else{ + int tokens = TikTokensUtil.tokens(modelName,stringBuffer.toString()); + chatMessageBo.setTotalTokens(tokens); + // 按token扣费并且保存消息记录 + IChatCostService.deductToken(chatMessageBo); + } + } + return; + } + // 解析返回内容 + ObjectMapper mapper = new ObjectMapper(); + ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); + if(completionResponse == null || CollectionUtil.isEmpty(completionResponse.getChoices())){ + return; + } + String content = "completionResponse.getChoices().get(0).getDelta().getContent()"; + if(StringUtils.isEmpty(content)){ + return; + } + if(StringUtils.isEmpty(modelName)){ + modelName = completionResponse.getModel(); + } + stringBuffer.append(content); + emitter.send(data); + } catch (Exception e) { + log.error("sse信息推送失败{}内容:{}",e.getMessage(),data); + eventSource.cancel(); + } + } + + @Override + public void onClosed(EventSource eventSource) { + log.info("OpenAI关闭sse连接..."); + } + + @SneakyThrows + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + if (Objects.isNull(response)) { + return; + } + ResponseBody body = response.body(); + if (Objects.nonNull(body)) { + log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t); + } else { + log.error("OpenAI sse连接异常data:{},异常:{}", response, t); + } + eventSource.cancel(); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SysUserImportListener.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SysUserImportListener.java new file mode 100644 index 00000000..455a5f67 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SysUserImportListener.java @@ -0,0 +1,123 @@ +package org.ruoyi.system.listener; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.digest.BCrypt; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.ValidatorUtils; +import org.ruoyi.common.excel.core.ExcelListener; +import org.ruoyi.common.excel.core.ExcelResult; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysUserImportVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 系统用户自定义导入 + * + * @author Lion Li + */ +@Slf4j +public class SysUserImportListener extends AnalysisEventListener implements ExcelListener { + + private final ISysUserService userService; + + private final String password; + + private final Boolean isUpdateSupport; + + private final Long operUserId; + + private int successNum = 0; + private int failureNum = 0; + private final StringBuilder successMsg = new StringBuilder(); + private final StringBuilder failureMsg = new StringBuilder(); + + public SysUserImportListener(Boolean isUpdateSupport) { + // String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword"); + this.userService = SpringUtils.getBean(ISysUserService.class); + this.password = BCrypt.hashpw("123456"); + this.isUpdateSupport = isUpdateSupport; + this.operUserId = LoginHelper.getUserId(); + } + + @Override + public void invoke(SysUserImportVo userVo, AnalysisContext context) { + SysUserVo sysUser = this.userService.selectUserByUserName(userVo.getUserName()); + try { + // 验证是否存在这个用户 + if (ObjectUtil.isNull(sysUser)) { + SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class); + ValidatorUtils.validate(user); + if(StringUtils.isEmpty(user.getNickName())){ + user.setNickName(user.getUserName()); + } + user.setDeptId(103L); + user.setPassword(password); + user.setCreateBy(operUserId); + userService.insertUser(user); + successNum++; + successMsg.append("
    ").append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功"); + } else if (isUpdateSupport) { + Long userId = sysUser.getUserId(); + SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class); + user.setUserId(userId); + ValidatorUtils.validate(user); + userService.checkUserAllowed(user.getUserId()); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(operUserId); + userService.updateUser(user); + successNum++; + successMsg.append("
    ").append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功"); + } else { + failureNum++; + failureMsg.append("
    ").append(failureNum).append("、账号 ").append(sysUser.getUserName()).append(" 已存在"); + } + } catch (Exception e) { + failureNum++; + String msg = "
    " + failureNum + "、账号 " + sysUser.getUserName() + " 导入失败:"; + failureMsg.append(msg).append(e.getMessage()); + log.error(msg, e); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + + } + + @Override + public ExcelResult getExcelResult() { + return new ExcelResult<>() { + + @Override + public String getAnalysis() { + if (failureNum > 0) { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } else { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } + + @Override + public List getList() { + return null; + } + + @Override + public List getErrorList() { + return null; + } + }; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatConfigMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatConfigMapper.java new file mode 100644 index 00000000..d230e9a4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatConfigMapper.java @@ -0,0 +1,17 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.ChatConfig; +import org.ruoyi.system.domain.vo.ChatConfigVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 对话配置信息 +Mapper接口 + * + * @author Lion Li + * @date 2024-04-13 + */ +public interface ChatConfigMapper extends BaseMapperPlus { + + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatGptsMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatGptsMapper.java new file mode 100644 index 00000000..75555a60 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatGptsMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.ChatGpts; +import org.ruoyi.system.domain.vo.ChatGptsVo; + +/** + * gpts管理Mapper接口 + * + * @author Lion Li + * @date 2024-07-09 + */ +public interface ChatGptsMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatMessageMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatMessageMapper.java new file mode 100644 index 00000000..a58d611d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatMessageMapper.java @@ -0,0 +1,16 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.ChatMessage; +import org.ruoyi.system.domain.vo.ChatMessageVo; + + +/** + * 聊天消息Mapper接口 + * + * @author Lion Li + * @date 2023-11-26 + */ +public interface ChatMessageMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatTokenMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatTokenMapper.java new file mode 100644 index 00000000..5bd62065 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatTokenMapper.java @@ -0,0 +1,16 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.ChatToken; +import org.ruoyi.system.domain.vo.ChatTokenVo; + + +/** + * 聊天消息Mapper接口 + * + * @author Lion Li + * @date 2023-11-26 + */ +public interface ChatTokenMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVisitorUsageMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVisitorUsageMapper.java new file mode 100644 index 00000000..7b4a8fc5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVisitorUsageMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.ChatVisitorUsage; +import org.ruoyi.system.domain.vo.ChatVisitorUsageVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 访客管理Mapper接口 + * + * @author Lion Li + * @date 2024-07-14 + */ +public interface ChatVisitorUsageMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVoucherMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVoucherMapper.java new file mode 100644 index 00000000..490270dc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/ChatVoucherMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.ChatVoucher; +import org.ruoyi.system.domain.vo.ChatVoucherVo; + +/** + * 用户兑换记录Mapper接口 + * + * @author Lion Li + * @date 2024-05-03 + */ +public interface ChatVoucherMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/PaymentOrdersMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/PaymentOrdersMapper.java new file mode 100644 index 00000000..67e404a8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/PaymentOrdersMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.PaymentOrder; +import org.ruoyi.system.domain.vo.PaymentOrdersVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 支付订单Mapper接口 + * + * @author Lion Li + * @date 2024-04-16 + */ +public interface PaymentOrdersMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysConfigMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysConfigMapper.java new file mode 100644 index 00000000..615b558a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysConfigMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysConfig; +import org.ruoyi.system.domain.vo.SysConfigVo; + +/** + * 参数配置 数据层 + * + * @author Lion Li + */ +public interface SysConfigMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDeptMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDeptMapper.java new file mode 100644 index 00000000..14b22d48 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDeptMapper.java @@ -0,0 +1,46 @@ +package org.ruoyi.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysDept; +import org.ruoyi.system.domain.vo.SysDeptVo; + +import java.util.List; + +/** + * 部门管理 数据层 + * + * @author Lion Li + */ +public interface SysDeptMapper extends BaseMapperPlus { + + /** + * 查询部门管理数据 + * + * @param queryWrapper 查询条件 + * @return 部门信息集合 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id") + }) + List selectDeptList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + @DataPermission({ + @DataColumn(key = "deptName", value = "dept_id") + }) + SysDeptVo selectDeptById(Long deptId); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictDataMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictDataMapper.java new file mode 100644 index 00000000..de80b697 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictDataMapper.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysDictData; +import org.ruoyi.system.domain.vo.SysDictDataVo; + +import java.util.List; + +/** + * 字典表 数据层 + * + * @author Lion Li + */ +public interface SysDictDataMapper extends BaseMapperPlus { + + default List selectDictDataByType(String dictType) { + return selectVoList( + new LambdaQueryWrapper() + .eq(SysDictData::getStatus, UserConstants.DICT_NORMAL) + .eq(SysDictData::getDictType, dictType) + .orderByAsc(SysDictData::getDictSort)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictTypeMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictTypeMapper.java new file mode 100644 index 00000000..733f408e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysDictType; +import org.ruoyi.system.domain.vo.SysDictTypeVo; + +/** + * 字典表 数据层 + * + * @author Lion Li + */ +public interface SysDictTypeMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysLogininforMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysLogininforMapper.java new file mode 100644 index 00000000..f01332b8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysLogininforMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysLogininfor; +import org.ruoyi.system.domain.vo.SysLogininforVo; + +/** + * 系统访问日志情况信息 数据层 + * + * @author Lion Li + */ +public interface SysLogininforMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysMenuMapper.java new file mode 100644 index 00000000..c3ed8a29 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysMenuMapper.java @@ -0,0 +1,83 @@ +package org.ruoyi.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysMenu; +import org.ruoyi.system.domain.vo.SysMenuVo; + +import java.util.List; + +/** + * 菜单表 数据层 + * + * @author Lion Li + */ +public interface SysMenuMapper extends BaseMapperPlus { + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param queryWrapper 查询条件 + * @return 菜单列表 + */ + List selectMenuListByUserId(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + List selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + List selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + default List selectMenuTreeAll() { + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .in(SysMenu::getMenuType, UserConstants.TYPE_DIR, UserConstants.TYPE_MENU) + .eq(SysMenu::getStatus, UserConstants.MENU_NORMAL) + .orderByAsc(SysMenu::getParentId) + .orderByAsc(SysMenu::getOrderNum); + return this.selectList(lqw); + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysModelMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysModelMapper.java new file mode 100644 index 00000000..18a1f4de --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysModelMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.SysModel; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 系统模型Mapper接口 + * + * @author Lion Li + * @date 2024-04-04 + */ +public interface SysModelMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeMapper.java new file mode 100644 index 00000000..17f2df5a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysNotice; +import org.ruoyi.system.domain.vo.SysNoticeVo; + +/** + * 通知公告表 数据层 + * + * @author Lion Li + */ +public interface SysNoticeMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeStateMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeStateMapper.java new file mode 100644 index 00000000..f09c47dc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysNoticeStateMapper.java @@ -0,0 +1,19 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.SysNoticeState; +import org.ruoyi.system.domain.vo.SysNoticeStateVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 用户阅读状态Mapper接口 + * + * @author Lion Li + * @date 2024-05-11 + */ +public interface SysNoticeStateMapper extends BaseMapperPlus { + + /** + * 阅读所有公告 + */ + void readAllNotice(); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOperLogMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOperLogMapper.java new file mode 100644 index 00000000..5e0fecfc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOperLogMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysOperLog; +import org.ruoyi.system.domain.vo.SysOperLogVo; + +/** + * 操作日志 数据层 + * + * @author Lion Li + */ +public interface SysOperLogMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssConfigMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssConfigMapper.java new file mode 100644 index 00000000..6da1a3b7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssConfigMapper.java @@ -0,0 +1,16 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysOssConfig; +import org.ruoyi.system.domain.vo.SysOssConfigVo; + +/** + * 对象存储配置Mapper接口 + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ +public interface SysOssConfigMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssMapper.java new file mode 100644 index 00000000..714f4473 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysOssMapper.java @@ -0,0 +1,13 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysOss; +import org.ruoyi.system.domain.vo.SysOssVo; + +/** + * 文件上传 数据层 + * + * @author Lion Li + */ +public interface SysOssMapper extends BaseMapperPlus { +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPackagePlanMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPackagePlanMapper.java new file mode 100644 index 00000000..492cf977 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPackagePlanMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysPackagePlan; +import org.ruoyi.system.domain.vo.SysPackagePlanVo; + +/** + * 套餐管理Mapper接口 + * + * @author Lion Li + * @date 2024-05-05 + */ +public interface SysPackagePlanMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPostMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPostMapper.java new file mode 100644 index 00000000..a7faa8b4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysPostMapper.java @@ -0,0 +1,32 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysPost; +import org.ruoyi.system.domain.vo.SysPostVo; + +import java.util.List; + +/** + * 岗位信息 数据层 + * + * @author Lion Li + */ +public interface SysPostMapper extends BaseMapperPlus { + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + List selectPostsByUserName(String userName); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleDeptMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 00000000..230d9c05 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,13 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author Lion Li + */ +public interface SysRoleDeptMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMapper.java new file mode 100644 index 00000000..dfe71ca4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMapper.java @@ -0,0 +1,68 @@ +package org.ruoyi.system.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysRole; +import org.ruoyi.system.domain.vo.SysRoleVo; + +import java.util.List; + +/** + * 角色表 数据层 + * + * @author Lion Li + */ +public interface SysRoleMapper extends BaseMapperPlus { + + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id") + }) + Page selectPageRoleList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询角色数据 + * + * @param queryWrapper 查询条件 + * @return 角色数据集合信息 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id") + }) + List selectRoleList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id") + }) + SysRoleVo selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + List selectRolePermissionByUserId(Long userId); + + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + List selectRoleListByUserId(Long userId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + List selectRolesByUserName(String userName); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMenuMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 00000000..7a6b273b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,13 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author Lion Li + */ +public interface SysRoleMenuMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantMapper.java new file mode 100644 index 00000000..40263d61 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysTenant; +import org.ruoyi.system.domain.vo.SysTenantVo; + +/** + * 租户Mapper接口 + * + * @author Michelle.Chung + */ +public interface SysTenantMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantPackageMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantPackageMapper.java new file mode 100644 index 00000000..1fbc9a2f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysTenantPackageMapper.java @@ -0,0 +1,14 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysTenantPackage; +import org.ruoyi.system.domain.vo.SysTenantPackageVo; + +/** + * 租户套餐Mapper接口 + * + * @author Michelle.Chung + */ +public interface SysTenantPackageMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserGroupMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserGroupMapper.java new file mode 100644 index 00000000..81410f34 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserGroupMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysUserGroup; +import org.ruoyi.system.domain.vo.SysUserGroupVo; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author Lion Li + * @date 2024-08-03 + */ +public interface SysUserGroupMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java new file mode 100644 index 00000000..66fd0dc6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserMapper.java @@ -0,0 +1,165 @@ +package org.ruoyi.system.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.mybatis.annotation.DataColumn; +import org.ruoyi.common.mybatis.annotation.DataPermission; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户表 数据层 + * + * @author Lion Li + */ +public interface SysUserMapper extends BaseMapperPlus { + + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id"), + @DataColumn(key = "userName", value = "u.user_id") + }) + Page selectPageUserList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询用户列表 + * + * @param queryWrapper 查询条件 + * @return 用户信息集合信息 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id"), + @DataColumn(key = "userName", value = "u.user_id") + }) + List selectUserList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param queryWrapper 查询条件 + * @return 用户信息集合信息 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id"), + @DataColumn(key = "userName", value = "u.user_id") + }) + Page selectAllocatedList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param queryWrapper 查询条件 + * @return 用户信息集合信息 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "d.dept_id"), + @DataColumn(key = "userName", value = "u.user_id") + }) + Page selectUnallocatedList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + SysUserVo selectUserByUserName(String userName); + + /** + * 通过OpenId查询用户 + * + * @param OpenId 微信用户唯一标识 + * @return 用户对象信息 + */ + SysUserVo selectUserByOpenId(String OpenId); + + /** + * 通过手机号查询用户 + * + * @param phonenumber 手机号 + * @return 用户对象信息 + */ + SysUserVo selectUserByPhonenumber(String phonenumber); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + SysUserVo selectUserByEmail(String email); + + /** + * 通过用户名查询用户(不走租户插件) + * + * @param userName 用户名 + * @param tenantId 租户id + * @return 用户对象信息 + */ + @InterceptorIgnore(tenantLine = "true") + SysUserVo selectTenantUserByUserName(String userName, String tenantId); + + /** + * 通过手机号查询用户(不走租户插件) + * + * @param phonenumber 手机号 + * @param tenantId 租户id + * @return 用户对象信息 + */ + @InterceptorIgnore(tenantLine = "true") + SysUserVo selectTenantUserByPhonenumber(String phonenumber, String tenantId); + + /** + * 通过邮箱查询用户(不走租户插件) + * + * @param email 邮箱 + * @param tenantId 租户id + * @return 用户对象信息 + */ + @InterceptorIgnore(tenantLine = "true") + SysUserVo selectTenantUserByEmail(String email, String tenantId); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ +// @DataPermission({ +// @DataColumn(key = "deptName", value = "d.dept_id"), +// @DataColumn(key = "userName", value = "u.user_id") +// }) + @InterceptorIgnore(dataPermission = "true") + SysUserVo selectUserById(Long userId); + + @Override +// @DataPermission({ +// @DataColumn(key = "deptName", value = "dept_id"), +// @DataColumn(key = "userName", value = "user_id") +// }) + @InterceptorIgnore(dataPermission = "true") + int update(@Param(Constants.ENTITY) SysUser user, @Param(Constants.WRAPPER) Wrapper updateWrapper); + + @Override +// @DataPermission({ +// @DataColumn(key = "deptName", value = "dept_id"), +// @DataColumn(key = "userName", value = "user_id") +// }) + @InterceptorIgnore(dataPermission = "true") + int updateById(@Param(Constants.ENTITY) SysUser user); + + + /** + * 小程序 -修改用户信息 + * + * @param user + * + */ + void updateXcxUser(SysUserBo user); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserModelMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserModelMapper.java new file mode 100644 index 00000000..0817e824 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserModelMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysUserModel; +import org.ruoyi.system.domain.vo.SysUserModelVo; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author Lion Li + * @date 2024-08-03 + */ +public interface SysUserModelMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserPostMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserPostMapper.java new file mode 100644 index 00000000..60665ccb --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserPostMapper.java @@ -0,0 +1,13 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author Lion Li + */ +public interface SysUserPostMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserRoleMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserRoleMapper.java new file mode 100644 index 00000000..9e81b611 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,17 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.SysUserRole; + +import java.util.List; + +/** + * 用户与角色关联表 数据层 + * + * @author Lion Li + */ +public interface SysUserRoleMapper extends BaseMapperPlus { + + List selectUserIdsByRoleId(Long roleId); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/VoiceRoleMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/VoiceRoleMapper.java new file mode 100644 index 00000000..bd7e22ed --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/VoiceRoleMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.VoiceRole; +import org.ruoyi.system.domain.vo.VoiceRoleVo; + +/** + * 配音角色Mapper接口 + * + * @author Lion Li + * @date 2024-03-19 + */ +public interface VoiceRoleMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobConfigMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobConfigMapper.java new file mode 100644 index 00000000..9cfc5b19 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobConfigMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.WxRobConfig; +import org.ruoyi.system.domain.vo.WxRobConfigVo; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface WxRobConfigMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobKeywordMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobKeywordMapper.java new file mode 100644 index 00000000..e76ec3ed --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobKeywordMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.system.domain.WxRobKeyword; +import org.ruoyi.system.domain.vo.WxRobKeywordVo; +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface WxRobKeywordMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobRelationMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobRelationMapper.java new file mode 100644 index 00000000..edd32fe6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/mapper/WxRobRelationMapper.java @@ -0,0 +1,15 @@ +package org.ruoyi.system.mapper; + +import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.WxRobRelation; +import org.ruoyi.system.domain.vo.WxRobRelationVo; + +/** + * 【请填写功能名称】Mapper接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface WxRobRelationMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleListDto.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleListDto.java new file mode 100644 index 00000000..d6a85191 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleListDto.java @@ -0,0 +1,24 @@ +package org.ruoyi.system.request; + +import lombok.Data; + +/** + * 描述: + * + * @author ageerle@163.com + * date 2024/4/27 + */ +@Data +public class RoleListDto { + + private String name; + + private String description; + + private String voicesId; + + private String avatar; + + private String previewAudio; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleRequest.java new file mode 100644 index 00000000..d77b1e97 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/RoleRequest.java @@ -0,0 +1,36 @@ +package org.ruoyi.system.request; + +import lombok.Data; + +/** + * @author WangLe + */ +@Data +public class RoleRequest { + + /** + * 角色名称 + */ + private String name; + + /** + * 角色默认风格音频样本,base64 编码的音频数据 + */ + private String prompt; + + /** + * 角色描述 + */ + private String description; + + /** + * 头像 + */ + private String avatar; + + /** + * 专业克隆样本Zip文件的分片上传ID,请先通过分片上传接口完成文件上传 + */ + private String lora; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/SimpleGenerateRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/SimpleGenerateRequest.java new file mode 100644 index 00000000..6a9378ee --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/request/SimpleGenerateRequest.java @@ -0,0 +1,63 @@ +package org.ruoyi.system.request; + +import lombok.Data; + +import java.util.List; + +/** + * @author WangLe + */ +@Data +public class SimpleGenerateRequest { + + /** + * 角色ID + */ + private String voiceId; + + /** + * 要生成的文本内容 + */ + private String text; + + /** + * 要生成的文本内容 优先级高于text + */ + private List texts; + + /** + * 角色风格 ID (默认为default) + */ + private String promptId = "default"; + + /** + * 要使用的模型ID (目前统一为reecho-neural-voice-001) + */ + private String model = "reecho-neural-voice-001"; + + /** + * 多样性 (0-100,默认为97) + */ + private Integer randomness; + + /** + * 稳定性过滤 (0-100,默认为100) + */ + private Integer stability_boost; + + /** + * 概率优选(0-100,默认为99) + */ + private Integer probability_optimization; + + /** + * 是否直接返回生成音频的Base64 DataURL,而不是Url链接(默认为false) + */ + private Boolean origin_audio; + + /** + * 是否启用流式生成 + */ + private Boolean stream; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/MetadataResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/MetadataResponse.java new file mode 100644 index 00000000..ff09e6f5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/MetadataResponse.java @@ -0,0 +1,27 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +import java.util.List; + +/** + * @author WangLe + */ +@Data +public class MetadataResponse { + + /** + * 语音角色头像 URL + */ + private String avatar; + + /** + * 语音角色描述 + */ + private String description; + + /** + * 语音角色风格列表 + */ + private List prompts; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/PromptResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/PromptResponse.java new file mode 100644 index 00000000..000f902f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/PromptResponse.java @@ -0,0 +1,29 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +/** + * 描述:语音角色风格列表 + * + * @author ageerle@163.com + * date 2024/4/27 + */ +@Data +public class PromptResponse { + + /** + * 角色风格 ID + */ + private String id; + + /** + * 角色风格名称 + */ + private String name; + + /** + * 角色风格样本音频 URL + */ + private String promptOriginAudioStorageUrl; + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleDataResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleDataResponse.java new file mode 100644 index 00000000..1e7c67e8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleDataResponse.java @@ -0,0 +1,35 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +/** + * @author WangLe + */ +@Data +public class RoleDataResponse { + /** + * 语音角色 ID + */ + private String id; + + /** + * 音角色名称 + */ + private String name; + + /** + * 语音角色状态,可以为 + * pending(瞬时克隆已完成) + * lora-pending(专业克隆训练中) + * lora-success(专业克隆已完成) + * lora-failed(专业克隆失败) + */ + private String status; + private MetadataResponse metadata; + private String from; + private String originId; + private String createdAt; + private String updatedAt; + private String deletedAt; + private String userId; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleResponse.java new file mode 100644 index 00000000..0f4ea427 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/RoleResponse.java @@ -0,0 +1,22 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +/** + * @author WangLe + */ +@Data +public class RoleResponse { + /** + * 状态码 + */ + private String status; + /** + * 状态信息 + */ + private String message; + /** + * 创建的语音角色详情 + */ + private RoleDataResponse data; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateDataResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateDataResponse.java new file mode 100644 index 00000000..79d904bb --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateDataResponse.java @@ -0,0 +1,30 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +/** + * @author WangLe + */ +@Data +public class SimpleGenerateDataResponse { + + /** + * 本次生成的ID + */ + private String id; + + /** + * 本次生成结果的音频文件地址 + */ + private String audio; + + /** + * 流式MP3端点 + */ + private String streamUrl; + + /** + * 本次生成所消耗的点数 + */ + private Integer credit_used; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateResponse.java new file mode 100644 index 00000000..ac4b3580 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/SimpleGenerateResponse.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.response; + +import lombok.Data; + +/** + * @author WangLe + */ +@Data +public class SimpleGenerateResponse { + + /** + * 状态码,失败时则为500 + */ + private String status; + + /** + * 状态消息 + */ + private String message; + + /** + * 生成详情 + */ + private SimpleGenerateDataResponse data; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/ContentResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/ContentResponse.java new file mode 100644 index 00000000..ea7f7471 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/ContentResponse.java @@ -0,0 +1,78 @@ +package org.ruoyi.system.response.rolelist; + +import lombok.Data; + +/** + * + * 描述:获取当前用户的语音角色列表返回对象 + * + * @author ageerle@163.com + * date 2024/4/27 + */ +import java.util.List; + +@Data +public class ContentResponse { + + /** + * 语音角色 ID + */ + private String id; + + /** + * 语音角色名称 + */ + private String name; + + /** + * 语音角色状态,可以为pending(瞬时克隆已完成)、lora-pending(专业克隆训练中)、lora-success(专业克隆已完成)、lora-failed(专业克隆失败) + */ + private String status; + + + private Metadata metadata; + @Data + public static class Metadata { + + /** + * 语音角色头像 URL + */ + private String avatar; + + /** + * 语音角色描述 + */ + private String description; + + /** + * 语音角色风格列表 + */ + private List prompts; + + private String previewAudio; + + private String promptMP3StorageUrl; + + @Data + public static class prompt { + /** + * 角色风格 ID + */ + private String id; + + /** + * 角色风格名称 + */ + private String name; + + /** + * 角色风格样本音频 URL + */ + private String promptOriginAudioStorageUrl; + + } + + } + +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListResponse.java new file mode 100644 index 00000000..a9857815 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListResponse.java @@ -0,0 +1,24 @@ +package org.ruoyi.system.response.rolelist; + +import lombok.Data; + +import java.util.List; + +/** + * @author WangLe + */ +@Data +public class RoleListResponse { + /** + * 状态码 + */ + private String status; + /** + * 状态信息 + */ + private String message; + /** + * 创建的语音角色详情 + */ + private List data; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListVO.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListVO.java new file mode 100644 index 00000000..cff24721 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/response/rolelist/RoleListVO.java @@ -0,0 +1,32 @@ +package org.ruoyi.system.response.rolelist; + +import lombok.Data; + +/** + * 描述: + * + * @author ageerle@163.com + * date 2024/4/27 + */ +@Data +public class RoleListVO { + + + private String name; + + private String description; + + private String voicesId; + + private String avatar; + + private String previewAudio; + + public RoleListVO(String name, String description, String voicesId, String previewAudio,String avatar) { + this.name = name; + this.description = description; + this.voicesId = voicesId; + this.previewAudio = previewAudio; + this.avatar = avatar; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/runner/SystemApplicationRunner.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/runner/SystemApplicationRunner.java new file mode 100644 index 00000000..13d9670c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/runner/SystemApplicationRunner.java @@ -0,0 +1,28 @@ +package org.ruoyi.system.runner; + +import org.ruoyi.system.service.ISysOssConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + * 初始化 system 模块对应业务数据 + * + * @author Lion Li + */ +@Slf4j +@RequiredArgsConstructor +@Component +public class SystemApplicationRunner implements ApplicationRunner { + + private final ISysOssConfigService ossConfigService; + + @Override + public void run(ApplicationArguments args) throws Exception { + ossConfigService.init(); + log.info("初始化OSS配置成功"); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatConfigService.java new file mode 100644 index 00000000..45e0067a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatConfigService.java @@ -0,0 +1,53 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.ChatConfigBo; +import org.ruoyi.system.domain.vo.ChatConfigVo; + +import java.util.Collection; +import java.util.List; + +/** + * 对话配置信息Service接口 + * @date 2024-04-13 + */ +public interface IChatConfigService { + + /** + * 查询配置信息 + + */ + ChatConfigVo queryById(Long id); + + /** + * 查询配置信息列表 + */ + TableDataInfo queryPageList(ChatConfigBo bo, PageQuery pageQuery); + + /** + * 查询配置信息列表 + */ + List queryList(ChatConfigBo bo); + + /** + * 新增配置信息 + + */ + Boolean insertByBo(ChatConfigBo bo); + + /** + * 修改配置信息 + */ + Boolean updateByBo(ChatConfigBo bo); + + /** + * 校验并批量删除配置信息信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 查询系统参数 + */ + List getSysConfigValue(String category); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatCostService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatCostService.java new file mode 100644 index 00000000..c1174656 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatCostService.java @@ -0,0 +1,37 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.bo.ChatMessageBo; + +public interface IChatCostService { + + /** + * 根据消耗的tokens扣除余额 + * + * @param chatMessageBo + * @return 结果 + */ + + void deductToken(ChatMessageBo chatMessageBo); + + /** + * 扣除用户的余额 + * + */ + void deductUserBalance(Long userId, Double numberCost); + + + /** + * 扣除任务费用并且保存记录 + * + * @param type 任务类型 + * @param prompt 任务描述 + * @param cost 扣除费用 + */ + void taskDeduct(String type,String prompt, double cost); + + + /** + * 判断用户是否付费 + */ + void checkUserGrade(); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatGptsService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatGptsService.java new file mode 100644 index 00000000..7f4bb365 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatGptsService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.ChatGptsVo; +import org.ruoyi.system.domain.bo.ChatGptsBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * gpts管理Service接口 + * + * @author Lion Li + * @date 2024-07-09 + */ +public interface IChatGptsService { + + /** + * 查询gpts管理 + */ + ChatGptsVo queryById(Long id); + + /** + * 查询gpts管理列表 + */ + TableDataInfo queryPageList(ChatGptsBo bo, PageQuery pageQuery); + + /** + * 查询gpts管理列表 + */ + List queryList(ChatGptsBo bo); + + /** + * 新增gpts管理 + */ + Boolean insertByBo(ChatGptsBo bo); + + /** + * 修改gpts管理 + */ + Boolean updateByBo(ChatGptsBo bo); + + /** + * 校验并批量删除gpts管理信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatMessageService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatMessageService.java new file mode 100644 index 00000000..671cdc04 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatMessageService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.vo.ChatMessageVo; + +import java.util.Collection; +import java.util.List; + +/** + * 聊天消息Service接口 + * + * @author Lion Li + * @date 2023-11-26 + */ +public interface IChatMessageService { + + /** + * 查询聊天消息 + */ + ChatMessageVo queryById(Long id); + + /** + * 查询聊天消息列表 + */ + TableDataInfo queryPageList(ChatMessageBo bo, PageQuery pageQuery); + + /** + * 查询聊天消息列表 + */ + List queryList(ChatMessageBo bo); + + /** + * 新增聊天消息 + */ + Boolean insertByBo(ChatMessageBo bo); + + /** + * 修改聊天消息 + */ + Boolean updateByBo(ChatMessageBo bo); + + /** + * 校验并批量删除聊天消息信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatTokenService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatTokenService.java new file mode 100644 index 00000000..c758a0d8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatTokenService.java @@ -0,0 +1,25 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.ChatToken; + +/** + * 聊天消息Service接口 + * + * @author Lion Li + * @date 2023-11-26 + */ +public interface IChatTokenService { + + /** + * 查询用户token + */ + ChatToken queryByUserId(Long userId,String modelName); + + /** + * 清空用户token + */ + void resetToken(Long userId,String modelName); + + void editToken(ChatToken chatToken); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVisitorUsageService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVisitorUsageService.java new file mode 100644 index 00000000..87ab91ce --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVisitorUsageService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.ChatVisitorUsageVo; +import org.ruoyi.system.domain.bo.ChatVisitorUsageBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 访客管理Service接口 + * + * @author Lion Li + * @date 2024-07-14 + */ +public interface IChatVisitorUsageService { + + /** + * 查询访客管理 + */ + ChatVisitorUsageVo queryById(Long id); + + /** + * 查询访客管理列表 + */ + TableDataInfo queryPageList(ChatVisitorUsageBo bo, PageQuery pageQuery); + + /** + * 查询访客管理列表 + */ + List queryList(ChatVisitorUsageBo bo); + + /** + * 新增访客管理 + */ + Boolean insertByBo(ChatVisitorUsageBo bo); + + /** + * 修改访客管理 + */ + Boolean updateByBo(ChatVisitorUsageBo bo); + + /** + * 校验并批量删除访客管理信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVoucherService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVoucherService.java new file mode 100644 index 00000000..4dfb6843 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IChatVoucherService.java @@ -0,0 +1,53 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.ChatVoucherVo; +import org.ruoyi.system.domain.bo.ChatVoucherBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 用户兑换记录Service接口 + * + * @author Lion Li + * @date 2024-05-03 + */ +public interface IChatVoucherService { + + /** + * 查询用户兑换记录 + */ + ChatVoucherVo queryById(Long id); + + /** + * 查询用户兑换记录列表 + */ + TableDataInfo queryPageList(ChatVoucherBo bo, PageQuery pageQuery); + + /** + * 查询用户兑换记录列表 + */ + List queryList(ChatVoucherBo bo); + + /** + * 新增用户兑换记录 + */ + Boolean insertByBo(ChatVoucherBo bo); + + /** + * 修改用户兑换记录 + */ + Boolean updateByBo(ChatVoucherBo bo); + + /** + * 校验并批量删除用户兑换记录信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 兑换卡密 + */ + Boolean redeem(ChatVoucherBo bo); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IPaymentOrdersService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IPaymentOrdersService.java new file mode 100644 index 00000000..1ac29b4b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IPaymentOrdersService.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.PaymentOrdersBo; +import org.ruoyi.system.domain.request.OrderRequest; +import org.ruoyi.system.domain.vo.PaymentOrdersVo; + +import java.util.Collection; +import java.util.List; + +/** + * 支付订单Service接口 + * + * @author Lion Li + * @date 2024-04-16 + */ +public interface IPaymentOrdersService { + + /** + * 查询支付订单 + */ + PaymentOrdersVo queryById(Long id); + + + /** + * 创建支付订单 + */ + PaymentOrdersBo createPayOrder(OrderRequest orderRequest); + + /** + * 修改订单状态为已支付 + * + */ + void updatePayOrder(OrderRequest orderRequest); + + + /** + * 查询支付订单列表 + */ + TableDataInfo queryPageList(PaymentOrdersBo bo, PageQuery pageQuery); + + /** + * 查询支付订单列表 + */ + List queryList(PaymentOrdersBo bo); + + /** + * 新增支付订单 + */ + Boolean insertByBo(PaymentOrdersBo bo); + + /** + * 修改支付订单 + */ + Boolean updateByBo(PaymentOrdersBo bo); + + /** + * 校验并批量删除支付订单信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISseService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISseService.java new file mode 100644 index 00000000..e6f89211 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISseService.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.service; + + +import jakarta.servlet.http.HttpServletRequest; +import org.ruoyi.common.chat.domain.request.ChatRequest; +import org.ruoyi.common.chat.domain.request.Dall3Request; +import org.ruoyi.common.chat.entity.Tts.TextToSpeech; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.images.Item; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * @date 2023-04-08 + */ +public interface ISseService { + + /** + * 客户端发送消息到服务端 + * @param chatRequest + */ + SseEmitter sseChat(ChatRequest chatRequest,HttpServletRequest request); + + /** + * 语音转文字 + * @param file + */ + WhisperResponse speechToTextTranscriptionsV2(MultipartFile file); + + /** + * 文字转语音 + */ + ResponseEntity textToSpeed(TextToSpeech textToSpeech); + + /** + * 客户端发送消息到服务端 + * @param chatRequest + */ + String chat(ChatRequest chatRequest,String userId); + + /** + * 客户端发送消息到服务端 + */ + List wxDall(String prompt,String userId); + + /** + * 绘画接口 + * @param request + */ + List dall3(Dall3Request request); + + + UploadFileResponse upload(MultipartFile file); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysConfigService.java new file mode 100644 index 00000000..17a86814 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysConfigService.java @@ -0,0 +1,87 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysConfigBo; +import org.ruoyi.system.domain.vo.SysConfigVo; + +import java.util.List; + +/** + * 参数配置 服务层 + * + * @author Lion Li + */ +public interface ISysConfigService { + + + TableDataInfo selectPageConfigList(SysConfigBo config, PageQuery pageQuery); + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + SysConfigVo selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + String selectConfigByKey(String configKey); + + /** + * 获取注册开关 + * @param tenantId 租户id + * @return true开启,false关闭 + */ + boolean selectRegisterEnabled(String tenantId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + List selectConfigList(SysConfigBo config); + + /** + * 新增参数配置 + * + * @param bo 参数配置信息 + * @return 结果 + */ + String insertConfig(SysConfigBo bo); + + /** + * 修改参数配置 + * + * @param bo 参数配置信息 + * @return 结果 + */ + String updateConfig(SysConfigBo bo); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + void deleteConfigByIds(Long[] configIds); + + /** + * 重置参数缓存数据 + */ + void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + boolean checkConfigKeyUnique(SysConfigBo config); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDataScopeService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDataScopeService.java new file mode 100644 index 00000000..158d6f4c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDataScopeService.java @@ -0,0 +1,26 @@ +package org.ruoyi.system.service; + +/** + * 通用 数据权限 服务 + * + * @author Lion Li + */ +public interface ISysDataScopeService { + + /** + * 获取角色自定义权限 + * + * @param roleId 角色id + * @return 部门id组 + */ + String getRoleCustom(Long roleId); + + /** + * 获取部门及以下权限 + * + * @param deptId 部门id + * @return 部门id组 + */ + String getDeptAndChild(Long deptId); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDeptService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDeptService.java new file mode 100644 index 00000000..16ea7f7f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDeptService.java @@ -0,0 +1,118 @@ +package org.ruoyi.system.service; + +import cn.hutool.core.lang.tree.Tree; +import org.ruoyi.system.domain.SysDept; +import org.ruoyi.system.domain.bo.SysDeptBo; +import org.ruoyi.system.domain.vo.SysDeptVo; + +import java.util.List; + +/** + * 部门管理 服务层 + * + * @author Lion Li + */ +public interface ISysDeptService { + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + List selectDeptList(SysDeptBo dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + List> selectDeptTreeList(SysDeptBo dept); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + List> buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + List selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + SysDeptVo selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门数(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + long selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + boolean checkDeptNameUnique(SysDeptBo dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param bo 部门信息 + * @return 结果 + */ + int insertDept(SysDeptBo bo); + + /** + * 修改保存部门信息 + * + * @param bo 部门信息 + * @return 结果 + */ + int updateDept(SysDeptBo bo); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + int deleteDeptById(Long deptId); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictDataService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictDataService.java new file mode 100644 index 00000000..bf68174e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictDataService.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysDictDataBo; +import org.ruoyi.system.domain.vo.SysDictDataVo; + +import java.util.List; + +/** + * 字典 业务层 + * + * @author Lion Li + */ +public interface ISysDictDataService { + + + TableDataInfo selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery); + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + List selectDictDataList(SysDictDataBo dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + SysDictDataVo selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param bo 字典数据信息 + * @return 结果 + */ + List insertDictData(SysDictDataBo bo); + + /** + * 修改保存字典数据信息 + * + * @param bo 字典数据信息 + * @return 结果 + */ + List updateDictData(SysDictDataBo bo); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictTypeService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictTypeService.java new file mode 100644 index 00000000..34b1ca67 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysDictTypeService.java @@ -0,0 +1,95 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysDictTypeBo; +import org.ruoyi.system.domain.vo.SysDictDataVo; +import org.ruoyi.system.domain.vo.SysDictTypeVo; + +import java.util.List; + +/** + * 字典 业务层 + * + * @author Lion Li + */ +public interface ISysDictTypeService { + + + TableDataInfo selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery); + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + List selectDictTypeList(SysDictTypeBo dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + SysDictTypeVo selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + SysDictTypeVo selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + void deleteDictTypeByIds(Long[] dictIds); + + /** + * 重置字典缓存数据 + */ + void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param bo 字典类型信息 + * @return 结果 + */ + List insertDictType(SysDictTypeBo bo); + + /** + * 修改保存字典类型信息 + * + * @param bo 字典类型信息 + * @return 结果 + */ + List updateDictType(SysDictTypeBo bo); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + boolean checkDictTypeUnique(SysDictTypeBo dictType); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysLogininforService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysLogininforService.java new file mode 100644 index 00000000..4e5561dc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysLogininforService.java @@ -0,0 +1,47 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysLogininforBo; +import org.ruoyi.system.domain.vo.SysLogininforVo; + +import java.util.List; + +/** + * 系统访问日志情况信息 服务层 + * + * @author Lion Li + */ +public interface ISysLogininforService { + + + TableDataInfo selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery); + + /** + * 新增系统登录日志 + * + * @param bo 访问日志对象 + */ + void insertLogininfor(SysLogininforBo bo); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + List selectLogininforList(SysLogininforBo logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + void cleanLogininfor(); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysMenuService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysMenuService.java new file mode 100644 index 00000000..6fb9187e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysMenuService.java @@ -0,0 +1,147 @@ +package org.ruoyi.system.service; + +import cn.hutool.core.lang.tree.Tree; +import org.ruoyi.system.domain.SysMenu; +import org.ruoyi.system.domain.bo.SysMenuBo; +import org.ruoyi.system.domain.vo.RouterVo; +import org.ruoyi.system.domain.vo.SysMenuVo; + +import java.util.List; +import java.util.Set; + +/** + * 菜单 业务层 + * + * @author Lion Li + */ +public interface ISysMenuService { + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuList(SysMenuBo menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + Set selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + Set selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + List selectMenuListByRoleId(Long roleId); + + /** + * 根据租户套餐ID查询菜单树信息 + * + * @param packageId 租户套餐ID + * @return 选中菜单列表 + */ + List selectMenuListByPackageId(Long packageId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + List buildMenus(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + List> buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + SysMenuVo selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param bo 菜单信息 + * @return 结果 + */ + int insertMenu(SysMenuBo bo); + + /** + * 修改保存菜单信息 + * + * @param bo 菜单信息 + * @return 结果 + */ + int updateMenu(SysMenuBo bo); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + boolean checkMenuNameUnique(SysMenuBo menu); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysModelService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysModelService.java new file mode 100644 index 00000000..3e0a6c36 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysModelService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; + +import java.util.Collection; +import java.util.List; + +/** + * 系统模型Service接口 + * + * @author Lion Li + * @date 2024-04-04 + */ +public interface ISysModelService { + + /** + * 查询系统模型 + */ + SysModelVo queryById(Long id); + + /** + * 查询系统模型列表 + */ + TableDataInfo queryPageList(SysModelBo bo, PageQuery pageQuery); + + /** + * 查询系统模型列表 + */ + List queryList(SysModelBo bo); + + /** + * 新增系统模型 + */ + Boolean insertByBo(SysModelBo bo); + + /** + * 修改系统模型 + */ + Boolean updateByBo(SysModelBo bo); + + /** + * 校验并批量删除系统模型信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeService.java new file mode 100644 index 00000000..e7dbcd52 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeService.java @@ -0,0 +1,68 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysNotice; +import org.ruoyi.system.domain.bo.SysNoticeBo; +import org.ruoyi.system.domain.vo.SysNoticeVo; + +/** + * 公告 服务层 + * + * @author Lion Li + */ +public interface ISysNoticeService { + + + TableDataInfo selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery); + + + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + SysNoticeVo selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + SysNotice getNotice(SysNoticeBo notice); + + /** + * 新增公告 + * + * @param bo 公告信息 + * @return 结果 + */ + int insertNotice(SysNoticeBo bo); + + /** + * 修改公告 + * + * @param bo 公告信息 + * @return 结果 + */ + int updateNotice(SysNoticeBo bo); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeStateService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeStateService.java new file mode 100644 index 00000000..14363ef7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysNoticeStateService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysNoticeStateBo; +import org.ruoyi.system.domain.vo.SysNoticeStateVo; + +import java.util.Collection; +import java.util.List; + +/** + * 用户阅读状态Service接口 + * + * @author Lion Li + * @date 2024-05-11 + */ +public interface ISysNoticeStateService { + + /** + * 查询用户阅读状态 + */ + SysNoticeStateVo queryById(Long id); + + /** + * 查询用户阅读状态列表 + */ + TableDataInfo queryPageList(SysNoticeStateBo bo, PageQuery pageQuery); + + /** + * 查询用户阅读状态列表 + */ + List queryList(SysNoticeStateBo bo); + + /** + * 新增用户阅读状态 + */ + Boolean insertByBo(SysNoticeStateBo bo); + + /** + * 修改用户阅读状态 + */ + Boolean updateByBo(SysNoticeStateBo bo); + + /** + * 校验并批量删除用户阅读状态信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOperLogService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOperLogService.java new file mode 100644 index 00000000..8c5f4bbf --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOperLogService.java @@ -0,0 +1,54 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysOperLogBo; +import org.ruoyi.system.domain.vo.SysOperLogVo; + +import java.util.List; + +/** + * 操作日志 服务层 + * + * @author Lion Li + */ +public interface ISysOperLogService { + + TableDataInfo selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery); + + /** + * 新增操作日志 + * + * @param bo 操作日志对象 + */ + void insertOperlog(SysOperLogBo bo); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + List selectOperLogList(SysOperLogBo operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + SysOperLogVo selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + void cleanOperLog(); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssConfigService.java new file mode 100644 index 00000000..e4e09da8 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssConfigService.java @@ -0,0 +1,65 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysOssConfigBo; +import org.ruoyi.system.domain.vo.SysOssConfigVo; + +import java.util.Collection; + +/** + * 对象存储配置Service接口 + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ +public interface ISysOssConfigService { + + /** + * 初始化OSS配置 + */ + void init(); + + /** + * 查询单个 + */ + SysOssConfigVo queryById(Long ossConfigId); + + /** + * 查询列表 + */ + TableDataInfo queryPageList(SysOssConfigBo bo, PageQuery pageQuery); + + + /** + * 根据新增业务对象插入对象存储配置 + * + * @param bo 对象存储配置新增业务对象 + * @return + */ + Boolean insertByBo(SysOssConfigBo bo); + + /** + * 根据编辑业务对象修改对象存储配置 + * + * @param bo 对象存储配置编辑业务对象 + * @return + */ + Boolean updateByBo(SysOssConfigBo bo); + + /** + * 校验并删除数据 + * + * @param ids 主键集合 + * @param isValid 是否校验,true-删除前校验,false-不校验 + * @return + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 启用停用状态 + */ + int updateOssConfigStatus(SysOssConfigBo bo); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssService.java new file mode 100644 index 00000000..14f00c35 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysOssService.java @@ -0,0 +1,33 @@ +package org.ruoyi.system.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysOssBo; +import org.ruoyi.system.domain.vo.SysOssVo; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +/** + * 文件上传 服务层 + * + * @author Lion Li + */ +public interface ISysOssService { + + TableDataInfo queryPageList(SysOssBo sysOss, PageQuery pageQuery); + + List listByIds(Collection ossIds); + + SysOssVo getById(Long ossId); + + SysOssVo upload(MultipartFile file); + + void download(Long ossId, HttpServletResponse response) throws IOException; + + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPackagePlanService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPackagePlanService.java new file mode 100644 index 00000000..d8bb5814 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPackagePlanService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 套餐管理Service接口 + * + * @author Lion Li + * @date 2024-05-05 + */ +public interface ISysPackagePlanService { + + /** + * 查询套餐管理 + */ + SysPackagePlanVo queryById(Long id); + + /** + * 查询套餐管理列表 + */ + TableDataInfo queryPageList(SysPackagePlanBo bo, PageQuery pageQuery); + + /** + * 查询套餐管理列表 + */ + List queryList(SysPackagePlanBo bo); + + /** + * 新增套餐管理 + */ + Boolean insertByBo(SysPackagePlanBo bo); + + /** + * 修改套餐管理 + */ + Boolean updateByBo(SysPackagePlanBo bo); + + /** + * 校验并批量删除套餐管理信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPermissionService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPermissionService.java new file mode 100644 index 00000000..c76616d9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPermissionService.java @@ -0,0 +1,28 @@ +package org.ruoyi.system.service; + +import java.util.Set; + +/** + * 用户权限处理 + * + * @author Lion Li + */ +public interface ISysPermissionService { + + /** + * 获取角色数据权限 + * + * @param userId 用户id + * @return 角色权限信息 + */ + Set getRolePermission(Long userId); + + /** + * 获取菜单数据权限 + * + * @param userId 用户id + * @return 菜单权限信息 + */ + Set getMenuPermission(Long userId); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPostService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPostService.java new file mode 100644 index 00000000..4e54405c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysPostService.java @@ -0,0 +1,106 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysPostBo; +import org.ruoyi.system.domain.vo.SysPostVo; + +import java.util.List; + +/** + * 岗位信息 服务层 + * + * @author Lion Li + */ +public interface ISysPostService { + + + TableDataInfo selectPagePostList(SysPostBo post, PageQuery pageQuery); + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + List selectPostList(SysPostBo post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + SysPostVo selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + boolean checkPostNameUnique(SysPostBo post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + boolean checkPostCodeUnique(SysPostBo post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + long countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param bo 岗位信息 + * @return 结果 + */ + int insertPost(SysPostBo bo); + + /** + * 修改保存岗位信息 + * + * @param bo 岗位信息 + * @return 结果 + */ + int updatePost(SysPostBo bo); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysRoleService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysRoleService.java new file mode 100644 index 00000000..d16cdc7b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysRoleService.java @@ -0,0 +1,183 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysUserRole; +import org.ruoyi.system.domain.bo.SysRoleBo; +import org.ruoyi.system.domain.vo.SysRoleVo; + +import java.util.List; +import java.util.Set; + +/** + * 角色业务层 + * + * @author Lion Li + */ +public interface ISysRoleService { + + + TableDataInfo selectPageRoleList(SysRoleBo role, PageQuery pageQuery); + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + List selectRoleList(SysRoleBo role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + SysRoleVo selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + boolean checkRoleNameUnique(SysRoleBo role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + boolean checkRoleKeyUnique(SysRoleBo role); + + /** + * 校验角色是否允许操作 + * + * @param roleId 角色ID + */ + void checkRoleAllowed(Long roleId); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + void checkRoleDataScope(Long roleId); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + long countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param bo 角色信息 + * @return 结果 + */ + int insertRole(SysRoleBo bo); + + /** + * 修改保存角色信息 + * + * @param bo 角色信息 + * @return 结果 + */ + int updateRole(SysRoleBo bo); + + /** + * 修改角色状态 + * + * @param roleId 角色ID + * @param status 角色状态 + * @return 结果 + */ + int updateRoleStatus(Long roleId, String status); + + /** + * 修改数据权限信息 + * + * @param bo 角色信息 + * @return 结果 + */ + int authDataScope(SysRoleBo bo); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + int deleteRoleByIds(Long[] roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + int insertAuthUsers(Long roleId, Long[] userIds); + + void cleanOnlineUserByRole(Long roleId); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantPackageService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantPackageService.java new file mode 100644 index 00000000..20a9b8d4 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantPackageService.java @@ -0,0 +1,57 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysTenantPackageBo; +import org.ruoyi.system.domain.vo.SysTenantPackageVo; + +import java.util.Collection; +import java.util.List; + +/** + * 租户套餐Service接口 + * + * @author Michelle.Chung + */ +public interface ISysTenantPackageService { + + /** + * 查询租户套餐 + */ + SysTenantPackageVo queryById(Long packageId); + + /** + * 查询租户套餐列表 + */ + TableDataInfo queryPageList(SysTenantPackageBo bo, PageQuery pageQuery); + + /** + * 查询租户套餐已启用列表 + */ + List selectList(); + + /** + * 查询租户套餐列表 + */ + List queryList(SysTenantPackageBo bo); + + /** + * 新增租户套餐 + */ + Boolean insertByBo(SysTenantPackageBo bo); + + /** + * 修改租户套餐 + */ + Boolean updateByBo(SysTenantPackageBo bo); + + /** + * 修改套餐状态 + */ + int updatePackageStatus(SysTenantPackageBo bo); + + /** + * 校验并批量删除租户套餐信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantService.java new file mode 100644 index 00000000..914c44c9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysTenantService.java @@ -0,0 +1,82 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysTenantBo; +import org.ruoyi.system.domain.vo.SysTenantVo; + +import java.util.Collection; +import java.util.List; + +/** + * 租户Service接口 + * + * @author Michelle.Chung + */ +public interface ISysTenantService { + + /** + * 查询租户 + */ + SysTenantVo queryById(Long id); + + /** + * 基于租户ID查询租户 + */ + SysTenantVo queryByTenantId(String tenantId); + + /** + * 查询租户列表 + */ + TableDataInfo queryPageList(SysTenantBo bo, PageQuery pageQuery); + + /** + * 查询租户列表 + */ + List queryList(SysTenantBo bo); + + /** + * 新增租户 + */ + Boolean insertByBo(SysTenantBo bo); + + /** + * 修改租户 + */ + Boolean updateByBo(SysTenantBo bo); + + /** + * 修改租户状态 + */ + int updateTenantStatus(SysTenantBo bo); + + /** + * 校验租户是否允许操作 + */ + void checkTenantAllowed(String tenantId); + + /** + * 校验并批量删除租户信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 校验企业名称是否唯一 + */ + boolean checkCompanyNameUnique(SysTenantBo bo); + + /** + * 校验账号余额 + */ + boolean checkAccountBalance(String tenantId); + + /** + * 校验有效期 + */ + boolean checkExpireTime(String tenantId); + + /** + * 同步租户套餐 + */ + Boolean syncTenantPackage(String tenantId, String packageId); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserGroupService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserGroupService.java new file mode 100644 index 00000000..b0186391 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserGroupService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.SysUserGroupBo; +import org.ruoyi.system.domain.vo.SysUserGroupVo; + +import java.util.Collection; +import java.util.List; + +/** + * 【请填写功能名称】Service接口 + * + * @author Lion Li + * @date 2024-08-03 + */ +public interface ISysUserGroupService { + + /** + * 查询【请填写功能名称】 + */ + SysUserGroupVo queryById(Long id); + + /** + * 查询【请填写功能名称】列表 + */ + TableDataInfo queryPageList(SysUserGroupBo bo, PageQuery pageQuery); + + /** + * 查询【请填写功能名称】列表 + */ + List queryList(SysUserGroupBo bo); + + /** + * 新增【请填写功能名称】 + */ + Boolean insertByBo(SysUserGroupBo bo); + + /** + * 修改【请填写功能名称】 + */ + Boolean updateByBo(SysUserGroupBo bo); + + /** + * 校验并批量删除【请填写功能名称】信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserModelService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserModelService.java new file mode 100644 index 00000000..45e2d40d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserModelService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.SysUserModelVo; +import org.ruoyi.system.domain.bo.SysUserModelBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 【请填写功能名称】Service接口 + * + * @author Lion Li + * @date 2024-08-03 + */ +public interface ISysUserModelService { + + /** + * 查询【请填写功能名称】 + */ + SysUserModelVo queryById(Long id); + + /** + * 查询【请填写功能名称】列表 + */ + TableDataInfo queryPageList(SysUserModelBo bo, PageQuery pageQuery); + + /** + * 查询【请填写功能名称】列表 + */ + List queryList(SysUserModelBo bo); + + /** + * 新增【请填写功能名称】 + */ + Boolean insertByBo(SysUserModelBo bo); + + /** + * 修改【请填写功能名称】 + */ + Boolean updateByBo(SysUserModelBo bo); + + /** + * 校验并批量删除【请填写功能名称】信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserService.java new file mode 100644 index 00000000..1597f9b0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/ISysUserService.java @@ -0,0 +1,230 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysUserVo; + +import java.util.List; + +/** + * 用户 业务层 + * + * @author Lion Li + */ +public interface ISysUserService { + + + TableDataInfo selectPageUserList(SysUserBo user, PageQuery pageQuery); + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + List selectUserList(SysUserBo user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + TableDataInfo selectAllocatedList(SysUserBo user, PageQuery pageQuery); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + TableDataInfo selectUnallocatedList(SysUserBo user, PageQuery pageQuery); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + SysUserVo selectUserByUserName(String userName); + + /** + * 通过OpenId查询用户 + * + * @Date 2023/5/18 + * @param openId + * @return SysUserVo + **/ + SysUserVo selectUserByOpenId(String openId); + + /** + * 通过手机号查询用户 + * + * @param phonenumber 手机号 + * @return 用户对象信息 + */ + SysUserVo selectUserByPhonenumber(String phonenumber); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + SysUserVo selectUserById(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + boolean checkUserNameUnique(SysUserBo user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + boolean checkPhoneUnique(SysUserBo user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + boolean checkEmailUnique(SysUserBo user); + + /** + * 校验用户是否允许操作 + * + * @param userId 用户ID + */ + void checkUserAllowed(Long userId); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + void checkUserDataScope(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + int insertUser(SysUserBo user); + + /** + * 注册用户信息 + * + * @Date 2023/5/18 + * @param user + * @param tenantId + * @return SysUser + **/ + SysUser registerUser(SysUserBo user, String tenantId); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + int updateUser(SysUserBo user); + + /** + * 小程序 - 修改用户信息 + * + * @param user 用户信息 + * + */ + SysUserVo updateXcxUser(SysUserBo user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param userId 用户ID + * @param status 帐号状态 + * @return 结果 + */ + int updateUserStatus(Long userId, String status); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + int updateUserProfile(SysUserBo user); + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + boolean updateUserAvatar(Long userId, String avatar); + + boolean updateUserName(Long userId, String nickName); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + + int resetUserPwd(Long userId, String password); + + + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + int deleteUserByIds(Long[] userIds); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IVoiceRoleService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IVoiceRoleService.java new file mode 100644 index 00000000..e0782968 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IVoiceRoleService.java @@ -0,0 +1,71 @@ +package org.ruoyi.system.service; + +import org.ruoyi.system.domain.vo.VoiceRoleVo; +import org.ruoyi.system.domain.bo.VoiceRoleBo; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.system.request.RoleListDto; +import org.ruoyi.system.request.RoleRequest; +import org.ruoyi.system.request.SimpleGenerateRequest; +import org.ruoyi.system.response.SimpleGenerateDataResponse; +import org.ruoyi.system.response.rolelist.RoleListVO; + +import java.util.Collection; +import java.util.List; + +/** + * 配音角色Service接口 + * + * @author Lion Li + * @date 2024-03-19 + */ +public interface IVoiceRoleService { + + /** + * 查询配音角色 + */ + VoiceRoleVo queryById(Long id); + + /** + * 查询配音角色列表 + */ + TableDataInfo queryPageList(VoiceRoleBo bo, PageQuery pageQuery); + + /** + * 查询配音角色列表 + */ + List queryList(VoiceRoleBo bo); + + /** + * 新增配音角色 + */ + Boolean insertByBo(RoleRequest roleRequest); + + /** + * 生成音频 + */ + SimpleGenerateDataResponse simpleGenerate(SimpleGenerateRequest simpleGenerateRequest); + + /** + * 修改配音角色 + */ + Boolean updateByBo(VoiceRoleBo bo); + + /** + * 校验并批量删除配音角色信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 查询市场角色 + * + * @return 角色列表 + */ + List roleList(); + + /** + * 收藏市场角色 + * + */ + void copyRole(RoleListDto roleListDto); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobConfigService.java new file mode 100644 index 00000000..bcfd940f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobConfigService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.WxRobConfigBo; +import org.ruoyi.system.domain.vo.WxRobConfigVo; + +import java.util.Collection; +import java.util.List; + +/** + * 机器人Service接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface IWxRobConfigService { + + /** + * 查询机器人 + */ + WxRobConfigVo queryById(Long id); + + /** + * 查询机器人列表 + */ + TableDataInfo queryPageList(WxRobConfigBo bo, PageQuery pageQuery); + + /** + * 查询当前用户绑定的机器人信息 + */ + List queryList(WxRobConfigBo bo); + + /** + * 新增机器人 + */ + Boolean insertByBo(WxRobConfigBo bo); + + /** + * 修改机器人 + */ + Boolean updateByBo(WxRobConfigBo bo); + + /** + * 校验并批量删除机器人信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobKeywordService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobKeywordService.java new file mode 100644 index 00000000..39bb5263 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobKeywordService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.WxRobKeywordBo; +import org.ruoyi.system.domain.vo.WxRobKeywordVo; + +import java.util.Collection; +import java.util.List; + +/** + * 【请填写功能名称】Service接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface IWxRobKeywordService { + + /** + * 查询【请填写功能名称】 + */ + WxRobKeywordVo queryById(Long id); + + /** + * 查询【请填写功能名称】列表 + */ + TableDataInfo queryPageList(WxRobKeywordBo bo, PageQuery pageQuery); + + /** + * 查询【请填写功能名称】列表 + */ + List queryList(WxRobKeywordBo bo); + + /** + * 新增【请填写功能名称】 + */ + Boolean insertByBo(WxRobKeywordBo bo); + + /** + * 修改【请填写功能名称】 + */ + Boolean updateByBo(WxRobKeywordBo bo); + + /** + * 校验并批量删除【请填写功能名称】信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobRelationService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobRelationService.java new file mode 100644 index 00000000..4344fbd9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/IWxRobRelationService.java @@ -0,0 +1,48 @@ +package org.ruoyi.system.service; + +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.bo.WxRobRelationBo; +import org.ruoyi.system.domain.vo.WxRobRelationVo; + +import java.util.Collection; +import java.util.List; + +/** + * 【请填写功能名称】Service接口 + * + * @author Lion Li + * @date 2024-05-01 + */ +public interface IWxRobRelationService { + + /** + * 查询【请填写功能名称】 + */ + WxRobRelationVo queryById(Long id); + + /** + * 查询【请填写功能名称】列表 + */ + TableDataInfo queryPageList(WxRobRelationBo bo, PageQuery pageQuery); + + /** + * 查询【请填写功能名称】列表 + */ + List queryList(WxRobRelationBo bo); + + /** + * 新增【请填写功能名称】 + */ + Boolean insertByBo(WxRobRelationBo bo); + + /** + * 修改【请填写功能名称】 + */ + Boolean updateByBo(WxRobRelationBo bo); + + /** + * 校验并批量删除【请填写功能名称】信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java new file mode 100644 index 00000000..9caac087 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java @@ -0,0 +1,429 @@ +package org.ruoyi.system.service; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.binarywang.wx.miniapp.util.WxMaConfigHolder; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.secure.BCrypt; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.domain.dto.RoleDTO; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.domain.model.VisitorLoginBody; +import org.ruoyi.common.core.domain.model.VisitorLoginUser; +import org.ruoyi.common.core.enums.*; +import org.ruoyi.common.core.exception.user.CaptchaException; +import org.ruoyi.common.core.exception.user.CaptchaExpireException; +import org.ruoyi.common.core.exception.user.UserException; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.*; +import org.ruoyi.common.log.event.LogininforEvent; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.tenant.exception.TenantException; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.LoginVo; +import org.ruoyi.system.domain.vo.SysTenantVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.function.Supplier; + +/** + * 登录校验方法 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Slf4j +@Service +public class SysLoginService { + + private final SysUserMapper userMapper; + private final ISysPermissionService permissionService; + private final ISysTenantService tenantService; + private final WxMaService wxMaService; + private final ISysUserService userService; + private final ConfigService configService; + @Value("${user.password.maxRetryCount}") + private Integer maxRetryCount; + + @Value("${user.password.lockTime}") + private Integer lockTime; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String tenantId, String username, String password, String code, String uuid) { + SysUserVo user = loadUserByUsername(tenantId, username); + checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); + // 此处可根据登录用户的数据不同 自行创建 loginUser + LoginUser loginUser = buildLoginUser(user); + // 生成token + LoginHelper.loginByDevice(loginUser, DeviceType.PC); + + recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLoginInfo(user.getUserId()); + return StpUtil.getTokenValue(); + } + + public String smsLogin(String tenantId, String phonenumber, String smsCode) { + // 校验租户 + checkTenant(tenantId); + // 通过手机号查找用户 + SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber); + + checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); + // 此处可根据登录用户的数据不同 自行创建 loginUser + LoginUser loginUser = buildLoginUser(user); + // 生成token + LoginHelper.loginByDevice(loginUser, DeviceType.APP); + + recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLoginInfo(user.getUserId()); + return StpUtil.getTokenValue(); + } + + public String emailLogin(String tenantId, String email, String emailCode) { + // 校验租户 + checkTenant(tenantId); + // 通过手机号查找用户 + SysUserVo user = loadUserByEmail(tenantId, email); + + checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode)); + // 此处可根据登录用户的数据不同 自行创建 loginUser + LoginUser loginUser = buildLoginUser(user); + // 生成token + LoginHelper.loginByDevice(loginUser, DeviceType.APP); + + recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLoginInfo(user.getUserId()); + return StpUtil.getTokenValue(); + } + + + /** + * 游客登录 + * + * @param loginBody + * @return String + * @Date 2023/5/18 + **/ + public void visitorLogin(VisitorLoginBody loginBody) { + String openid = ""; + // PC端游客登录 + if(LoginUserType.PC.getCode().equals(loginBody.getType())){ + openid = loginBody.getCode(); + }else { + // 小程序匿名登录 + try { + WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginBody.getCode()); + openid = session.getOpenid(); + } catch ( + WxErrorException e) { + log.error(e.getMessage(), e); + } finally { + // 清理ThreadLocal + WxMaConfigHolder.remove(); + } + } + } + + public LoginVo mpLogin(String openid) { + // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户 + SysUserVo user = userService.selectUserByOpenId(openid); + VisitorLoginUser loginUser = new VisitorLoginUser(); + if (ObjectUtil.isNull(user)) { + SysUserBo sysUser = new SysUserBo(); + // 改为自增 + String name = "用户" + UUID.randomUUID(); + // 设置默认用户名 + sysUser.setUserName(name); + // 设置默认昵称 + sysUser.setNickName(name); + // 设置默认密码 + sysUser.setPassword(BCrypt.hashpw("123456")); + // 设置微信openId + sysUser.setOpenId(openid); + String configValue = configService.getConfigValue("mail", "amount"); + // 设置默认余额 + sysUser.setUserBalance(NumberUtils.toDouble(configValue,1)); + // 注册用户,设置默认租户为0 + SysUser registerUser = userService.registerUser(sysUser, "0"); + + // 构建登录用户信息 + loginUser.setTenantId("0"); + loginUser.setUserId(registerUser.getUserId()); + loginUser.setUsername(registerUser.getUserName()); + loginUser.setUserType(UserType.APP_USER.getUserType()); + loginUser.setOpenid(openid); + loginUser.setNickName(registerUser.getNickName()); + + } else { + // 此处可根据登录用户的数据不同 自行创建 loginUser + loginUser.setTenantId(user.getTenantId()); + loginUser.setUserId(user.getUserId()); + loginUser.setUsername(user.getUserName()); + loginUser.setUserType(user.getUserType()); + loginUser.setNickName(user.getNickName()); + loginUser.setAvatar(user.getWxAvatar()); + loginUser.setOpenid(openid); + } + // 生成token + LoginHelper.loginByDevice(loginUser, DeviceType.XCX); + recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + LoginVo loginVo = new LoginVo(); + // 生成令牌 + loginVo.setToken(StpUtil.getTokenValue()); + loginVo.setUserInfo(loginUser); + return loginVo; + } + + + /** + * 退出登录 + */ + public void logout() { + try { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) { + // 超级管理员 登出清除动态租户 + TenantHelper.clearDynamic(); + } + StpUtil.logout(); + recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success")); + } catch (NotLoginException ignored) { + } + } + + /** + * 记录登录信息 + * + * @param tenantId 租户ID + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + */ + private void recordLogininfor(String tenantId, String username, String status, String message) { + LogininforEvent logininforEvent = new LogininforEvent(); + logininforEvent.setTenantId(tenantId); + logininforEvent.setUsername(username); + logininforEvent.setStatus(status); + logininforEvent.setMessage(message); + logininforEvent.setRequest(ServletUtils.getRequest()); + SpringUtils.context().publishEvent(logininforEvent); + } + + /** + * 校验短信验证码 + */ + private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) { + String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber); + if (StringUtils.isBlank(code)) { + recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + return code.equals(smsCode); + } + + /** + * 校验邮箱验证码 + */ + private boolean validateEmailCode(String tenantId, String email, String emailCode) { + String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email); + if (StringUtils.isBlank(code)) { + recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + return code.equals(emailCode); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + */ + public void validateCaptcha(String tenantId, String username, String code, String uuid) { + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); + String captcha = RedisUtils.getCacheObject(verifyKey); + RedisUtils.deleteObject(verifyKey); + if (captcha == null) { + recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) { + recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); + throw new CaptchaException(); + } + } + + private SysUserVo loadUserByUsername(String tenantId, String username) { + + SysUser user = userMapper.selectOne(new LambdaQueryWrapper() + .select(SysUser::getUserName, SysUser::getStatus) + .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId) + .eq(SysUser::getUserName, username)); + if (ObjectUtil.isNull(user)) { + log.info("登录用户:{} 不存在.", username); + throw new UserException("user.not.exists", username); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new UserException("user.blocked", username); + } + if (TenantHelper.isEnable()) { + return userMapper.selectTenantUserByUserName(username, tenantId); + } + return userMapper.selectUserByUserName(username); + } + + private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) { + SysUser user = userMapper.selectOne(new LambdaQueryWrapper() + .select(SysUser::getPhonenumber, SysUser::getStatus) + .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId) + .eq(SysUser::getPhonenumber, phonenumber)); + if (ObjectUtil.isNull(user)) { + log.info("登录用户:{} 不存在.", phonenumber); + throw new UserException("user.not.exists", phonenumber); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", phonenumber); + throw new UserException("user.blocked", phonenumber); + } + if (TenantHelper.isEnable()) { + return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId); + } + return userMapper.selectUserByPhonenumber(phonenumber); + } + + private SysUserVo loadUserByEmail(String tenantId, String email) { + SysUser user = userMapper.selectOne(new LambdaQueryWrapper() + .select(SysUser::getPhonenumber, SysUser::getStatus) + .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId) + .eq(SysUser::getEmail, email)); + if (ObjectUtil.isNull(user)) { + log.info("登录用户:{} 不存在.", email); + throw new UserException("user.not.exists", email); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", email); + throw new UserException("user.blocked", email); + } + if (TenantHelper.isEnable()) { + return userMapper.selectTenantUserByEmail(email, tenantId); + } + return userMapper.selectUserByEmail(email); + } + + /** + * 构建登录用户 + */ + private LoginUser buildLoginUser(SysUserVo user) { + LoginUser loginUser = new LoginUser(); + loginUser.setTenantId(user.getTenantId()); + loginUser.setUserId(user.getUserId()); + loginUser.setDeptId(user.getDeptId()); + loginUser.setUsername(user.getUserName()); + loginUser.setAvatar(user.getAvatar()); + loginUser.setUserType(user.getUserType()); + loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId())); + loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId())); + loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName()); + List roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class); + loginUser.setRoles(roles); + return loginUser; + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(ServletUtils.getClientIP()); + sysUser.setLoginDate(DateUtils.getNowDate()); + sysUser.setUpdateBy(userId); + userMapper.updateById(sysUser); + } + + /** + * 登录校验 + */ + private void checkLogin(LoginType loginType, String tenantId, String username, Supplier supplier) { + String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username; + String loginFail = Constants.LOGIN_FAIL; + + // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip) + Integer errorNumber = RedisUtils.getCacheObject(errorKey); + // 锁定时间内登录 则踢出 + if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) { + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); + throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); + } + + if (supplier.get()) { + // 是否第一次 + errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1; + // 达到规定错误次数 则锁定登录 + if (errorNumber.equals(maxRetryCount)) { + RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime)); + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); + throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); + } else { + // 未达到规定错误次数 则递增 + RedisUtils.setCacheObject(errorKey, errorNumber); + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber)); + throw new UserException(loginType.getRetryLimitCount(), errorNumber); + } + } + + // 登录成功 清空错误次数 + RedisUtils.deleteObject(errorKey); + } + + private void checkTenant(String tenantId) { + if (!TenantHelper.isEnable()) { + return; + } + if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { + return; + } + SysTenantVo tenant = tenantService.queryByTenantId(tenantId); + if (ObjectUtil.isNull(tenant)) { + log.info("登录租户:{} 不存在.", tenantId); + throw new TenantException("tenant.not.exists"); + } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) { + log.info("登录租户:{} 已被停用.", tenantId); + throw new TenantException("tenant.blocked"); + } else if (ObjectUtil.isNotNull(tenant.getExpireTime()) + && new Date().after(tenant.getExpireTime())) { + log.info("登录租户:{} 已超过有效期.", tenantId); + throw new TenantException("tenant.expired"); + } + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysRegisterService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysRegisterService.java new file mode 100644 index 00000000..84701ba7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysRegisterService.java @@ -0,0 +1,153 @@ +package org.ruoyi.system.service; + +import cn.dev33.satoken.secure.BCrypt; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.math.NumberUtils; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.constant.GlobalConstants; +import org.ruoyi.common.core.domain.model.RegisterBody; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.core.exception.user.CaptchaException; +import org.ruoyi.common.core.exception.user.CaptchaExpireException; +import org.ruoyi.common.core.exception.user.UserException; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.MessageUtils; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.log.event.LogininforEvent; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.SysUserRole; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.SysUserRoleMapper; +import org.springframework.stereotype.Service; + +/** + * 注册校验方法 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysRegisterService { + + private final ISysUserService userService; + + private final SysUserRoleMapper userRoleMapper; + + private final ConfigService configService; + /** + * 注册 + */ + public void register(RegisterBody registerBody) { + + + String tenantId = Constants.TENANT_ID; + if(StringUtils.isNotBlank(registerBody.getTenantId())){ + tenantId = registerBody.getTenantId(); + } + String username = registerBody.getUsername(); + String password = registerBody.getPassword(); + + // 检查验证码是否正确 + validateEmail(username,registerBody.getCode()); + SysUserBo sysUser = new SysUserBo(); + sysUser.setDomainName(registerBody.getDomainName()); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPassword(BCrypt.hashpw(password)); + if (!userService.checkUserNameUnique(sysUser)) { + throw new UserException("添加用户失败", username); + } + String configValue = configService.getConfigValue("mail", "amount"); + + + + // 设置默认余额 + sysUser.setUserBalance(NumberUtils.toDouble(configValue,1)); + SysUser user = userService.registerUser(sysUser, tenantId); + if (user == null) { + throw new UserException("用户注册失败!"); + } + // 设置默认角色 + SysUserRole sysRole = new SysUserRole(); + sysRole.setUserId(user.getUserId()); + sysRole.setRoleId(1L); + userRoleMapper.insert(sysRole); + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success")); + } + + /** + * 重置密码 + */ + public void resetPassWord(RegisterBody registerBody) { + String username = registerBody.getUsername(); + String password = registerBody.getPassword(); + SysUserVo user = userService.selectUserByUserName(username); + if(user == null){ + throw new UserException(String.format("用户【%s】,未注册!",username)); + } + // 检查验证码是否正确 + validateEmail(username,registerBody.getCode()); + userService.resetUserPwd(user.getUserId(),BCrypt.hashpw(password)); + } + + /** + * 校验邮箱验证码 + * + * @param username 用户名 + */ + public void validateEmail(String username,String code) { + String key = GlobalConstants.CAPTCHA_CODE_KEY + username; + String captcha = RedisUtils.getCacheObject(key); + if(code.equals(captcha)){ + RedisUtils.deleteObject(captcha); + }else { + throw new BaseException("验证码错误,请重试!"); + } + } + + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + */ + public void validateCaptcha(String tenantId, String username, String code, String uuid) { + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); + String captcha = RedisUtils.getCacheObject(verifyKey); + RedisUtils.deleteObject(verifyKey); + if (captcha == null) { + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire")); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) { + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error")); + throw new CaptchaException(); + } + } + + /** + * 记录登录信息 + * + * @param tenantId 租户ID + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * @return + */ + private void recordLogininfor(String tenantId, String username, String status, String message) { + LogininforEvent logininforEvent = new LogininforEvent(); + logininforEvent.setTenantId(tenantId); + logininforEvent.setUsername(username); + logininforEvent.setStatus(status); + logininforEvent.setMessage(message); + logininforEvent.setRequest(ServletUtils.getRequest()); + SpringUtils.context().publishEvent(logininforEvent); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/WeixinUserService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/WeixinUserService.java new file mode 100644 index 00000000..6cf778c5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/WeixinUserService.java @@ -0,0 +1,12 @@ +package org.ruoyi.system.service; + +/** + * @Author 公众号:程序猿阿朗 + */ +public interface WeixinUserService { + + void checkSignature(String signature, String timestamp, String nonce); + + String handleWeixinMsg(String body); + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatConfigServiceImpl.java new file mode 100644 index 00000000..7d2c99f6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatConfigServiceImpl.java @@ -0,0 +1,159 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.event.ConfigChangeEvent; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.ChatConfig; +import org.ruoyi.system.domain.bo.ChatConfigBo; +import org.ruoyi.system.domain.vo.ChatConfigVo; +import org.ruoyi.system.mapper.ChatConfigMapper; +import org.ruoyi.system.service.IChatConfigService; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; + +/** + * 对话配置信息 +Service业务层处理 + * + * @author Lion Li + * @date 2024-04-13 + */ +@RequiredArgsConstructor +@Service +@Slf4j +public class ChatConfigServiceImpl implements IChatConfigService, ConfigService { + + private final ChatConfigMapper baseMapper; + + private final ApplicationEventPublisher eventPublisher; + + /** + * 查询对话配置信息 + + */ + @Override + public ChatConfigVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询对话配置信息列表 + */ + @Override + public TableDataInfo queryPageList(ChatConfigBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询对话配置信息列表 + */ + @Override + public List queryList(ChatConfigBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(ChatConfigBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getCategory()), ChatConfig::getCategory, bo.getCategory()); + lqw.like(StringUtils.isNotBlank(bo.getConfigName()), ChatConfig::getConfigName, bo.getConfigName()); + lqw.eq(StringUtils.isNotBlank(bo.getConfigValue()), ChatConfig::getConfigValue, bo.getConfigValue()); + return lqw; + } + + /** + * 新增对话配置信息 + + */ + @Override + public Boolean insertByBo(ChatConfigBo bo) { + ChatConfig add = MapstructUtils.convert(bo, ChatConfig.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改对话配置信息 + + */ + @Override + public Boolean updateByBo(ChatConfigBo bo) { + ChatConfig update = MapstructUtils.convert(bo, ChatConfig.class); + validEntityBeforeSave(update); + // 更新配置信息(类型区分) + eventPublisher.publishEvent(new ConfigChangeEvent(this)); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(ChatConfig entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除对话配置信息 + + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 根据配置类型和配置key获取值 + * + * @param category + * @param configKey + * @return + */ + @Override + public String getConfigValue(String category,String configKey) { + ChatConfigBo bo = new ChatConfigBo(); + bo.setCategory(category); + bo.setConfigName(configKey); + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + ChatConfigVo chatConfigVo = baseMapper.selectVoOne(lqw); + if(chatConfigVo != null){ + return chatConfigVo.getConfigValue(); + }else { + return ""; + } + } + + /** + * 根据配置类型和配置key获取值 + * + * @param category + * @return + */ + @Override + public List getSysConfigValue(String category) { + ChatConfigBo bo = new ChatConfigBo(); + bo.setCategory(category); + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatCostServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatCostServiceImpl.java new file mode 100644 index 00000000..137bea06 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatCostServiceImpl.java @@ -0,0 +1,161 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.ChatToken; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.IChatCostService; +import org.ruoyi.system.service.IChatMessageService; +import org.ruoyi.system.service.IChatTokenService; +import org.ruoyi.system.service.ISysModelService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author hncboy + * @date 2023/3/22 19:41 + * 聊天相关业务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ChatCostServiceImpl implements IChatCostService { + + private final SysUserMapper sysUserMapper; + + private final IChatMessageService chatMessageService; + + private final IChatTokenService chatTokenService; + + private final ISysModelService sysModelService; + + /** + * 根据消耗的tokens扣除余额 + * + * @param chatMessageBo + */ + public void deductToken(ChatMessageBo chatMessageBo) { + // 计算总token数 + ChatToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), chatMessageBo.getModelName()); + if (chatToken == null) { + chatToken = new ChatToken(); + chatToken.setToken(0); + } + int totalTokens = chatToken.getToken() + chatMessageBo.getTotalTokens(); + // 如果总token数大于等于1000,进行费用扣除 + if (totalTokens >= 1000) { + // 计算费用 + int token1 = totalTokens / 1000; + int token2 = totalTokens % 1000; + if (token2 > 0) { + // 保存剩余tokens + chatToken.setModelName(chatMessageBo.getModelName()); + chatToken.setUserId(chatMessageBo.getUserId()); + chatToken.setToken(token2); + chatTokenService.editToken(chatToken); + } else { + chatTokenService.resetToken(chatMessageBo.getUserId(), chatMessageBo.getModelName()); + } + // 扣除用户余额 + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName(chatMessageBo.getModelName()); + List sysModelList = sysModelService.queryList(sysModelBo); + double modelPrice = sysModelList.get(0).getModelPrice(); + Double numberCost = token1 * modelPrice; + deductUserBalance(chatMessageBo.getUserId(), numberCost); + chatMessageBo.setDeductCost(numberCost); + } else { + // 扣除用户余额 + deductUserBalance(chatMessageBo.getUserId(), 0.0); + chatMessageBo.setDeductCost(0d); + chatMessageBo.setRemark("不满1kToken,计入下一次!"); + chatToken.setToken(totalTokens); + chatToken.setModelName(chatMessageBo.getModelName()); + chatToken.setUserId(chatMessageBo.getUserId()); + chatTokenService.editToken(chatToken); + } + // 保存消息记录 + chatMessageService.insertByBo(chatMessageBo); + } + + + + /** + * 从用户余额中扣除费用 + * + * @param userId 用户ID + * @param numberCost 要扣除的费用 + */ + @Override + public void deductUserBalance(Long userId, Double numberCost) { + SysUser sysUser = sysUserMapper.selectById(userId); + if (sysUser == null) { + return; + } + + Double userBalance = sysUser.getUserBalance(); + if (userBalance < numberCost || userBalance == 0) { + throw new ServiceException("余额不足, 请充值"); + } + sysUserMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0)) + .eq(SysUser::getUserId, userId)); + } + + + + /** + * 扣除任务费用 + * + */ + @Override + public void taskDeduct(String type,String prompt, double cost) { + // 判断用户是否付费 + checkUserGrade(); + // 扣除费用 + deductUserBalance(getUserId(), cost); + // 保存消息记录 + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setUserId(getUserId()); + chatMessageBo.setModelName(type); + chatMessageBo.setContent(prompt); + chatMessageBo.setDeductCost(cost); + chatMessageBo.setTotalTokens(0); + chatMessageService.insertByBo(chatMessageBo); + } + + /** + * 判断用户是否付费 + */ + @Override + public void checkUserGrade() { + SysUser sysUser = sysUserMapper.selectById(getUserId()); + if("0".equals(sysUser.getUserGrade())){ + throw new BaseException("This model is currently unavailable for free users. Please make any amount of deposit to access."); + } + } + + /** + * 获取用户Id + * + * @return + */ + public Long getUserId() { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new BaseException("用户未登录!"); + } + return loginUser.getUserId(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatGptsServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatGptsServiceImpl.java new file mode 100644 index 00000000..38ca74a5 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatGptsServiceImpl.java @@ -0,0 +1,118 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.ChatGpts; +import org.ruoyi.system.domain.bo.ChatGptsBo; +import org.ruoyi.system.domain.vo.ChatGptsVo; +import org.ruoyi.system.mapper.ChatGptsMapper; +import org.ruoyi.system.service.IChatGptsService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * gpts管理Service业务层处理 + * + * @author Lion Li + * @date 2024-07-09 + */ +@RequiredArgsConstructor +@Service +public class ChatGptsServiceImpl implements IChatGptsService { + + private final ChatGptsMapper baseMapper; + + /** + * 查询gpts管理 + */ + @Override + public ChatGptsVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询gpts管理列表 + */ + @Override + public TableDataInfo queryPageList(ChatGptsBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询gpts管理列表 + */ + @Override + public List queryList(ChatGptsBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(ChatGptsBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getGid()), ChatGpts::getGid, bo.getGid()); + lqw.like(StringUtils.isNotBlank(bo.getName()), ChatGpts::getName, bo.getName()); + lqw.eq(StringUtils.isNotBlank(bo.getLogo()), ChatGpts::getLogo, bo.getLogo()); + lqw.eq(StringUtils.isNotBlank(bo.getInfo()), ChatGpts::getInfo, bo.getInfo()); + lqw.eq(StringUtils.isNotBlank(bo.getAuthorId()), ChatGpts::getAuthorId, bo.getAuthorId()); + lqw.like(StringUtils.isNotBlank(bo.getAuthorName()), ChatGpts::getAuthorName, bo.getAuthorName()); + lqw.eq(StringUtils.isNotBlank(bo.getUseCnt()), ChatGpts::getUseCnt, bo.getUseCnt()); + lqw.eq(StringUtils.isNotBlank(bo.getBad()), ChatGpts::getBad, bo.getBad()); + lqw.eq(StringUtils.isNotBlank(bo.getType()), ChatGpts::getType, bo.getType()); + lqw.eq(StringUtils.isNotBlank(bo.getUpdateIp()), ChatGpts::getUpdateIp, bo.getUpdateIp()); + return lqw; + } + + /** + * 新增gpts管理 + */ + @Override + public Boolean insertByBo(ChatGptsBo bo) { + ChatGpts add = MapstructUtils.convert(bo, ChatGpts.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改gpts管理 + */ + @Override + public Boolean updateByBo(ChatGptsBo bo) { + ChatGpts update = MapstructUtils.convert(bo, ChatGpts.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(ChatGpts entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除gpts管理 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatMessageServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatMessageServiceImpl.java new file mode 100644 index 00000000..421c6cfc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatMessageServiceImpl.java @@ -0,0 +1,144 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.ChatMessage; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.vo.ChatMessageVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.ChatMessageMapper; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.IChatMessageService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 聊天消息Service业务层处理 + * + * @author Lion Li + * @date 2023-11-26 + */ +@RequiredArgsConstructor +@Service +public class ChatMessageServiceImpl implements IChatMessageService { + + private final ChatMessageMapper baseMapper; + + private final SysUserMapper sysUserMapper; + /** + * 查询聊天消息 + */ + @Override + public ChatMessageVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询聊天消息列表 + */ + @Override + public TableDataInfo queryPageList(ChatMessageBo bo, PageQuery pageQuery) { + // 根据用户名称查询用户 + if(StringUtils.isNotEmpty(bo.getUserName())){ + SysUserVo sysUserVo = sysUserMapper.selectUserByUserName(bo.getUserName()); + bo.setUserId(sysUserVo.getUserId()); + } + + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + if(CollectionUtil.isEmpty(result.getRecords())){ + return TableDataInfo.build(result); + } + List userIds = result.getRecords().stream() + .map(ChatMessageVo::getUserId) + .collect(Collectors.toList()); + // 一次性查询所有userName + Map userIdToUserNameMap = getUserNamesByUserIds(userIds); + // 设置userName + result.getRecords().forEach(chatMessageVo -> { + chatMessageVo.setUserName(userIdToUserNameMap.get(chatMessageVo.getUserId())); + }); + return TableDataInfo.build(result); + } + + private Map getUserNamesByUserIds(List userIds) { + // 实现批量查询userName的逻辑,例如通过sysUserMapper查询sys_user表 + List sysUsers = sysUserMapper.selectBatchIds(userIds); + return sysUsers.stream() + .collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName)); + } + + /** + * 查询聊天消息列表 + */ + @Override + public List queryList(ChatMessageBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(ChatMessageBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, ChatMessage::getUserId, bo.getUserId()); + lqw.eq(StringUtils.isNotBlank(bo.getContent()), ChatMessage::getContent, bo.getContent()); + lqw.eq(bo.getDeductCost() != null, ChatMessage::getDeductCost, bo.getDeductCost()); + lqw.eq(bo.getTotalTokens() != null, ChatMessage::getTotalTokens, bo.getTotalTokens()); + lqw.like(StringUtils.isNotBlank(bo.getModelName()), ChatMessage::getModelName, bo.getModelName()); + return lqw; + } + + /** + * 新增聊天消息 + */ + @Override + public Boolean insertByBo(ChatMessageBo bo) { + ChatMessage add = MapstructUtils.convert(bo, ChatMessage.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改聊天消息 + */ + @Override + public Boolean updateByBo(ChatMessageBo bo) { + ChatMessage update = MapstructUtils.convert(bo, ChatMessage.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(ChatMessage entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除聊天消息 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatTokenServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatTokenServiceImpl.java new file mode 100644 index 00000000..f5e69bc3 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatTokenServiceImpl.java @@ -0,0 +1,55 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.ruoyi.system.domain.ChatToken; +import org.ruoyi.system.mapper.ChatTokenMapper; +import org.ruoyi.system.service.IChatTokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 聊天消息Service业务层处理 + * + * @author Lion Li + * @date 2023-11-26 + */ +@RequiredArgsConstructor +@Service +public class ChatTokenServiceImpl implements IChatTokenService { + + private final ChatTokenMapper baseMapper; + + @Override + public ChatToken queryByUserId(Long userId,String modelName) { + return baseMapper.selectOne( + new LambdaQueryWrapper() + .eq(ChatToken::getUserId, userId) + .eq(ChatToken::getModelName, modelName) + .last("limit 1") + ); + } + + /** + * 清空用户token + * + */ + @Override + public void resetToken(Long userId,String modelName) { + ChatToken chatToken = queryByUserId(userId, modelName); + chatToken.setToken(0); + baseMapper.updateById(chatToken); + } + + /** + * 增加用户token + * + */ + @Override + public void editToken(ChatToken chatToken) { + if(chatToken.getId() == null){ + baseMapper.insert(chatToken); + }else { + baseMapper.updateById(chatToken); + } + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVisitorUsageServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVisitorUsageServiceImpl.java new file mode 100644 index 00000000..85f1e767 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVisitorUsageServiceImpl.java @@ -0,0 +1,112 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.ChatVisitorUsageBo; +import org.ruoyi.system.domain.vo.ChatVisitorUsageVo; +import org.ruoyi.system.domain.ChatVisitorUsage; +import org.ruoyi.system.mapper.ChatVisitorUsageMapper; +import org.ruoyi.system.service.IChatVisitorUsageService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 访客管理Service业务层处理 + * + * @author Lion Li + * @date 2024-07-14 + */ +@RequiredArgsConstructor +@Service +public class ChatVisitorUsageServiceImpl implements IChatVisitorUsageService { + + private final ChatVisitorUsageMapper baseMapper; + + /** + * 查询访客管理 + */ + @Override + public ChatVisitorUsageVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询访客管理列表 + */ + @Override + public TableDataInfo queryPageList(ChatVisitorUsageBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询访客管理列表 + */ + @Override + public List queryList(ChatVisitorUsageBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(ChatVisitorUsageBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getFingerprint()), ChatVisitorUsage::getFingerprint, bo.getFingerprint()); + lqw.eq(StringUtils.isNotBlank(bo.getUsageCount()), ChatVisitorUsage::getUsageCount, bo.getUsageCount()); + lqw.eq(StringUtils.isNotBlank(bo.getIpAddress()), ChatVisitorUsage::getIpAddress, bo.getIpAddress()); + lqw.eq(StringUtils.isNotBlank(bo.getUpdateIp()), ChatVisitorUsage::getUpdateIp, bo.getUpdateIp()); + return lqw; + } + + /** + * 新增访客管理 + */ + @Override + public Boolean insertByBo(ChatVisitorUsageBo bo) { + ChatVisitorUsage add = MapstructUtils.convert(bo, ChatVisitorUsage.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改访客管理 + */ + @Override + public Boolean updateByBo(ChatVisitorUsageBo bo) { + ChatVisitorUsage update = MapstructUtils.convert(bo, ChatVisitorUsage.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(ChatVisitorUsage entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除访客管理 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVoucherServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVoucherServiceImpl.java new file mode 100644 index 00000000..41078ab6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/ChatVoucherServiceImpl.java @@ -0,0 +1,180 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.ISysUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.ChatVoucherBo; +import org.ruoyi.system.domain.vo.ChatVoucherVo; +import org.ruoyi.system.domain.ChatVoucher; +import org.ruoyi.system.mapper.ChatVoucherMapper; +import org.ruoyi.system.service.IChatVoucherService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * 用户兑换记录Service业务层处理 + * + * @author Lion Li + * @date 2024-05-03 + */ +@RequiredArgsConstructor +@Service +public class ChatVoucherServiceImpl implements IChatVoucherService { + + private final ChatVoucherMapper baseMapper; + + private final ISysUserService sysUserService; + + private final SysUserMapper sysUserMapper; + + /** + * 查询用户兑换记录 + */ + @Override + public ChatVoucherVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询用户兑换记录列表 + */ + @Override + public TableDataInfo queryPageList(ChatVoucherBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + if(CollectionUtil.isEmpty(result.getRecords())){ + return TableDataInfo.build(result); + } + // 获取所有userId + List userIds = result.getRecords().stream() + .map(ChatVoucherVo::getUserId) + .collect(Collectors.toList()); + // 一次性查询所有userName + Map userIdToUserNameMap = getUserNamesByUserIds(userIds); + // 设置userName + result.getRecords().forEach(chatVoucherVo -> { + chatVoucherVo.setUserName(userIdToUserNameMap.get(chatVoucherVo.getUserId())); + }); + return TableDataInfo.build(result); + } + + private Map getUserNamesByUserIds(List userIds) { + // 实现批量查询userName的逻辑,例如通过sysUserMapper查询sys_user表 + List sysUsers = sysUserMapper.selectBatchIds(userIds); + return sysUsers.stream() + .collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName)); + } + + /** + * 查询用户兑换记录列表 + */ + @Override + public List queryList(ChatVoucherBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(ChatVoucherBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, ChatVoucher::getUserId, bo.getUserId()); + lqw.eq(StringUtils.isNotBlank(bo.getCode()), ChatVoucher::getCode, bo.getCode()); + lqw.eq(bo.getAmount() != null, ChatVoucher::getAmount, bo.getAmount()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ChatVoucher::getStatus, bo.getStatus()); + return lqw; + } + + /** + * 新增用户兑换记录 + */ + @Override + public Boolean insertByBo(ChatVoucherBo bo) { + ChatVoucher add = MapstructUtils.convert(bo, ChatVoucher.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改用户兑换记录 + */ + @Override + public Boolean updateByBo(ChatVoucherBo bo) { + ChatVoucher update = MapstructUtils.convert(bo, ChatVoucher.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(ChatVoucher entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除用户兑换记录 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 兑换卡密 + * + * @param bo 卡密信息 + */ + @Override + public Boolean redeem(ChatVoucherBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getCode()), ChatVoucher::getCode, bo.getCode()); + ChatVoucherVo chatVoucherVo = baseMapper.selectVoOne(lqw); + if(chatVoucherVo != null){ + // 如果卡密已经兑换 + if("2".equals(chatVoucherVo.getStatus())){ + return false; + } + SysUserVo sysUserVo = sysUserService.selectUserById(LoginHelper.getLoginUser().getUserId()); + // 更新卡密记录 + chatVoucherVo.setUserId(LoginHelper.getLoginUser().getUserId()); + chatVoucherVo.setStatus("2"); + chatVoucherVo.setBalanceBefore(sysUserVo.getUserBalance()); + chatVoucherVo.setBalanceAfter(sysUserVo.getUserBalance()+chatVoucherVo.getAmount()); + // 添加用户余额 + sysUserVo.setUserBalance(sysUserVo.getUserBalance() + chatVoucherVo.getAmount()); + SysUserBo user = new SysUserBo(); + BeanUtil.copyProperties(sysUserVo,user); + sysUserService.updateUser(user); + + ChatVoucher update = MapstructUtils.convert(chatVoucherVo, ChatVoucher.class); + baseMapper.updateById(update); + }else { + return false; + } + return true; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/PaymentOrdersServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/PaymentOrdersServiceImpl.java new file mode 100644 index 00000000..1415aaa1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/PaymentOrdersServiceImpl.java @@ -0,0 +1,211 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.PaymentOrder; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.PaymentOrdersBo; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.request.OrderRequest; +import org.ruoyi.system.domain.vo.PaymentOrdersVo; +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.PaymentOrdersMapper; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.IPaymentOrdersService; +import org.ruoyi.system.service.ISysPackagePlanService; +import org.ruoyi.system.service.ISysUserService; +import org.ruoyi.system.util.OrderNumberGenerator; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 支付订单Service业务层处理 + * + * @author Lion Li + * @date 2024-04-16 + */ +@RequiredArgsConstructor +@Service +public class PaymentOrdersServiceImpl implements IPaymentOrdersService { + + private final PaymentOrdersMapper baseMapper; + + private final SysUserMapper sysUserMapper; + + private final ISysUserService userService; + + private final ISysPackagePlanService planService; + + /** + * 查询支付订单 + */ + @Override + public PaymentOrdersVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 创建支付订单 + */ + @Override + public PaymentOrdersBo createPayOrder(OrderRequest orderRequest) { + LoginUser loginUser = LoginHelper.getLoginUser(); + PaymentOrdersBo paymentOrder = new PaymentOrdersBo(); + paymentOrder.setOrderName(orderRequest.getName()); + paymentOrder.setAmount(new BigDecimal(orderRequest.getMoney())); + paymentOrder.setOrderNo(OrderNumberGenerator.generate()); + paymentOrder.setUserId(loginUser.getUserId()); + // TODO 支付状态默认待支付 - 添加枚举 + paymentOrder.setPaymentStatus("1"); + // 保存支付订单 + insertByBo(paymentOrder); + return paymentOrder; + } + + /** + * 修改订单状态为已支付 + * + */ + @Override + public void updatePayOrder(OrderRequest orderRequest) { + PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); + paymentOrdersBo.setOrderNo(orderRequest.getOrderNo()); + List paymentOrdersList = queryList(paymentOrdersBo); + if (CollectionUtil.isEmpty(paymentOrdersList)){ + throw new BaseException("订单不存在!"); + } + // 根据价格查询套餐 + SysPackagePlanBo sysPackagePlanBo = new SysPackagePlanBo(); + sysPackagePlanBo.setPrice(new BigDecimal(orderRequest.getMoney())); + SysPackagePlanVo sysPackagePlanVo = planService.queryList(sysPackagePlanBo).get(0); + + + // 订单状态修改为已支付 + PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); + // 1 未支付 2未支付 + paymentOrdersVo.setPaymentStatus("2"); + paymentOrdersVo.setPaymentMethod(orderRequest.getPayType()); + BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo); + updateByBo(paymentOrdersBo); + // 用户充值费用 + double money = paymentOrdersBo.getAmount().doubleValue(); + SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId()); + sysUserVo.setUserBalance(sysUserVo.getUserBalance() + money); + SysUserBo sysUserBo = new SysUserBo(); + BeanUtil.copyProperties(sysUserVo,sysUserBo); + // 设置为付费用户 + sysUserBo.setUserGrade("1"); + sysUserBo.setUserPlan(sysPackagePlanVo.getId().toString()); + userService.updateUser(sysUserBo); + } + + /** + * 查询支付订单列表 + */ + @Override + public TableDataInfo queryPageList(PaymentOrdersBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + if(CollectionUtil.isEmpty(result.getRecords())){ + return TableDataInfo.build(result); + } + // 获取所有userId + List userIds = result.getRecords().stream() + .map(PaymentOrdersVo::getUserId) + .collect(Collectors.toList()); + // 一次性查询所有userName + Map userIdToUserNameMap = getUserNamesByUserIds(userIds); + // 设置userName + result.getRecords().forEach(paymentOrderVo -> { + paymentOrderVo.setUserName(userIdToUserNameMap.get(paymentOrderVo.getUserId())); + }); + return TableDataInfo.build(result); + } + private Map getUserNamesByUserIds(List userIds) { + // 实现批量查询userName的逻辑,例如通过sysUserMapper查询sys_user表 + List sysUsers = sysUserMapper.selectBatchIds(userIds); + return sysUsers.stream() + .collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName)); + } + + /** + * 查询支付订单列表 + */ + @Override + public List queryList(PaymentOrdersBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(PaymentOrdersBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), PaymentOrder::getOrderNo, bo.getOrderNo()); + lqw.like(StringUtils.isNotBlank(bo.getOrderName()), PaymentOrder::getOrderName, bo.getOrderName()); + lqw.eq(bo.getAmount() != null, PaymentOrder::getAmount, bo.getAmount()); + lqw.eq(StringUtils.isNotBlank(bo.getPaymentStatus()), PaymentOrder::getPaymentStatus, bo.getPaymentStatus()); + lqw.eq(StringUtils.isNotBlank(bo.getPaymentMethod()), PaymentOrder::getPaymentMethod, bo.getPaymentMethod()); + lqw.eq(bo.getUserId() != null, PaymentOrder::getUserId, bo.getUserId()); + return lqw; + } + + /** + * 新增支付订单 + */ + @Override + public Boolean insertByBo(PaymentOrdersBo bo) { + PaymentOrder add = MapstructUtils.convert(bo, PaymentOrder.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改支付订单 + */ + @Override + public Boolean updateByBo(PaymentOrdersBo bo) { + PaymentOrder update = MapstructUtils.convert(bo, PaymentOrder.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(PaymentOrder entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除支付订单 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java new file mode 100644 index 00000000..610b025e --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java @@ -0,0 +1,524 @@ +package org.ruoyi.system.service.impl; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson.JSONObject; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.ruoyi.common.chat.config.ChatConfig; +import org.ruoyi.common.chat.config.LocalCache; +import org.ruoyi.common.chat.domain.request.ChatRequest; +import org.ruoyi.common.chat.domain.request.Dall3Request; +import org.ruoyi.common.chat.entity.Tts.TextToSpeech; +import org.ruoyi.common.chat.entity.chat.*; +import org.ruoyi.common.chat.entity.files.UploadFileResponse; +import org.ruoyi.common.chat.entity.images.Image; +import org.ruoyi.common.chat.entity.images.ImageResponse; +import org.ruoyi.common.chat.entity.images.Item; +import org.ruoyi.common.chat.entity.images.ResponseFormat; +import org.ruoyi.common.chat.entity.whisper.WhisperResponse; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.utils.TikTokensUtil; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.bo.ChatMessageBo; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.listener.SSEEventSourceListener; +import org.ruoyi.system.service.*; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class SseServiceImpl implements ISseService { + + private OpenAiStreamClient openAiStreamClient; + + private final ChatConfig chatConfig; + + private final IChatCostService chatService; + + private final IChatMessageService chatMessageService; + + private final ISysModelService sysModelService; + + private final ISysUserService userService; + + private final ConfigService configService; + + static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build(); + + private final ISysPackagePlanService sysPackagePlanService; + + + @Override + public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) { + openAiStreamClient = chatConfig.getOpenAiStreamClient(); + SseEmitter sseEmitter = new SseEmitter(0L); + SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter); + // 获取对话消息列表 + List messages = chatRequest.getMessages(); + try { + if (StpUtil.isLogin()) { + SysUserVo sysUserVo = userService.selectUserById(getUserId()); + if (!checkModel(sysUserVo.getUserPlan(), chatRequest.getModel())) { + throw new BaseException("当前套餐不支持此模型!"); + } + LocalCache.CACHE.put("userId", getUserId()); + + Object content = messages.get(0).getContent(); + String chatString = ""; + if (content instanceof List listContent) { + if (!listContent.isEmpty() && listContent.get(0) instanceof Content) { + chatString = ((Content) listContent.get(0)).getText(); + } + } else if (content instanceof String) { + chatString = (String) content; + } + + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setUserId(getUserId()); + chatMessageBo.setModelName(chatRequest.getModel()); + + chatMessageBo.setContent(chatString); + String configValue = getKey("enabled"); + if (Boolean.parseBoolean(configValue)) { + // 判断文本是否合规 + String type = textReview(chatString); + // 审核状态 1 代表合法 + if (!"1".equals(type) && StringUtils.isNotEmpty(type)) { + throw new BaseException("文本不合规,请修改!"); + } + } + //根据模型名称查询模型信息 + SysModelBo sysModelBo = new SysModelBo(); + // 如果是gpts系列模型 + if (chatRequest.getModel().startsWith("gpt-4-gizmo")) { + sysModelBo.setModelName("gpt-4-gizmo"); + } else { + sysModelBo.setModelName(chatRequest.getModel()); + } + List sysModelList = sysModelService.queryList(sysModelBo); + + if (CollectionUtil.isEmpty(sysModelList)) { + // 如果模型不存在默认使用token扣费方式 + processByToken(chatRequest.getModel(), chatString, chatMessageBo); + } else { + openAiStreamClient = chatConfig.createOpenAiStreamClient(sysModelList.get(0).getApiHost(), sysModelList.get(0).getApiKey()); + // 模型设置默认提示词 + SysModelVo firstModel = sysModelList.get(0); + if (StringUtils.isNotEmpty(firstModel.getSystemPrompt())) { + Message sysMessage = Message.builder().content(firstModel.getSystemPrompt()).role(Message.Role.SYSTEM).build(); + messages.add(sysMessage); + } + // 计费类型: 1 token扣费 2 次数扣费 + if ("2".equals(firstModel.getModelType())) { + processByModelPrice(firstModel, chatMessageBo); + } else { + processByToken(chatRequest.getModel(), chatString, chatMessageBo); + } + } + } else { + if (checkModel("Visitor", chatRequest.getModel())) { + // 初始请求次数 + int number = 1; + // 获取请求IP + String realIp = getClientIpAddress(request); + // 根据IP获取次数 + Integer requestNumber = RedisUtils.getCacheObject(realIp); + if (requestNumber == null) { + // 记录ip使用次数 + RedisUtils.setCacheObject(realIp, number); + } else { + String configValue = configService.getConfigValue("mail", "free"); + if (requestNumber > Integer.parseInt(configValue)) { + throw new BaseException("剩余次数不足,请充值后使用"); + } + RedisUtils.setCacheObject(realIp, requestNumber + 1); + } + } else { + throw new BaseException("当前套餐不支持此模型!"); + } + } + ChatCompletion completion = ChatCompletion + .builder() + .messages(messages) + .model(chatRequest.getModel()) + .temperature(chatRequest.getTemperature()) + .topP(chatRequest.getTop_p()) + .stream(true) + .build(); + openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener); + } catch (Exception e) { + String message = e.getMessage(); + sendErrorEvent(sseEmitter, message); + return sseEmitter; + } + return sseEmitter; + } + + + /** + * 查当前用户是否可以调用此模型 + * + * @param planId + * @return + */ + public Boolean checkModel(String planId, String modelName) { + SysPackagePlanBo sysPackagePlanBo = new SysPackagePlanBo(); + if (modelName.startsWith("gpt-4-gizmo")) { + modelName = "gpt-4-gizmo"; + } + if (StringUtils.isEmpty(planId)) { + sysPackagePlanBo.setName("Visitor"); + } else if ("Visitor".equals(planId) || "Free".equals(planId)) { + sysPackagePlanBo.setName(planId); + } else { + // sysPackagePlanBo.setId(Long.valueOf(planId)); + return true; + } + + SysPackagePlanVo sysPackagePlanVo = sysPackagePlanService.queryList(sysPackagePlanBo).get(0); + // 将字符串转换为数组 + String[] array = sysPackagePlanVo.getPlanDetail().split(","); + return Arrays.asList(array).contains(modelName); + } + + /** + * 根据次数扣除余额 + * + * @param model 模型信息 + * @param chatMessageBo 对话信息 + */ + private void processByModelPrice(SysModelVo model, ChatMessageBo chatMessageBo) { + double cost = model.getModelPrice(); + chatService.deductUserBalance(getUserId(), cost); + chatMessageBo.setDeductCost(cost); + chatMessageService.insertByBo(chatMessageBo); + } + + /** + * 根据token扣除余额 + * + * @param modelName 模型名称 + * @param text 消息内容 + * @param chatMessageBo 消息记录 + */ + private void processByToken(String modelName, String text, ChatMessageBo chatMessageBo) { + int tokens = TikTokensUtil.tokens(modelName, text); + chatMessageBo.setTotalTokens(tokens); + chatService.deductToken(chatMessageBo); + } + + /** + * 文字转语音 + */ + @Override + public ResponseEntity textToSpeed(TextToSpeech textToSpeech) { + + try (ResponseBody body = openAiStreamClient.textToSpeech(textToSpeech)) { + if (body != null) { + // 将ResponseBody转换为InputStreamResource + InputStreamResource resource = new InputStreamResource(body.byteStream()); + + // 创建并返回ResponseEntity + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("audio/mpeg")) + .body(resource); + + } else { + // 如果ResponseBody为空,返回404状态码 + return ResponseEntity.notFound().build(); + } + } + } + + /** + * 语音转文字 + */ + @Override + public WhisperResponse speechToTextTranscriptionsV2(MultipartFile file) { + // 确保文件不为空 + if (file.isEmpty()) { + throw new IllegalStateException("Cannot convert an empty MultipartFile"); + } + // 创建一个文件对象 + File fileA = new File(System.getProperty("java.io.tmpdir") + File.separator + file.getOriginalFilename()); + try { + // 将 MultipartFile 的内容写入文件 + file.transferTo(fileA); + } catch (IOException e) { + throw new RuntimeException("Failed to convert MultipartFile to File", e); + } + return openAiStreamClient.speechToTextTranscriptions(fileA); + } + + @Override + public String chat(ChatRequest chatRequest, String userId) { +// chatService.deductUserBalance(Long.valueOf(userId), 0.01); +// // 保存消息记录 +// ChatMessageBo chatMessageBo = new ChatMessageBo(); +// chatMessageBo.setUserId(Long.valueOf(userId)); +// chatMessageBo.setModelName(ChatCompletion.Model.GPT_3_5_TURBO.getName()); +// chatMessageBo.setContent(chatRequest.getPrompt()); +// chatMessageBo.setDeductCost(0.01); +// chatMessageBo.setTotalTokens(0); +// chatMessageService.insertByBo(chatMessageBo); +// +// openAiStreamClient = chatConfig.getOpenAiStreamClient(); +// Message message = Message.builder().role(Message.Role.USER).content(chatRequest.getPrompt()).build(); +// ChatCompletion chatCompletion = ChatCompletion +// .builder() +// .messages(Collections.singletonList(message)) +// .model(chatRequest.getModel()) +// .build(); +// ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion); +// return chatCompletionResponse.getChoices().get(0).getMessage().getContent(); + return null; + } + + /** + * dall-e-3绘画接口 + * + * @param request + * @return + */ + public List dall3(Dall3Request request) { + openAiStreamClient = chatConfig.getOpenAiStreamClient(); + chatService.checkUserGrade(); + // DALL3 绘图模型 + Image image = Image.builder() + .responseFormat(ResponseFormat.URL.getName()) + .model(Image.Model.DALL_E_3.getName()) + .prompt(request.getPrompt()) + .n(1) + .quality(request.getQuality()) + .size(request.getSize()) + .style(request.getStyle()) + .build(); + ImageResponse imageResponse = openAiStreamClient.genImages(image); + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName(request.getModel()); + List sysModelList = sysModelService.queryList(sysModelBo); + //chatService.deductUserBalance(getUserId(),sysModelList.get(0).getModelPrice()); + // 保存消息记录 + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setUserId(getUserId()); + chatMessageBo.setModelName(Image.Model.DALL_E_3.getName()); + chatMessageBo.setContent(request.getPrompt()); + chatMessageBo.setDeductCost(sysModelList.get(0).getModelPrice()); + chatMessageBo.setTotalTokens(0); + chatMessageService.insertByBo(chatMessageBo); + return imageResponse.getData(); + } + + @Override + public List wxDall(String prompt, String userId) { + openAiStreamClient = chatConfig.getOpenAiStreamClient(); + // DALL3 绘图模型 + Image image = Image.builder() + .responseFormat(ResponseFormat.URL.getName()) + .model(Image.Model.DALL_E_3.getName()) + .prompt(prompt) + .n(1) + .build(); + ImageResponse imageResponse = openAiStreamClient.genImages(image); + SysModelBo sysModelBo = new SysModelBo(); + sysModelBo.setModelName("dall3"); + List sysModelList = sysModelService.queryList(sysModelBo); + chatService.deductUserBalance(Long.valueOf(userId), 0.3); + // 保存消息记录 + ChatMessageBo chatMessageBo = new ChatMessageBo(); + chatMessageBo.setUserId(getUserId()); + chatMessageBo.setModelName(Image.Model.DALL_E_3.getName()); + chatMessageBo.setContent(prompt); + chatMessageBo.setDeductCost(sysModelList.get(0).getModelPrice()); + chatMessageBo.setTotalTokens(0); + chatMessageService.insertByBo(chatMessageBo); + return imageResponse.getData(); + } + + /** + * 获取用户Id + * + * @return + */ + public Long getUserId() { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new BaseException("用户未登录!"); + } + return loginUser.getUserId(); + } + + @Override + public UploadFileResponse upload(MultipartFile file) { + openAiStreamClient = chatConfig.getOpenAiStreamClient(); + return openAiStreamClient.uploadFile("fine-tune", convertMultiPartToFile(file)); + } + + private File convertMultiPartToFile(MultipartFile multipartFile) { + File file = null; + try { + // 获取原始文件名 + String originalFileName = multipartFile.getOriginalFilename(); + // 默认扩展名 + String extension = ".tmp"; + // 尝试从原始文件名中获取扩展名 + if (originalFileName != null && originalFileName.contains(".")) { + extension = originalFileName.substring(originalFileName.lastIndexOf(".")); + } + + // 使用原始文件的扩展名创建临时文件 + Path tempFile = Files.createTempFile(null, extension); + file = tempFile.toFile(); + + // 将MultipartFile的内容写入文件 + try (InputStream inputStream = multipartFile.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(file)) { + int read; + byte[] bytes = new byte[1024]; + while ((read = inputStream.read(bytes)) != -1) { + outputStream.write(bytes, 0, read); + } + } catch (IOException e) { + // 处理文件写入异常 + e.printStackTrace(); + } + } catch (IOException e) { + // 处理临时文件创建异常 + e.printStackTrace(); + } + return file; + } + + // 发送SSE错误事件的封装方法 + private void sendErrorEvent(SseEmitter sseEmitter, String errorMessage) { + SseEmitter.SseEventBuilder event = SseEmitter.event() + .name("error") + .data(errorMessage); + try { + sseEmitter.send(event); + } catch (IOException e) { + log.error("发送事件失败: {}", e.getMessage()); + } + sseEmitter.complete(); + } + + /** + * 文本内容审核 + * + * @param msg + * @return String + * @Date 2023/5/27 + **/ + public String textReview(String msg) { + String conclusionType = ""; + try { + String text = URLEncoder.encode(msg); + okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/x-www-form-urlencoded"); + RequestBody body = RequestBody.create(mediaType, "text=" + text); + Request request = new Request.Builder() + .url("https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token=" + getAccessToken()) + .method("POST", body) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .addHeader("Accept", "application/json") + .build(); + Response response = HTTP_CLIENT.newCall(request).execute(); + JSONObject jsonObject = JSONObject.parseObject(response.body().string()); + conclusionType = jsonObject.getString("conclusionType"); + } catch (IOException e) { + log.info("发生错误{}", e.getMessage()); + } + return conclusionType; + } + + /** + * 从用户的AK,SK生成鉴权签名(Access Token) + * + * @return 鉴权签名(Access Token) + * @throws IOException IO异常 + */ + public String getAccessToken() throws IOException { + okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/x-www-form-urlencoded"); + RequestBody body = RequestBody.create(mediaType, "grant_type=client_credentials&client_id=" + getKey("apiKey") + + "&client_secret=" + getKey("secretKey")); + Request request = new Request.Builder() + .url("https://aip.baidubce.com/oauth/2.0/token") + .method("POST", body) + .addHeader("Content-Type", "application/x-www-form-urlencoded") + .build(); + Response response = HTTP_CLIENT.newCall(request).execute(); + return JSONObject.parseObject(response.body().string()).getString("access_token"); + } + + public String getKey(String key) { + return configService.getConfigValue("review", key); + } + + /** + * 获取客户端的 IP 地址 + * + * @param request HTTP 请求对象 + * @return 客户端的 IP 地址,如果无法获取则返回 "unknown" + */ + public static String getClientIpAddress(HttpServletRequest request) { + String ipAddress = request.getHeader("X-Forwarded-For"); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress.split(",")[0].trim(); + } + + ipAddress = request.getHeader("Proxy-Client-IP"); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress; + } + + ipAddress = request.getHeader("WL-Proxy-Client-IP"); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress; + } + + ipAddress = request.getHeader("HTTP_CLIENT_IP"); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress; + } + + ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR"); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress; + } + + ipAddress = request.getRemoteAddr(); + if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) { + return ipAddress; + } + + return "unknown"; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 00000000..fbefa717 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,203 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.system.domain.SysConfig; +import org.ruoyi.system.domain.bo.SysConfigBo; +import org.ruoyi.system.domain.vo.SysConfigVo; +import org.ruoyi.system.mapper.SysConfigMapper; +import org.ruoyi.system.service.ISysConfigService; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * 参数配置 服务层实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysConfigServiceImpl implements ISysConfigService { + + private final SysConfigMapper baseMapper; + + @Override + public TableDataInfo selectPageConfigList(SysConfigBo config, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(config); + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + @DS("master") + public SysConfigVo selectConfigById(Long configId) { + return baseMapper.selectVoById(configId); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Cacheable(cacheNames = CacheNames.SYS_CONFIG, key = "#configKey") + @Override + public String selectConfigByKey(String configKey) { + SysConfig retConfig = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(SysConfig::getConfigKey, configKey)); + if (ObjectUtil.isNotNull(retConfig)) { + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取注册开关 + * @param tenantId 租户id + * @return true开启,false关闭 + */ + @Override + public boolean selectRegisterEnabled(String tenantId) { + SysConfig retConfig = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(SysConfig::getConfigKey, "sys.account.registerUser") + .eq(TenantHelper.isEnable(),SysConfig::getTenantId, tenantId)); + if (ObjectUtil.isNull(retConfig)) { + return false; + } + return Convert.toBool(retConfig.getConfigValue()); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfigBo config) { + LambdaQueryWrapper lqw = buildQueryWrapper(config); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysConfigBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getConfigName()), SysConfig::getConfigName, bo.getConfigName()); + lqw.eq(StringUtils.isNotBlank(bo.getConfigType()), SysConfig::getConfigType, bo.getConfigType()); + lqw.like(StringUtils.isNotBlank(bo.getConfigKey()), SysConfig::getConfigKey, bo.getConfigKey()); + lqw.between(params.get("beginTime") != null && params.get("endTime") != null, + SysConfig::getCreateTime, params.get("beginTime"), params.get("endTime")); + return lqw; + } + + /** + * 新增参数配置 + * + * @param bo 参数配置信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#bo.configKey") + @Override + public String insertConfig(SysConfigBo bo) { + SysConfig config = MapstructUtils.convert(bo, SysConfig.class); + int row = baseMapper.insert(config); + if (row > 0) { + return config.getConfigValue(); + } + throw new ServiceException("操作失败"); + } + + /** + * 修改参数配置 + * + * @param bo 参数配置信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#bo.configKey") + @Override + public String updateConfig(SysConfigBo bo) { + int row = 0; + SysConfig config = MapstructUtils.convert(bo, SysConfig.class); + if (config.getConfigId() != null) { + SysConfig temp = baseMapper.selectById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) { + CacheUtils.evict(CacheNames.SYS_CONFIG, temp.getConfigKey()); + } + row = baseMapper.updateById(config); + } else { + row = baseMapper.update(config, new LambdaQueryWrapper() + .eq(SysConfig::getConfigKey, config.getConfigKey())); + } + if (row > 0) { + return config.getConfigValue(); + } + throw new ServiceException("操作失败"); + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) { + for (Long configId : configIds) { + SysConfig config = baseMapper.selectById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey()); + } + baseMapper.deleteBatchIds(Arrays.asList(configIds)); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() { + CacheUtils.clear(CacheNames.SYS_CONFIG); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfigBo config) { + long configId = ObjectUtil.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = baseMapper.selectOne(new LambdaQueryWrapper().eq(SysConfig::getConfigKey, config.getConfigKey())); + if (ObjectUtil.isNotNull(info) && info.getConfigId() != configId) { + return false; + } + return true; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDataScopeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDataScopeServiceImpl.java new file mode 100644 index 00000000..4e1df984 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDataScopeServiceImpl.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.mybatis.helper.DataBaseHelper; +import org.ruoyi.system.domain.SysDept; +import org.ruoyi.system.domain.SysRoleDept; +import org.ruoyi.system.mapper.SysDeptMapper; +import org.ruoyi.system.mapper.SysRoleDeptMapper; +import org.ruoyi.system.service.ISysDataScopeService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 数据权限 实现 + *

    + * 注意: 此Service内不允许调用标注`数据权限`注解的方法 + * 例如: deptMapper.selectList 此 selectList 方法标注了`数据权限`注解 会出现循环解析的问题 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service("sdss") +public class SysDataScopeServiceImpl implements ISysDataScopeService { + + private final SysRoleDeptMapper roleDeptMapper; + private final SysDeptMapper deptMapper; + + @Override + public String getRoleCustom(Long roleId) { + List list = roleDeptMapper.selectList( + new LambdaQueryWrapper() + .select(SysRoleDept::getDeptId) + .eq(SysRoleDept::getRoleId, roleId)); + if (CollUtil.isNotEmpty(list)) { + return StreamUtils.join(list, rd -> Convert.toStr(rd.getDeptId())); + } + return null; + } + + @Override + public String getDeptAndChild(Long deptId) { + List deptList = deptMapper.selectList(new LambdaQueryWrapper() + .select(SysDept::getDeptId) + .apply(DataBaseHelper.findInSet(deptId, "ancestors"))); + List ids = StreamUtils.toList(deptList, SysDept::getDeptId); + ids.add(deptId); + List list = deptMapper.selectList(new LambdaQueryWrapper() + .select(SysDept::getDeptId) + .in(SysDept::getDeptId, ids)); + if (CollUtil.isNotEmpty(list)) { + return StreamUtils.join(list, d -> Convert.toStr(d.getDeptId())); + } + return null; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDeptServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 00000000..d7995f95 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,325 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.DeptService; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.TreeBuildUtils; +import org.ruoyi.common.mybatis.helper.DataBaseHelper; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysDept; +import org.ruoyi.system.domain.SysRole; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysDeptBo; +import org.ruoyi.system.domain.vo.SysDeptVo; +import org.ruoyi.system.mapper.SysDeptMapper; +import org.ruoyi.system.mapper.SysRoleMapper; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.ISysDeptService; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 部门管理 服务实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysDeptServiceImpl implements ISysDeptService, DeptService { + + private final SysDeptMapper baseMapper; + private final SysRoleMapper roleMapper; + private final SysUserMapper userMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + public List selectDeptList(SysDeptBo dept) { + LambdaQueryWrapper lqw = buildQueryWrapper(dept); + return baseMapper.selectDeptList(lqw); + } + + /** + * 查询部门树结构信息 + * + * @param bo 部门信息 + * @return 部门树信息集合 + */ + @Override + public List> selectDeptTreeList(SysDeptBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + List depts = baseMapper.selectList(lqw); + return buildDeptTreeSelect(depts); + } + + private LambdaQueryWrapper buildQueryWrapper(SysDeptBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(SysDept::getDelFlag, "0"); + lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId()); + lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId()); + lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDept::getStatus, bo.getStatus()); + lqw.orderByAsc(SysDept::getParentId); + lqw.orderByAsc(SysDept::getOrderNum); + return lqw; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List> buildDeptTreeSelect(List depts) { + if (CollUtil.isEmpty(depts)) { + return CollUtil.newArrayList(); + } + return TreeBuildUtils.build(depts, (dept, tree) -> + tree.setId(dept.getDeptId()) + .setParentId(dept.getParentId()) + .setName(dept.getDeptName()) + .setWeight(dept.getOrderNum())); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) { + SysRole role = roleMapper.selectById(roleId); + return baseMapper.selectDeptListByRoleId(roleId, role.getDeptCheckStrictly()); + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Cacheable(cacheNames = CacheNames.SYS_DEPT, key = "#deptId") + @Override + public SysDeptVo selectDeptById(Long deptId) { + SysDeptVo dept = baseMapper.selectVoById(deptId); + if (ObjectUtil.isNull(dept)) { + return null; + } + SysDeptVo parentDept = baseMapper.selectVoOne(new LambdaQueryWrapper() + .select(SysDept::getDeptName).eq(SysDept::getDeptId, dept.getParentId())); + dept.setParentName(ObjectUtil.isNotNull(parentDept) ? parentDept.getDeptName() : null); + return dept; + } + + /** + * 通过部门ID查询部门名称 + * + * @param deptIds 部门ID串逗号分隔 + * @return 部门名称串逗号分隔 + */ + @Override + public String selectDeptNameByIds(String deptIds) { + List list = new ArrayList<>(); + for (Long id : StringUtils.splitTo(deptIds, Convert::toLong)) { + SysDeptVo vo = SpringUtils.getAopProxy(this).selectDeptById(id); + if (ObjectUtil.isNotNull(vo)) { + list.add(vo.getDeptName()); + } + } + return String.join(StringUtils.SEPARATOR, list); + } + + /** + * 根据ID查询所有子部门数(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public long selectNormalChildrenDeptById(Long deptId) { + return baseMapper.selectCount(new LambdaQueryWrapper() + .eq(SysDept::getStatus, UserConstants.DEPT_NORMAL) + .apply(DataBaseHelper.findInSet(deptId, "ancestors"))); + } + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) { + return baseMapper.exists(new LambdaQueryWrapper() + .eq(SysDept::getParentId, deptId)); + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) { + return userMapper.exists(new LambdaQueryWrapper() + .eq(SysUser::getDeptId, deptId)); + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDeptBo dept) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysDept::getDeptName, dept.getDeptName()) + .eq(SysDept::getParentId, dept.getParentId()) + .ne(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId())); + return !exist; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) { + if (ObjectUtil.isNull(deptId)) { + return; + } + if (LoginHelper.isSuperAdmin()) { + return; + } + SysDeptVo dept = baseMapper.selectDeptById(deptId); + if (ObjectUtil.isNull(dept)) { + throw new ServiceException("没有权限访问部门数据!"); + } + } + + /** + * 新增保存部门信息 + * + * @param bo 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDeptBo bo) { + SysDept info = baseMapper.selectById(bo.getParentId()); + // 如果父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) { + throw new ServiceException("部门停用,不允许新增"); + } + SysDept dept = MapstructUtils.convert(bo, SysDept.class); + dept.setAncestors(info.getAncestors() + StringUtils.SEPARATOR + dept.getParentId()); + return baseMapper.insert(dept); + } + + /** + * 修改保存部门信息 + * + * @param bo 部门信息 + * @return 结果 + */ + @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#bo.deptId") + @Override + public int updateDept(SysDeptBo bo) { + SysDept dept = MapstructUtils.convert(bo, SysDept.class); + SysDept oldDept = baseMapper.selectById(dept.getDeptId()); + if (!oldDept.getParentId().equals(dept.getParentId())) { + // 如果是新父部门 则校验是否具有新父部门权限 避免越权 + this.checkDeptDataScope(dept.getParentId()); + SysDept newParentDept = baseMapper.selectById(dept.getParentId()); + if (ObjectUtil.isNotNull(newParentDept) && ObjectUtil.isNotNull(oldDept)) { + String newAncestors = newParentDept.getAncestors() + StringUtils.SEPARATOR + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + } + int result = baseMapper.updateById(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals(UserConstants.DEPT_NORMAL, dept.getAncestors())) { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + baseMapper.update(null, new LambdaUpdateWrapper() + .set(SysDept::getStatus, UserConstants.DEPT_NORMAL) + .in(SysDept::getDeptId, Arrays.asList(deptIds))); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + private void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) { + List children = baseMapper.selectList(new LambdaQueryWrapper() + .apply(DataBaseHelper.findInSet(deptId, "ancestors"))); + List list = new ArrayList<>(); + for (SysDept child : children) { + SysDept dept = new SysDept(); + dept.setDeptId(child.getDeptId()); + dept.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + list.add(dept); + } + if (CollUtil.isNotEmpty(list)) { + if (baseMapper.updateBatchById(list)) { + list.forEach(dept -> CacheUtils.evict(CacheNames.SYS_DEPT, dept.getDeptId())); + } + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#deptId") + @Override + public int deleteDeptById(Long deptId) { + return baseMapper.deleteById(deptId); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictDataServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 00000000..7bfdf9dc --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,139 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.system.domain.SysDictData; +import org.ruoyi.system.domain.bo.SysDictDataBo; +import org.ruoyi.system.domain.vo.SysDictDataVo; +import org.ruoyi.system.mapper.SysDictDataMapper; +import org.ruoyi.system.service.ISysDictDataService; +import org.springframework.cache.annotation.CachePut; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 字典 业务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysDictDataServiceImpl implements ISysDictDataService { + + private final SysDictDataMapper baseMapper; + + @Override + public TableDataInfo selectPageDictDataList(SysDictDataBo dictData, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(dictData); + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictDataBo dictData) { + LambdaQueryWrapper lqw = buildQueryWrapper(dictData); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysDictDataBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getDictSort() != null, SysDictData::getDictSort, bo.getDictSort()); + lqw.like(StringUtils.isNotBlank(bo.getDictLabel()), SysDictData::getDictLabel, bo.getDictLabel()); + lqw.eq(StringUtils.isNotBlank(bo.getDictType()), SysDictData::getDictType, bo.getDictType()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDictData::getStatus, bo.getStatus()); + lqw.orderByAsc(SysDictData::getDictSort); + return lqw; + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) { + return baseMapper.selectOne(new LambdaQueryWrapper() + .select(SysDictData::getDictLabel) + .eq(SysDictData::getDictType, dictType) + .eq(SysDictData::getDictValue, dictValue)) + .getDictLabel(); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictDataVo selectDictDataById(Long dictCode) { + return baseMapper.selectVoById(dictCode); + } + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) { + for (Long dictCode : dictCodes) { + SysDictData data = baseMapper.selectById(dictCode); + baseMapper.deleteById(dictCode); + CacheUtils.evict(CacheNames.SYS_DICT, data.getDictType()); + } + } + + /** + * 新增保存字典数据信息 + * + * @param bo 字典数据信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType") + @Override + public List insertDictData(SysDictDataBo bo) { + SysDictData data = MapstructUtils.convert(bo, SysDictData.class); + int row = baseMapper.insert(data); + if (row > 0) { + return baseMapper.selectDictDataByType(data.getDictType()); + } + throw new ServiceException("操作失败"); + } + + /** + * 修改保存字典数据信息 + * + * @param bo 字典数据信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType") + @Override + public List updateDictData(SysDictDataBo bo) { + SysDictData data = MapstructUtils.convert(bo, SysDictData.class); + int row = baseMapper.updateById(data); + if (row > 0) { + return baseMapper.selectDictDataByType(data.getDictType()); + } + throw new ServiceException("操作失败"); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 00000000..b429edff --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,268 @@ +package org.ruoyi.system.service.impl; + +import cn.dev33.satoken.context.SaHolder; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.core.constant.CacheConstants; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.DictService; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.system.domain.SysDictData; +import org.ruoyi.system.domain.SysDictType; +import org.ruoyi.system.domain.bo.SysDictTypeBo; +import org.ruoyi.system.domain.vo.SysDictDataVo; +import org.ruoyi.system.domain.vo.SysDictTypeVo; +import org.ruoyi.system.mapper.SysDictDataMapper; +import org.ruoyi.system.mapper.SysDictTypeMapper; +import org.ruoyi.system.service.ISysDictTypeService; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 字典 业务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService { + + private final SysDictTypeMapper baseMapper; + private final SysDictDataMapper dictDataMapper; + + @Override + public TableDataInfo selectPageDictTypeList(SysDictTypeBo dictType, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(dictType); + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictTypeBo dictType) { + LambdaQueryWrapper lqw = buildQueryWrapper(dictType); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysDictTypeBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getDictName()), SysDictType::getDictName, bo.getDictName()); + lqw.like(StringUtils.isNotBlank(bo.getDictType()), SysDictType::getDictType, bo.getDictType()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysDictType::getStatus, bo.getStatus()); + lqw.between(params.get("beginTime") != null && params.get("endTime") != null, + SysDictType::getCreateTime, params.get("beginTime"), params.get("endTime")); + return lqw; + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() { + return baseMapper.selectVoList(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + // @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType") + @Override + public List selectDictDataByType(String dictType) { + List dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (CollUtil.isNotEmpty(dictDatas)) { + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictTypeVo selectDictTypeById(Long dictId) { + return baseMapper.selectVoById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType") + @Override + public SysDictTypeVo selectDictTypeByType(String dictType) { + return baseMapper.selectVoById(new LambdaQueryWrapper().eq(SysDictType::getDictType, dictType)); + } + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) { + for (Long dictId : dictIds) { + SysDictType dictType = baseMapper.selectById(dictId); + if (dictDataMapper.exists(new LambdaQueryWrapper() + .eq(SysDictData::getDictType, dictType.getDictType()))) { + throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); + } + CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType()); + } + baseMapper.deleteBatchIds(Arrays.asList(dictIds)); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() { + CacheUtils.clear(CacheNames.SYS_DICT); + } + + /** + * 新增保存字典类型信息 + * + * @param bo 字典类型信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType") + @Override + public List insertDictType(SysDictTypeBo bo) { + SysDictType dict = MapstructUtils.convert(bo, SysDictType.class); + int row = baseMapper.insert(dict); + if (row > 0) { + return new ArrayList<>(); + } + throw new ServiceException("操作失败"); + } + + /** + * 修改保存字典类型信息 + * + * @param bo 字典类型信息 + * @return 结果 + */ + @CachePut(cacheNames = CacheNames.SYS_DICT, key = "#bo.dictType") + @Override + @Transactional(rollbackFor = Exception.class) + public List updateDictType(SysDictTypeBo bo) { + SysDictType dict = MapstructUtils.convert(bo, SysDictType.class); + SysDictType oldDict = baseMapper.selectById(dict.getDictId()); + dictDataMapper.update(null, new LambdaUpdateWrapper() + .set(SysDictData::getDictType, dict.getDictType()) + .eq(SysDictData::getDictType, oldDict.getDictType())); + int row = baseMapper.updateById(dict); + if (row > 0) { + CacheUtils.evict(CacheNames.SYS_DICT, oldDict.getDictType()); + return dictDataMapper.selectDictDataByType(dict.getDictType()); + } + throw new ServiceException("操作失败"); + } + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictTypeBo dictType) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysDictType::getDictType, dictType.getDictType()) + .ne(ObjectUtil.isNotNull(dictType.getDictId()), SysDictType::getDictId, dictType.getDictId())); + return !exist; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + @SuppressWarnings("unchecked cast") + @Override + public String getDictLabel(String dictType, String dictValue, String separator) { + // 优先从本地缓存获取 + List datas = (List) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType); + if (ObjectUtil.isNull(datas)) { + datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType); + SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas); + } + + Map map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel); + if (StringUtils.containsAny(dictValue, separator)) { + return Arrays.stream(dictValue.split(separator)) + .map(v -> map.getOrDefault(v, StringUtils.EMPTY)) + .collect(Collectors.joining(separator)); + } else { + return map.getOrDefault(dictValue, StringUtils.EMPTY); + } + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + @SuppressWarnings("unchecked cast") + @Override + public String getDictValue(String dictType, String dictLabel, String separator) { + // 优先从本地缓存获取 + List datas = (List) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType); + if (ObjectUtil.isNull(datas)) { + datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType); + SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas); + } + + Map map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue); + if (StringUtils.containsAny(dictLabel, separator)) { + return Arrays.stream(dictLabel.split(separator)) + .map(l -> map.getOrDefault(l, StringUtils.EMPTY)) + .collect(Collectors.joining(separator)); + } else { + return map.getOrDefault(dictLabel, StringUtils.EMPTY); + } + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 00000000..a6b35300 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,160 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.ServletUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.ip.AddressUtils; +import org.ruoyi.common.log.event.LogininforEvent; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysLogininfor; +import org.ruoyi.system.domain.bo.SysLogininforBo; +import org.ruoyi.system.domain.vo.SysLogininforVo; +import org.ruoyi.system.mapper.SysLogininforMapper; +import org.ruoyi.system.service.ISysLogininforService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Slf4j +@Service +public class SysLogininforServiceImpl implements ISysLogininforService { + + private final SysLogininforMapper baseMapper; + + /** + * 记录登录信息 + * + * @param logininforEvent 登录事件 + */ + @Async + @EventListener + public void recordLogininfor(LogininforEvent logininforEvent) { + HttpServletRequest request = logininforEvent.getRequest(); + final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); + final String ip = ServletUtils.getClientIP(request); + + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(getBlock(ip)); + s.append(address); + s.append(getBlock(logininforEvent.getUsername())); + s.append(getBlock(logininforEvent.getStatus())); + s.append(getBlock(logininforEvent.getMessage())); + // 打印信息到日志 + log.info(s.toString(), logininforEvent.getArgs()); + // 获取客户端操作系统 + String os = userAgent.getOs().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininforBo logininfor = new SysLogininforBo(); + logininfor.setTenantId(logininforEvent.getTenantId()); + logininfor.setUserName(logininforEvent.getUsername()); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(logininforEvent.getMessage()); + // 日志状态 + if (StringUtils.equalsAny(logininforEvent.getStatus(), Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) { + logininfor.setStatus(Constants.SUCCESS); + } else if (Constants.LOGIN_FAIL.equals(logininforEvent.getStatus())) { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + insertLogininfor(logininfor); + } + + private String getBlock(Object msg) { + if (msg == null) { + msg = ""; + } + return "[" + msg.toString() + "]"; + } + + @Override + public TableDataInfo selectPageLogininforList(SysLogininforBo logininfor, PageQuery pageQuery) { + Map params = logininfor.getParams(); + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(logininfor.getIpaddr()), SysLogininfor::getIpaddr, logininfor.getIpaddr()) + .eq(StringUtils.isNotBlank(logininfor.getStatus()), SysLogininfor::getStatus, logininfor.getStatus()) + .like(StringUtils.isNotBlank(logininfor.getUserName()), SysLogininfor::getUserName, logininfor.getUserName()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + SysLogininfor::getLoginTime, params.get("beginTime"), params.get("endTime")); + if (StringUtils.isBlank(pageQuery.getOrderByColumn())) { + pageQuery.setOrderByColumn("info_id"); + pageQuery.setIsAsc("desc"); + } + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 新增系统登录日志 + * + * @param bo 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininforBo bo) { + SysLogininfor logininfor = MapstructUtils.convert(bo, SysLogininfor.class); + logininfor.setLoginTime(new Date()); + baseMapper.insert(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininforBo logininfor) { + Map params = logininfor.getParams(); + return baseMapper.selectVoList(new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(logininfor.getIpaddr()), SysLogininfor::getIpaddr, logininfor.getIpaddr()) + .eq(StringUtils.isNotBlank(logininfor.getStatus()), SysLogininfor::getStatus, logininfor.getStatus()) + .like(StringUtils.isNotBlank(logininfor.getUserName()), SysLogininfor::getUserName, logininfor.getUserName()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + SysLogininfor::getLoginTime, params.get("beginTime"), params.get("endTime")) + .orderByDesc(SysLogininfor::getInfoId)); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) { + return baseMapper.deleteBatchIds(Arrays.asList(infoIds)); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() { + baseMapper.delete(new LambdaQueryWrapper<>()); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 00000000..706ebf98 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,365 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.TreeBuildUtils; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysMenu; +import org.ruoyi.system.domain.SysRole; +import org.ruoyi.system.domain.SysRoleMenu; +import org.ruoyi.system.domain.SysTenantPackage; +import org.ruoyi.system.domain.bo.SysMenuBo; +import org.ruoyi.system.domain.vo.MetaVo; +import org.ruoyi.system.domain.vo.RouterVo; +import org.ruoyi.system.domain.vo.SysMenuVo; +import org.ruoyi.system.mapper.SysMenuMapper; +import org.ruoyi.system.mapper.SysRoleMapper; +import org.ruoyi.system.mapper.SysRoleMenuMapper; +import org.ruoyi.system.mapper.SysTenantPackageMapper; +import org.ruoyi.system.service.ISysMenuService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; + +/** + * 菜单 业务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysMenuServiceImpl implements ISysMenuService { + + private final SysMenuMapper baseMapper; + private final SysRoleMapper roleMapper; + private final SysRoleMenuMapper roleMenuMapper; + private final SysTenantPackageMapper tenantPackageMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List selectMenuList(Long userId) { + return selectMenuList(new SysMenuBo(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenuBo menu, Long userId) { + List menuList; + // 管理员显示所有菜单信息 + if (LoginHelper.isSuperAdmin(userId)) { + menuList = baseMapper.selectVoList(new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName()) + .eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible()) + .eq(StringUtils.isNotBlank(menu.getStatus()), SysMenu::getStatus, menu.getStatus()) + .orderByAsc(SysMenu::getParentId) + .orderByAsc(SysMenu::getOrderNum)); + } else { + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("sur.user_id", userId) + .like(StringUtils.isNotBlank(menu.getMenuName()), "m.menu_name", menu.getMenuName()) + .eq(StringUtils.isNotBlank(menu.getVisible()), "m.visible", menu.getVisible()) + .eq(StringUtils.isNotBlank(menu.getStatus()), "m.status", menu.getStatus()) + .orderByAsc("m.parent_id") + .orderByAsc("m.order_num"); + List list = baseMapper.selectMenuListByUserId(wrapper); + menuList = MapstructUtils.convert(list, SysMenuVo.class); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByUserId(Long userId) { + List perms = baseMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(StringUtils.splitList(perm.trim())); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByRoleId(Long roleId) { + List perms = baseMapper.selectMenuPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(StringUtils.splitList(perm.trim())); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List selectMenuTreeByUserId(Long userId) { + List menus; + if (LoginHelper.isSuperAdmin(userId)) { + menus = baseMapper.selectMenuTreeAll(); + } else { + menus = baseMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) { + SysRole role = roleMapper.selectById(roleId); + return baseMapper.selectMenuListByRoleId(roleId, role.getMenuCheckStrictly()); + } + + /** + * 根据租户套餐ID查询菜单树信息 + * + * @param packageId 租户套餐ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByPackageId(Long packageId) { + SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId); + List menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong); + if (CollUtil.isEmpty(menuIds)) { + return List.of(); + } + List parentIds = null; + if (tenantPackage.getMenuCheckStrictly()) { + parentIds = baseMapper.selectObjs(new LambdaQueryWrapper() + .select(SysMenu::getParentId) + .in(SysMenu::getMenuId, menuIds), Convert::toLong); + } + return baseMapper.selectObjs(new LambdaQueryWrapper() + .in(SysMenu::getMenuId, menuIds) + .notIn(CollUtil.isNotEmpty(parentIds), SysMenu::getMenuId, parentIds), Convert::toLong); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) { + List routers = new LinkedList<>(); + for (SysMenu menu : menus) { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(menu.getRouteName()); + router.setPath(menu.getRouterPath()); + router.setComponent(menu.getComponentInfo()); + router.setQuery(menu.getQueryParam()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List cMenus = menu.getChildren(); + if (CollUtil.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } else if (menu.isMenuFrame()) { + router.setMeta(null); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponentInfo()); + children.setName(StringUtils.capitalize(menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQueryParam()); + childrenList.add(children); + router.setChildren(childrenList); + } else if (menu.getParentId().intValue() == 0 && menu.isInnerLink()) { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + String routerPath = SysMenu.innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(StringUtils.capitalize(routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List> buildMenuTreeSelect(List menus) { + if (CollUtil.isEmpty(menus)) { + return CollUtil.newArrayList(); + } + return TreeBuildUtils.build(menus, (menu, tree) -> + tree.setId(menu.getMenuId()) + .setParentId(menu.getParentId()) + .setName(menu.getMenuName()) + .setWeight(menu.getOrderNum())); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenuVo selectMenuById(Long menuId) { + return baseMapper.selectVoById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) { + return baseMapper.exists(new LambdaQueryWrapper().eq(SysMenu::getParentId, menuId)); + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) { + return roleMenuMapper.exists(new LambdaQueryWrapper().eq(SysRoleMenu::getMenuId, menuId)); + } + + /** + * 新增保存菜单信息 + * + * @param bo 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenuBo bo) { + SysMenu menu = MapstructUtils.convert(bo, SysMenu.class); + return baseMapper.insert(menu); + } + + /** + * 修改保存菜单信息 + * + * @param bo 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenuBo bo) { + SysMenu menu = MapstructUtils.convert(bo, SysMenu.class); + return baseMapper.updateById(menu); + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) { + return baseMapper.deleteById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenuBo menu) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysMenu::getMenuName, menu.getMenuName()) + .eq(SysMenu::getParentId, menu.getParentId()) + .ne(ObjectUtil.isNotNull(menu.getMenuId()), SysMenu::getMenuId, menu.getMenuId())); + return !exist; + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + private List getChildPerms(List list, int parentId) { + List returnList = new ArrayList<>(); + for (SysMenu t : list) { + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysMenu t) { + // 得到子节点列表 + List childList = StreamUtils.filter(list, n -> n.getParentId().equals(t.getMenuId())); + t.setChildren(childList); + for (SysMenu tChild : childList) { + // 判断是否有子节点 + if (list.stream().anyMatch(n -> n.getParentId().equals(tChild.getMenuId()))) { + recursionFn(list, tChild); + } + } + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysModelServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysModelServiceImpl.java new file mode 100644 index 00000000..107f9e67 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysModelServiceImpl.java @@ -0,0 +1,110 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysModel; +import org.ruoyi.system.domain.bo.SysModelBo; +import org.ruoyi.system.domain.vo.SysModelVo; +import org.ruoyi.system.mapper.SysModelMapper; +import org.ruoyi.system.service.ISysModelService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; + +/** + * 系统模型Service业务层处理 + * + * @author Lion Li + * @date 2024-04-04 + */ +@RequiredArgsConstructor +@Service +public class SysModelServiceImpl implements ISysModelService { + + private final SysModelMapper baseMapper; + + /** + * 查询系统模型 + */ + @Override + public SysModelVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询系统模型列表 + */ + @Override + public TableDataInfo queryPageList(SysModelBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询系统模型列表 + */ + @Override + public List queryList(SysModelBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysModelBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getModelName()), SysModel::getModelName, bo.getModelName()); + lqw.like(StringUtils.isNotBlank(bo.getModelShow()), SysModel::getModelShow, bo.getModelShow()); + lqw.eq(StringUtils.isNotBlank(bo.getModelDescribe()), SysModel::getModelDescribe, bo.getModelDescribe()); + lqw.eq(StringUtils.isNotBlank(bo.getModelType()), SysModel::getModelType, bo.getModelType()); + return lqw; + } + + /** + * 新增系统模型 + */ + @Override + public Boolean insertByBo(SysModelBo bo) { + SysModel add = MapstructUtils.convert(bo, SysModel.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改系统模型 + */ + @Override + public Boolean updateByBo(SysModelBo bo) { + SysModel update = MapstructUtils.convert(bo, SysModel.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysModel entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除系统模型 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 00000000..8ed0ba99 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,160 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysNotice; +import org.ruoyi.system.domain.SysNoticeState; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.bo.SysNoticeBo; +import org.ruoyi.system.domain.vo.SysNoticeVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.SysNoticeMapper; +import org.ruoyi.system.mapper.SysNoticeStateMapper; +import org.ruoyi.system.mapper.SysUserMapper; +import org.ruoyi.system.service.ISysNoticeService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 公告 服务层实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysNoticeServiceImpl implements ISysNoticeService { + + private final SysNoticeMapper baseMapper; + private final SysNoticeStateMapper noticeStateMapper; + private final SysUserMapper userMapper; + + @Override + public TableDataInfo selectPageNoticeList(SysNoticeBo notice, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(notice); + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNoticeVo selectNoticeById(Long noticeId) { + return baseMapper.selectVoById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public SysNotice getNotice(SysNoticeBo notice) { + LambdaQueryWrapper lqwState = Wrappers.lambdaQuery(); + Long userId = LoginHelper.getLoginUser().getUserId(); + + lqwState.eq(userId != null, SysNoticeState::getUserId, userId); + // 查询未读通知 + lqwState.eq(SysNoticeState::getReadStatus, "0"); + lqwState.orderByDesc(SysNoticeState::getCreateTime); // 按创建时间降序排序 + List states = noticeStateMapper.selectList(lqwState); // 查询公告阅读状态 + SysNoticeState sysNoticeState = states.isEmpty() ? null : states.get(0); // 取第一条记录 + if (sysNoticeState != null) { + return baseMapper.selectById(sysNoticeState.getNoticeId()); + }else { + return null; + } + } + + private LambdaQueryWrapper buildQueryWrapper(SysNoticeBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getNoticeTitle()), SysNotice::getNoticeTitle, bo.getNoticeTitle()); + lqw.eq(StringUtils.isNotBlank(bo.getNoticeType()), SysNotice::getNoticeType, bo.getNoticeType()); + if (StringUtils.isNotBlank(bo.getCreateByName())) { + SysUserVo sysUser = userMapper.selectUserByUserName(bo.getCreateByName()); + lqw.eq(SysNotice::getCreateBy, ObjectUtil.isNotNull(sysUser) ? sysUser.getUserId() : null); + } + return lqw; + } + + /** + * 新增公告 + * + * @param bo 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNoticeBo bo) { + SysNotice notice = MapstructUtils.convert(bo, SysNotice.class); + // 插入公告 + int insert = baseMapper.insert(notice); + // 公告类型(1通知 2公告) + if("1".equals(bo.getNoticeType())){ + // 将之前通知全部设为已读 + noticeStateMapper.readAllNotice(); + // 插入通知阅读状态 + List sysUserList = userMapper.selectList(); + List noticeStateList = new ArrayList<>(); + for (SysUser sysUser : sysUserList) { + SysNoticeState sysNoticeState = new SysNoticeState(); + if (notice != null) { + sysNoticeState.setNoticeId(notice.getNoticeId()); + sysNoticeState.setUserId(sysUser.getUserId()); + noticeStateList.add(sysNoticeState); + } + } + noticeStateMapper.insertBatch(noticeStateList); + } + return insert; + } + + /** + * 修改公告 + * + * @param bo 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNoticeBo bo) { + SysNotice notice = MapstructUtils.convert(bo, SysNotice.class); + return baseMapper.updateById(notice); + + } + + /** + * 删除公告对象 + * + * @param noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) { + return baseMapper.deleteById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) { + return baseMapper.deleteBatchIds(Arrays.asList(noticeIds)); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeStateServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeStateServiceImpl.java new file mode 100644 index 00000000..f107cda6 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysNoticeStateServiceImpl.java @@ -0,0 +1,112 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.SysNoticeStateBo; +import org.ruoyi.system.domain.vo.SysNoticeStateVo; +import org.ruoyi.system.domain.SysNoticeState; +import org.ruoyi.system.mapper.SysNoticeStateMapper; +import org.ruoyi.system.service.ISysNoticeStateService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 用户阅读状态Service业务层处理 + * + * @author Lion Li + * @date 2024-05-11 + */ +@RequiredArgsConstructor +@Service +public class SysNoticeStateServiceImpl implements ISysNoticeStateService { + + private final SysNoticeStateMapper baseMapper; + + /** + * 查询用户阅读状态 + */ + @Override + public SysNoticeStateVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询用户阅读状态列表 + */ + @Override + public TableDataInfo queryPageList(SysNoticeStateBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询用户阅读状态列表 + */ + @Override + public List queryList(SysNoticeStateBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysNoticeStateBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, SysNoticeState::getUserId, bo.getUserId()); + lqw.eq(bo.getNoticeId() != null, SysNoticeState::getNoticeId, bo.getNoticeId()); + lqw.eq(StringUtils.isNotBlank(bo.getReadStatus()), SysNoticeState::getReadStatus, bo.getReadStatus()); + return lqw; + } + + /** + * 新增用户阅读状态 + */ + @Override + public Boolean insertByBo(SysNoticeStateBo bo) { + SysNoticeState add = MapstructUtils.convert(bo, SysNoticeState.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改用户阅读状态 + */ + @Override + public Boolean updateByBo(SysNoticeStateBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + SysNoticeState sysNoticeState = baseMapper.selectOne(lqw); + sysNoticeState.setReadStatus("1"); + return baseMapper.updateById(sysNoticeState) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysNoticeState entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除用户阅读状态 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOperLogServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 00000000..bdbbe8ec --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,144 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.util.ArrayUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.ip.AddressUtils; +import org.ruoyi.common.log.event.OperLogEvent; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysOperLog; +import org.ruoyi.system.domain.bo.SysOperLogBo; +import org.ruoyi.system.domain.vo.SysOperLogVo; +import org.ruoyi.system.mapper.SysOperLogMapper; +import org.ruoyi.system.service.ISysOperLogService; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 操作日志 服务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysOperLogServiceImpl implements ISysOperLogService { + + private final SysOperLogMapper baseMapper; + + /** + * 操作日志记录 + * + * @param operLogEvent 操作日志事件 + */ + @Async + @EventListener + public void recordOper(OperLogEvent operLogEvent) { + SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class); + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + insertOperlog(operLog); + } + + @Override + public TableDataInfo selectPageOperLogList(SysOperLogBo operLog, PageQuery pageQuery) { + Map params = operLog.getParams(); + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle()) + .eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0, + SysOperLog::getBusinessType, operLog.getBusinessType()) + .func(f -> { + if (ArrayUtil.isNotEmpty(operLog.getBusinessTypes())) { + f.in(SysOperLog::getBusinessType, Arrays.asList(operLog.getBusinessTypes())); + } + }) + .eq(operLog.getStatus() != null, + SysOperLog::getStatus, operLog.getStatus()) + .like(StringUtils.isNotBlank(operLog.getOperName()), SysOperLog::getOperName, operLog.getOperName()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + SysOperLog::getOperTime, params.get("beginTime"), params.get("endTime")); + if (StringUtils.isBlank(pageQuery.getOrderByColumn())) { + pageQuery.setOrderByColumn("oper_id"); + pageQuery.setIsAsc("desc"); + } + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 新增操作日志 + * + * @param bo 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLogBo bo) { + SysOperLog operLog = MapstructUtils.convert(bo, SysOperLog.class); + operLog.setOperTime(new Date()); + baseMapper.insert(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLogBo operLog) { + Map params = operLog.getParams(); + return baseMapper.selectVoList(new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle()) + .eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0, + SysOperLog::getBusinessType, operLog.getBusinessType()) + .func(f -> { + if (ArrayUtil.isNotEmpty(operLog.getBusinessTypes())) { + f.in(SysOperLog::getBusinessType, Arrays.asList(operLog.getBusinessTypes())); + } + }) + .eq(operLog.getStatus() != null && operLog.getStatus() > 0, + SysOperLog::getStatus, operLog.getStatus()) + .like(StringUtils.isNotBlank(operLog.getOperName()), SysOperLog::getOperName, operLog.getOperName()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + SysOperLog::getOperTime, params.get("beginTime"), params.get("endTime")) + .orderByDesc(SysOperLog::getOperId)); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) { + return baseMapper.deleteBatchIds(Arrays.asList(operIds)); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLogVo selectOperLogById(Long operId) { + return baseMapper.selectVoById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() { + baseMapper.delete(new LambdaQueryWrapper<>()); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssConfigServiceImpl.java new file mode 100644 index 00000000..bd1f8f3c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssConfigServiceImpl.java @@ -0,0 +1,186 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.json.utils.JsonUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.oss.constant.OssConstant; +import org.ruoyi.common.redis.utils.CacheUtils; +import org.ruoyi.common.redis.utils.RedisUtils; +import org.ruoyi.common.tenant.core.TenantEntity; +import org.ruoyi.common.tenant.helper.TenantHelper; +import org.ruoyi.system.domain.SysOssConfig; +import org.ruoyi.system.domain.bo.SysOssConfigBo; +import org.ruoyi.system.domain.vo.SysOssConfigVo; +import org.ruoyi.system.mapper.SysOssConfigMapper; +import org.ruoyi.system.service.ISysOssConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 对象存储配置Service业务层处理 + * + * @author Lion Li + * @author 孤舟烟雨 + * @date 2021-08-13 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class SysOssConfigServiceImpl implements ISysOssConfigService { + + private final SysOssConfigMapper baseMapper; + + /** + * 项目启动时,初始化参数到缓存,加载配置类 + */ + @Override + public void init() { + List list = TenantHelper.ignore(() -> + baseMapper.selectList( + new LambdaQueryWrapper().orderByAsc(TenantEntity::getTenantId)) + ); + Map> map = StreamUtils.groupByKey(list, SysOssConfig::getTenantId); + try { + for (String tenantId : map.keySet()) { + TenantHelper.setDynamic(tenantId); + // 加载OSS初始化配置 + for (SysOssConfig config : map.get(tenantId)) { + String configKey = config.getConfigKey(); + if ("0".equals(config.getStatus())) { + RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey); + } + CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); + } + } + } finally { + TenantHelper.clearDynamic(); + } + } + + @Override + public SysOssConfigVo queryById(Long ossConfigId) { + return baseMapper.selectVoById(ossConfigId); + } + + @Override + public TableDataInfo queryPageList(SysOssConfigBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + + private LambdaQueryWrapper buildQueryWrapper(SysOssConfigBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getConfigKey()), SysOssConfig::getConfigKey, bo.getConfigKey()); + lqw.like(StringUtils.isNotBlank(bo.getBucketName()), SysOssConfig::getBucketName, bo.getBucketName()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysOssConfig::getStatus, bo.getStatus()); + return lqw; + } + + @Override + public Boolean insertByBo(SysOssConfigBo bo) { + SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class); + validEntityBeforeSave(config); + boolean flag = baseMapper.insert(config) > 0; + if (flag) { + CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); + } + return flag; + } + + @Override + public Boolean updateByBo(SysOssConfigBo bo) { + SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class); + validEntityBeforeSave(config); + LambdaUpdateWrapper luw = new LambdaUpdateWrapper<>(); + luw.set(ObjectUtil.isNull(config.getPrefix()), SysOssConfig::getPrefix, ""); + luw.set(ObjectUtil.isNull(config.getRegion()), SysOssConfig::getRegion, ""); + luw.set(ObjectUtil.isNull(config.getExt1()), SysOssConfig::getExt1, ""); + luw.set(ObjectUtil.isNull(config.getRemark()), SysOssConfig::getRemark, ""); + luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId()); + boolean flag = baseMapper.update(config, luw) > 0; + if (flag) { + CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config)); + } + return flag; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysOssConfig entity) { + if (StringUtils.isNotEmpty(entity.getConfigKey()) + && !checkConfigKeyUnique(entity)) { + throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!"); + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) { + throw new ServiceException("系统内置, 不可删除!"); + } + } + List list = CollUtil.newArrayList(); + for (Long configId : ids) { + SysOssConfig config = baseMapper.selectById(configId); + list.add(config); + } + boolean flag = baseMapper.deleteBatchIds(ids) > 0; + if (flag) { + list.forEach(sysOssConfig -> + CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey())); + } + return flag; + } + + /** + * 判断configKey是否唯一 + */ + private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) { + long ossConfigId = ObjectUtil.isNull(sysOssConfig.getOssConfigId()) ? -1L : sysOssConfig.getOssConfigId(); + SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper() + .select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey) + .eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey())); + if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) { + return false; + } + return true; + } + + /** + * 启用禁用状态 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateOssConfigStatus(SysOssConfigBo bo) { + SysOssConfig sysOssConfig = MapstructUtils.convert(bo, SysOssConfig.class); + int row = baseMapper.update(null, new LambdaUpdateWrapper() + .set(SysOssConfig::getStatus, "1")); + row += baseMapper.updateById(sysOssConfig); + if (row > 0) { + RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey()); + } + return row; + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java new file mode 100644 index 00000000..717ecf1c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysOssServiceImpl.java @@ -0,0 +1,174 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.OssService; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.core.utils.file.FileUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.oss.core.OssClient; +import org.ruoyi.common.oss.entity.UploadResult; +import org.ruoyi.common.oss.enumd.AccessPolicyType; +import org.ruoyi.common.oss.factory.OssFactory; +import org.ruoyi.system.domain.SysOss; +import org.ruoyi.system.domain.bo.SysOssBo; +import org.ruoyi.system.domain.vo.SysOssVo; +import org.ruoyi.system.mapper.SysOssMapper; +import org.ruoyi.system.service.ISysOssService; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 文件上传 服务层实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysOssServiceImpl implements ISysOssService, OssService { + + private final SysOssMapper baseMapper; + + @Override + public TableDataInfo queryPageList(SysOssBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + List filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl); + result.setRecords(filterResult); + return TableDataInfo.build(result); + } + + @Override + public List listByIds(Collection ossIds) { + List list = new ArrayList<>(); + for (Long id : ossIds) { + SysOssVo vo = SpringUtils.getAopProxy(this).getById(id); + if (ObjectUtil.isNotNull(vo)) { + list.add(this.matchingUrl(vo)); + } + } + return list; + } + + @Override + public String selectUrlByIds(String ossIds) { + List list = new ArrayList<>(); + for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) { + SysOssVo vo = SpringUtils.getAopProxy(this).getById(id); + if (ObjectUtil.isNotNull(vo)) { + list.add(this.matchingUrl(vo).getUrl()); + } + } + return String.join(StringUtils.SEPARATOR, list); + } + + private LambdaQueryWrapper buildQueryWrapper(SysOssBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName()); + lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName()); + lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix()); + lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl()); + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + lqw.eq(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy()); + lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService()); + return lqw; + } + + @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId") + @Override + public SysOssVo getById(Long ossId) { + return baseMapper.selectVoById(ossId); + } + + @Override + public void download(Long ossId, HttpServletResponse response) throws IOException { + SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId); + if (ObjectUtil.isNull(sysOss)) { + throw new ServiceException("文件数据不存在!"); + } + FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); + OssClient storage = OssFactory.instance(); + try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) { + int available = inputStream.available(); + IoUtil.copy(inputStream, response.getOutputStream(), available); + response.setContentLength(available); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + + @Override + public SysOssVo upload(MultipartFile file) { + String originalfileName = file.getOriginalFilename(); + String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); + OssClient storage = OssFactory.instance(); + UploadResult uploadResult; + try { + uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); + } catch (IOException e) { + throw new ServiceException(e.getMessage()); + } + // 保存文件信息 + SysOss oss = new SysOss(); + oss.setUrl(uploadResult.getUrl()); + oss.setFileSuffix(suffix); + oss.setFileName(uploadResult.getFilename()); + oss.setOriginalName(originalfileName); + oss.setService(storage.getConfigKey()); + baseMapper.insert(oss); + SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class); + return this.matchingUrl(sysOssVo); + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 做一些业务上的校验,判断是否需要校验 + } + List list = baseMapper.selectBatchIds(ids); + for (SysOss sysOss : list) { + OssClient storage = OssFactory.instance(sysOss.getService()); + storage.delete(sysOss.getUrl()); + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 匹配Url + * + * @param oss OSS对象 + * @return oss 匹配Url的OSS对象 + */ + private SysOssVo matchingUrl(SysOssVo oss) { + OssClient storage = OssFactory.instance(oss.getService()); + // 仅修改桶类型为 private 的URL,临时URL时长为120s + if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) { + oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120)); + } + return oss; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPackagePlanServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPackagePlanServiceImpl.java new file mode 100644 index 00000000..06dc5391 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPackagePlanServiceImpl.java @@ -0,0 +1,112 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysPackagePlan; +import org.ruoyi.system.domain.bo.SysPackagePlanBo; +import org.ruoyi.system.domain.vo.SysPackagePlanVo; +import org.ruoyi.system.mapper.SysPackagePlanMapper; +import org.ruoyi.system.service.ISysPackagePlanService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 套餐管理Service业务层处理 + * + * @author Lion Li + * @date 2024-05-05 + */ +@RequiredArgsConstructor +@Service +public class SysPackagePlanServiceImpl implements ISysPackagePlanService { + + private final SysPackagePlanMapper baseMapper; + + /** + * 查询套餐管理 + */ + @Override + public SysPackagePlanVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询套餐管理列表 + */ + @Override + public TableDataInfo queryPageList(SysPackagePlanBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询套餐管理列表 + */ + @Override + public List queryList(SysPackagePlanBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysPackagePlanBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getName()), SysPackagePlan::getName, bo.getName()); + lqw.eq(bo.getPrice() != null, SysPackagePlan::getPrice, bo.getPrice()); + lqw.eq(bo.getDuration() != null, SysPackagePlan::getDuration, bo.getDuration()); + lqw.eq(StringUtils.isNotBlank(bo.getPlanDetail()), SysPackagePlan::getPlanDetail, bo.getPlanDetail()); + return lqw; + } + + /** + * 新增套餐管理 + */ + @Override + public Boolean insertByBo(SysPackagePlanBo bo) { + SysPackagePlan add = MapstructUtils.convert(bo, SysPackagePlan.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改套餐管理 + */ + @Override + public Boolean updateByBo(SysPackagePlanBo bo) { + SysPackagePlan update = MapstructUtils.convert(bo, SysPackagePlan.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysPackagePlan entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除套餐管理 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPermissionServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPermissionServiceImpl.java new file mode 100644 index 00000000..c185c548 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPermissionServiceImpl.java @@ -0,0 +1,61 @@ +package org.ruoyi.system.service.impl; + +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.service.ISysMenuService; +import org.ruoyi.system.service.ISysPermissionService; +import org.ruoyi.system.service.ISysRoleService; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.Set; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@RequiredArgsConstructor +@Service +public class SysPermissionServiceImpl implements ISysPermissionService { + + private final ISysRoleService roleService; + private final ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param userId 用户id + * @return 角色权限信息 + */ + @Override + public Set getRolePermission(Long userId) { + Set roles = new HashSet<>(); + // 管理员拥有所有权限 + if (LoginHelper.isSuperAdmin(userId)) { + roles.add(TenantConstants.SUPER_ADMIN_ROLE_KEY); + } else { + roles.addAll(roleService.selectRolePermissionByUserId(userId)); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param userId 用户id + * @return 菜单权限信息 + */ + @Override + public Set getMenuPermission(Long userId) { + Set perms = new HashSet<>(); + // 管理员拥有所有权限 + if (LoginHelper.isSuperAdmin(userId)) { + perms.add("*:*:*"); + } else { + perms.addAll(menuService.selectMenuPermsByUserId(userId)); + } + return perms; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPostServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 00000000..8f68553a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,188 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysPost; +import org.ruoyi.system.domain.SysUserPost; +import org.ruoyi.system.domain.bo.SysPostBo; +import org.ruoyi.system.domain.vo.SysPostVo; +import org.ruoyi.system.mapper.SysPostMapper; +import org.ruoyi.system.mapper.SysUserPostMapper; +import org.ruoyi.system.service.ISysPostService; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; + +/** + * 岗位信息 服务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysPostServiceImpl implements ISysPostService { + + private final SysPostMapper baseMapper; + private final SysUserPostMapper userPostMapper; + + @Override + public TableDataInfo selectPagePostList(SysPostBo post, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(post); + Page page = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(page); + } + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPostBo post) { + LambdaQueryWrapper lqw = buildQueryWrapper(post); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysPostBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getPostCode()), SysPost::getPostCode, bo.getPostCode()); + lqw.like(StringUtils.isNotBlank(bo.getPostName()), SysPost::getPostName, bo.getPostName()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysPost::getStatus, bo.getStatus()); + lqw.orderByAsc(SysPost::getPostSort); + return lqw; + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() { + return baseMapper.selectVoList(new QueryWrapper<>()); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPostVo selectPostById(Long postId) { + return baseMapper.selectVoById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List selectPostListByUserId(Long userId) { + return baseMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPostBo post) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysPost::getPostName, post.getPostName()) + .ne(ObjectUtil.isNotNull(post.getPostId()), SysPost::getPostId, post.getPostId())); + return !exist; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPostBo post) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysPost::getPostCode, post.getPostCode()) + .ne(ObjectUtil.isNotNull(post.getPostId()), SysPost::getPostId, post.getPostId())); + return !exist; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public long countUserPostById(Long postId) { + return userPostMapper.selectCount(new LambdaQueryWrapper().eq(SysUserPost::getPostId, postId)); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) { + return baseMapper.deleteById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) { + for (Long postId : postIds) { + SysPost post = baseMapper.selectById(postId); + if (countUserPostById(postId) > 0) { + throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName())); + } + } + return baseMapper.deleteBatchIds(Arrays.asList(postIds)); + } + + /** + * 新增保存岗位信息 + * + * @param bo 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPostBo bo) { + SysPost post = MapstructUtils.convert(bo, SysPost.class); + return baseMapper.insert(post); + } + + /** + * 修改保存岗位信息 + * + * @param bo 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPostBo bo) { + SysPost post = MapstructUtils.convert(bo, SysPost.class); + return baseMapper.updateById(post); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 00000000..ee4bfa91 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,459 @@ +package org.ruoyi.system.service.impl; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysRole; +import org.ruoyi.system.domain.SysRoleDept; +import org.ruoyi.system.domain.SysRoleMenu; +import org.ruoyi.system.domain.SysUserRole; +import org.ruoyi.system.domain.bo.SysRoleBo; +import org.ruoyi.system.domain.vo.SysRoleVo; +import org.ruoyi.system.mapper.SysRoleDeptMapper; +import org.ruoyi.system.mapper.SysRoleMapper; +import org.ruoyi.system.mapper.SysRoleMenuMapper; +import org.ruoyi.system.mapper.SysUserRoleMapper; +import org.ruoyi.system.service.ISysRoleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +/** + * 角色 业务层处理 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Service +public class SysRoleServiceImpl implements ISysRoleService { + + private final SysRoleMapper baseMapper; + private final SysRoleMenuMapper roleMenuMapper; + private final SysUserRoleMapper userRoleMapper; + private final SysRoleDeptMapper roleDeptMapper; + + @Override + public TableDataInfo selectPageRoleList(SysRoleBo role, PageQuery pageQuery) { + Page page = baseMapper.selectPageRoleList(pageQuery.build(), this.buildQueryWrapper(role)); + return TableDataInfo.build(page); + } + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + public List selectRoleList(SysRoleBo role) { + return baseMapper.selectRoleList(this.buildQueryWrapper(role)); + } + + private Wrapper buildQueryWrapper(SysRoleBo bo) { + Map params = bo.getParams(); + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("r.del_flag", UserConstants.ROLE_NORMAL) + .eq(ObjectUtil.isNotNull(bo.getRoleId()), "r.role_id", bo.getRoleId()) + .like(StringUtils.isNotBlank(bo.getRoleName()), "r.role_name", bo.getRoleName()) + .eq(StringUtils.isNotBlank(bo.getStatus()), "r.status", bo.getStatus()) + .like(StringUtils.isNotBlank(bo.getRoleKey()), "r.role_key", bo.getRoleKey()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + "r.create_time", params.get("beginTime"), params.get("endTime")) + .orderByAsc("r.role_sort").orderByAsc("r.create_time");; + return wrapper; + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) { + List userRoles = baseMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRoleVo role : roles) { + for (SysRoleVo userRole : userRoles) { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) { + List perms = baseMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRoleVo perm : perms) { + if (ObjectUtil.isNotNull(perm)) { + permsSet.addAll(StringUtils.splitList(perm.getRoleKey().trim())); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() { + return this.selectRoleList(new SysRoleBo()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) { + return baseMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRoleVo selectRoleById(Long roleId) { + return baseMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRoleBo role) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysRole::getRoleName, role.getRoleName()) + .ne(ObjectUtil.isNotNull(role.getRoleId()), SysRole::getRoleId, role.getRoleId())); + return !exist; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRoleBo role) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysRole::getRoleKey, role.getRoleKey()) + .ne(ObjectUtil.isNotNull(role.getRoleId()), SysRole::getRoleId, role.getRoleId())); + return !exist; + } + + /** + * 校验角色是否允许操作 + * + * @param roleId 角色ID + */ + @Override + public void checkRoleAllowed(Long roleId) { + if (ObjectUtil.isNotNull(roleId) && LoginHelper.isSuperAdmin(roleId)) { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) { + if (ObjectUtil.isNull(roleId)) { + return; + } + if (LoginHelper.isSuperAdmin()) { + return; + } + List roles = this.selectRoleList(new SysRoleBo(roleId)); + if (CollUtil.isEmpty(roles)) { + throw new ServiceException("没有权限访问角色数据!"); + } + + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public long countUserRoleByRoleId(Long roleId) { + return userRoleMapper.selectCount(new LambdaQueryWrapper().eq(SysUserRole::getRoleId, roleId)); + } + + /** + * 新增保存角色信息 + * + * @param bo 角色信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertRole(SysRoleBo bo) { + SysRole role = MapstructUtils.convert(bo, SysRole.class); + // 新增角色信息 + baseMapper.insert(role); + bo.setRoleId(role.getRoleId()); + return insertRoleMenu(bo); + } + + /** + * 修改保存角色信息 + * + * @param bo 角色信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateRole(SysRoleBo bo) { + SysRole role = MapstructUtils.convert(bo, SysRole.class); + // 修改角色信息 + baseMapper.updateById(role); + // 删除角色与菜单关联 + roleMenuMapper.delete(new LambdaQueryWrapper().eq(SysRoleMenu::getRoleId, role.getRoleId())); + return insertRoleMenu(bo); + } + + /** + * 修改角色状态 + * + * @param roleId 角色ID + * @param status 角色状态 + * @return 结果 + */ + @Override + public int updateRoleStatus(Long roleId, String status) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(SysRole::getStatus, status) + .eq(SysRole::getRoleId, roleId)); + } + + /** + * 修改数据权限信息 + * + * @param bo 角色信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int authDataScope(SysRoleBo bo) { + SysRole role = MapstructUtils.convert(bo, SysRole.class); + // 修改角色信息 + baseMapper.updateById(role); + // 删除角色与部门关联 + roleDeptMapper.delete(new LambdaQueryWrapper().eq(SysRoleDept::getRoleId, role.getRoleId())); + // 新增角色和部门信息(数据权限) + return insertRoleDept(bo); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + private int insertRoleMenu(SysRoleBo role) { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) { + rows = roleMenuMapper.insertBatch(list) ? list.size() : 0; + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + private int insertRoleDept(SysRoleBo role) { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) { + rows = roleDeptMapper.insertBatch(list) ? list.size() : 0; + } + return rows; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteRoleById(Long roleId) { + // 删除角色与菜单关联 + roleMenuMapper.delete(new LambdaQueryWrapper().eq(SysRoleMenu::getRoleId, roleId)); + // 删除角色与部门关联 + roleDeptMapper.delete(new LambdaQueryWrapper().eq(SysRoleDept::getRoleId, roleId)); + return baseMapper.deleteById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteRoleByIds(Long[] roleIds) { + for (Long roleId : roleIds) { + checkRoleAllowed(roleId); + checkRoleDataScope(roleId); + SysRole role = baseMapper.selectById(roleId); + if (countUserRoleByRoleId(roleId) > 0) { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + List ids = Arrays.asList(roleIds); + // 删除角色与菜单关联 + roleMenuMapper.delete(new LambdaQueryWrapper().in(SysRoleMenu::getRoleId, ids)); + // 删除角色与部门关联 + roleDeptMapper.delete(new LambdaQueryWrapper().in(SysRoleDept::getRoleId, ids)); + return baseMapper.deleteBatchIds(ids); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) { + int rows = userRoleMapper.delete(new LambdaQueryWrapper() + .eq(SysUserRole::getRoleId, userRole.getRoleId()) + .eq(SysUserRole::getUserId, userRole.getUserId())); + if (rows > 0) { + cleanOnlineUserByRole(userRole.getRoleId()); + } + return rows; + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, Long[] userIds) { + int rows = userRoleMapper.delete(new LambdaQueryWrapper() + .eq(SysUserRole::getRoleId, roleId) + .in(SysUserRole::getUserId, Arrays.asList(userIds))); + if (rows > 0) { + cleanOnlineUserByRole(roleId); + } + return rows; + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) { + // 新增用户与角色管理 + int rows = 1; + List list = StreamUtils.toList(List.of(userIds), userId -> { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + return ur; + }); + if (CollUtil.isNotEmpty(list)) { + rows = userRoleMapper.insertBatch(list) ? list.size() : 0; + } + if (rows > 0) { + cleanOnlineUserByRole(roleId); + } + return rows; + } + + @Override + public void cleanOnlineUserByRole(Long roleId) { + List keys = StpUtil.searchTokenValue("", 0, -1, false); + if (CollUtil.isEmpty(keys)) { + return; + } + // 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作 + keys.parallelStream().forEach(key -> { + String token = StringUtils.substringAfterLast(key, ":"); + // 如果已经过期则跳过 + if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { + return; + } + // LoginUser loginUser = LoginHelper.getLoginUser(token); + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) { + try { + StpUtil.logoutByTokenValue(token); + } catch (NotLoginException ignored) { + } + } + }); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysSensitiveServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysSensitiveServiceImpl.java new file mode 100644 index 00000000..f850a61a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysSensitiveServiceImpl.java @@ -0,0 +1,26 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.common.sensitive.core.SensitiveService; +import org.springframework.stereotype.Service; + +/** + * 脱敏服务 + * 默认管理员不过滤 + * 需自行根据业务重写实现 + * + * @author Lion Li + * @version 3.6.0 + */ +@Service +public class SysSensitiveServiceImpl implements SensitiveService { + + /** + * 是否脱敏 + */ + @Override + public boolean isSensitive() { + return !LoginHelper.isSuperAdmin() || !LoginHelper.isTenantAdmin(); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantPackageServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantPackageServiceImpl.java new file mode 100644 index 00000000..afb724ea --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantPackageServiceImpl.java @@ -0,0 +1,146 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.SysTenant; +import org.ruoyi.system.domain.SysTenantPackage; +import org.ruoyi.system.domain.bo.SysTenantPackageBo; +import org.ruoyi.system.domain.vo.SysTenantPackageVo; +import org.ruoyi.system.mapper.SysTenantMapper; +import org.ruoyi.system.mapper.SysTenantPackageMapper; +import org.ruoyi.system.service.ISysTenantPackageService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 租户套餐Service业务层处理 + * + * @author Michelle.Chung + */ +@RequiredArgsConstructor +@Service +public class SysTenantPackageServiceImpl implements ISysTenantPackageService { + + private final SysTenantPackageMapper baseMapper; + private final SysTenantMapper tenantMapper; + + /** + * 查询租户套餐 + */ + @Override + public SysTenantPackageVo queryById(Long packageId){ + return baseMapper.selectVoById(packageId); + } + + /** + * 查询租户套餐列表 + */ + @Override + public TableDataInfo queryPageList(SysTenantPackageBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + @Override + public List selectList() { + return baseMapper.selectVoList(new LambdaQueryWrapper() + .eq(SysTenantPackage::getStatus, TenantConstants.NORMAL)); + } + + /** + * 查询租户套餐列表 + */ + @Override + public List queryList(SysTenantPackageBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysTenantPackageBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getPackageName()), SysTenantPackage::getPackageName, bo.getPackageName()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysTenantPackage::getStatus, bo.getStatus()); + return lqw; + } + + /** + * 新增租户套餐 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(SysTenantPackageBo bo) { + SysTenantPackage add = MapstructUtils.convert(bo, SysTenantPackage.class); + // 保存菜单id + List menuIds = Arrays.asList(bo.getMenuIds()); + if (CollUtil.isNotEmpty(menuIds)) { + add.setMenuIds(StringUtils.join(menuIds, ", ")); + } else { + add.setMenuIds(""); + } + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setPackageId(add.getPackageId()); + } + return flag; + } + + /** + * 修改租户套餐 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(SysTenantPackageBo bo) { + SysTenantPackage update = MapstructUtils.convert(bo, SysTenantPackage.class); + // 保存菜单id + List menuIds = Arrays.asList(bo.getMenuIds()); + if (CollUtil.isNotEmpty(menuIds)) { + update.setMenuIds(StringUtils.join(menuIds, ", ")); + } else { + update.setMenuIds(""); + } + return baseMapper.updateById(update) > 0; + } + + /** + * 修改套餐状态 + * + * @param bo 套餐信息 + * @return 结果 + */ + @Override + public int updatePackageStatus(SysTenantPackageBo bo) { + SysTenantPackage tenantPackage = MapstructUtils.convert(bo, SysTenantPackage.class); + return baseMapper.updateById(tenantPackage); + } + + /** + * 批量删除租户套餐 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + boolean exists = tenantMapper.exists(new LambdaQueryWrapper().in(SysTenant::getPackageId, ids)); + if (exists) { + throw new ServiceException("租户套餐已被使用"); + } + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantServiceImpl.java new file mode 100644 index 00000000..8fa25a36 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysTenantServiceImpl.java @@ -0,0 +1,367 @@ +package org.ruoyi.system.service.impl; + +import cn.dev33.satoken.secure.BCrypt; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.constant.Constants; +import org.ruoyi.common.core.constant.TenantConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.SpringUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.*; +import org.ruoyi.system.domain.bo.SysTenantBo; +import org.ruoyi.system.domain.vo.SysTenantVo; +import org.ruoyi.system.mapper.*; +import org.ruoyi.system.service.ISysTenantService; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 租户Service业务层处理 + * + * @author Michelle.Chung + */ +@RequiredArgsConstructor +@Service +public class SysTenantServiceImpl implements ISysTenantService { + + private final SysTenantMapper baseMapper; + private final SysTenantPackageMapper tenantPackageMapper; + private final SysUserMapper userMapper; + private final SysDeptMapper deptMapper; + private final SysRoleMapper roleMapper; + private final SysRoleMenuMapper roleMenuMapper; + private final SysRoleDeptMapper roleDeptMapper; + private final SysUserRoleMapper userRoleMapper; + private final SysDictTypeMapper dictTypeMapper; + private final SysDictDataMapper dictDataMapper; + private final SysConfigMapper configMapper; + + /** + * 查询租户 + */ + @Override + public SysTenantVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 基于租户ID查询租户 + */ + @Cacheable(cacheNames = CacheNames.SYS_TENANT, key = "#tenantId") + @Override + public SysTenantVo queryByTenantId(String tenantId) { + return baseMapper.selectVoOne(new LambdaQueryWrapper().eq(SysTenant::getTenantId, tenantId)); + } + + /** + * 查询租户列表 + */ + @Override + public TableDataInfo queryPageList(SysTenantBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询租户列表 + */ + @Override + public List queryList(SysTenantBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysTenantBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getTenantId()), SysTenant::getTenantId, bo.getTenantId()); + lqw.like(StringUtils.isNotBlank(bo.getContactUserName()), SysTenant::getContactUserName, bo.getContactUserName()); + lqw.eq(StringUtils.isNotBlank(bo.getContactPhone()), SysTenant::getContactPhone, bo.getContactPhone()); + lqw.like(StringUtils.isNotBlank(bo.getCompanyName()), SysTenant::getCompanyName, bo.getCompanyName()); + lqw.eq(StringUtils.isNotBlank(bo.getLicenseNumber()), SysTenant::getLicenseNumber, bo.getLicenseNumber()); + lqw.eq(StringUtils.isNotBlank(bo.getAddress()), SysTenant::getAddress, bo.getAddress()); + lqw.eq(StringUtils.isNotBlank(bo.getIntro()), SysTenant::getIntro, bo.getIntro()); + lqw.like(StringUtils.isNotBlank(bo.getDomain()), SysTenant::getDomain, bo.getDomain()); + lqw.eq(bo.getPackageId() != null, SysTenant::getPackageId, bo.getPackageId()); + lqw.eq(bo.getExpireTime() != null, SysTenant::getExpireTime, bo.getExpireTime()); + lqw.eq(bo.getAccountCount() != null, SysTenant::getAccountCount, bo.getAccountCount()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysTenant::getStatus, bo.getStatus()); + return lqw; + } + + /** + * 新增租户 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(SysTenantBo bo) { + SysTenant add = MapstructUtils.convert(bo, SysTenant.class); + + // 获取所有租户编号 + List tenantIds = baseMapper.selectObjs( + new LambdaQueryWrapper().select(SysTenant::getTenantId), Convert::toStr); + String tenantId = generateTenantId(tenantIds); + add.setTenantId(tenantId); + boolean flag = baseMapper.insert(add) > 0; + if (!flag) { + throw new ServiceException("创建租户失败"); + } + bo.setId(add.getId()); + + // 根据套餐创建角色 + Long roleId = createTenantRole(tenantId, bo.getPackageId()); + + // 创建部门: 公司名是部门名称 + SysDept dept = new SysDept(); + dept.setTenantId(tenantId); + dept.setDeptName(bo.getCompanyName()); + dept.setLeader(bo.getUsername()); + dept.setParentId(Constants.TOP_PARENT_ID); + dept.setAncestors(Constants.TOP_PARENT_ID.toString()); + deptMapper.insert(dept); + Long deptId = dept.getDeptId(); + + // 角色和部门关联表 + SysRoleDept roleDept = new SysRoleDept(); + roleDept.setRoleId(roleId); + roleDept.setDeptId(deptId); + roleDeptMapper.insert(roleDept); + + // 创建系统用户 + SysUser user = new SysUser(); + user.setTenantId(tenantId); + user.setUserName(bo.getUsername()); + user.setNickName(bo.getUsername()); + user.setPassword(BCrypt.hashpw(bo.getPassword())); + user.setDeptId(deptId); + userMapper.insert(user); + + // 用户和角色关联表 + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(user.getUserId()); + userRole.setRoleId(roleId); + userRoleMapper.insert(userRole); + + String defaultTenantId = TenantConstants.DEFAULT_TENANT_ID; + List dictTypeList = dictTypeMapper.selectList( + new LambdaQueryWrapper().eq(SysDictType::getTenantId, defaultTenantId)); + List dictDataList = dictDataMapper.selectList( + new LambdaQueryWrapper().eq(SysDictData::getTenantId, defaultTenantId)); + for (SysDictType dictType : dictTypeList) { + dictType.setDictId(null); + dictType.setTenantId(tenantId); + } + for (SysDictData dictData : dictDataList) { + dictData.setDictCode(null); + dictData.setTenantId(tenantId); + } + dictTypeMapper.insertBatch(dictTypeList); + dictDataMapper.insertBatch(dictDataList); + + List sysConfigList = configMapper.selectList( + new LambdaQueryWrapper().eq(SysConfig::getTenantId, defaultTenantId)); + for (SysConfig config : sysConfigList) { + config.setConfigId(null); + config.setTenantId(tenantId); + } + configMapper.insertBatch(sysConfigList); + return true; + } + + /** + * 生成租户id + * + * @param tenantIds 已有租户id列表 + * @return 租户id + */ + private String generateTenantId(List tenantIds) { + // 随机生成6位 + String numbers = RandomUtil.randomNumbers(6); + // 判断是否存在,如果存在则重新生成 + if (tenantIds.contains(numbers)) { + generateTenantId(tenantIds); + } + return numbers; + } + + /** + * 根据租户菜单创建租户角色 + * + * @param tenantId 租户编号 + * @param packageId 租户套餐id + * @return 角色id + */ + private Long createTenantRole(String tenantId, Long packageId) { + // 获取租户套餐 + SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId); + if (ObjectUtil.isNull(tenantPackage)) { + throw new ServiceException("套餐不存在"); + } + // 获取套餐菜单id + List menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong); + + // 创建角色 + SysRole role = new SysRole(); + role.setTenantId(tenantId); + role.setRoleName(TenantConstants.TENANT_ADMIN_ROLE_NAME); + role.setRoleKey(TenantConstants.TENANT_ADMIN_ROLE_KEY); + role.setRoleSort(1); + role.setStatus(TenantConstants.NORMAL); + roleMapper.insert(role); + Long roleId = role.getRoleId(); + + // 创建角色菜单 + List roleMenus = new ArrayList<>(menuIds.size()); + menuIds.forEach(menuId -> { + SysRoleMenu roleMenu = new SysRoleMenu(); + roleMenu.setRoleId(roleId); + roleMenu.setMenuId(menuId); + roleMenus.add(roleMenu); + }); + roleMenuMapper.insertBatch(roleMenus); + + return roleId; + } + + /** + * 修改租户 + */ + @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId") + @Override + public Boolean updateByBo(SysTenantBo bo) { + SysTenant tenant = MapstructUtils.convert(bo, SysTenant.class); + tenant.setTenantId(null); + tenant.setPackageId(null); + return baseMapper.updateById(tenant) > 0; + } + + /** + * 修改租户状态 + * + * @param bo 租户信息 + * @return 结果 + */ + @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId") + @Override + public int updateTenantStatus(SysTenantBo bo) { + SysTenant tenant = MapstructUtils.convert(bo, SysTenant.class); + return baseMapper.updateById(tenant); + } + + /** + * 校验租户是否允许操作 + * + * @param tenantId 租户ID + */ + @Override + public void checkTenantAllowed(String tenantId) { + if (ObjectUtil.isNotNull(tenantId) && TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) { + throw new ServiceException("不允许操作管理租户"); + } + } + + /** + * 批量删除租户 + */ + @CacheEvict(cacheNames = CacheNames.SYS_TENANT, allEntries = true) + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 做一些业务上的校验,判断是否需要校验 + if (ids.contains(TenantConstants.SUPER_ADMIN_ID)) { + throw new ServiceException("超管租户不能删除"); + } + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 校验企业名称是否唯一 + */ + @Override + public boolean checkCompanyNameUnique(SysTenantBo bo) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysTenant::getCompanyName, bo.getCompanyName()) + .ne(ObjectUtil.isNotNull(bo.getTenantId()), SysTenant::getTenantId, bo.getTenantId())); + return !exist; + } + + /** + * 校验账号余额 + */ + @Override + public boolean checkAccountBalance(String tenantId) { + SysTenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId); + // 如果余额为-1代表不限制 + if (tenant.getAccountCount() == -1) { + return true; + } + Long userNumber = userMapper.selectCount(new LambdaQueryWrapper<>()); + // 如果余额大于0代表还有可用名额 + return tenant.getAccountCount() - userNumber > 0; + } + + /** + * 校验有效期 + */ + @Override + public boolean checkExpireTime(String tenantId) { + SysTenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId); + // 如果未设置过期时间代表不限制 + if (ObjectUtil.isNull(tenant.getExpireTime())) { + return true; + } + // 如果当前时间在过期时间之前则通过 + return new Date().before(tenant.getExpireTime()); + } + + /** + * 同步租户套餐 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean syncTenantPackage(String tenantId, String packageId) { + SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId); + List roles = roleMapper.selectList( + new LambdaQueryWrapper().eq(SysRole::getTenantId, tenantId)); + List roleIds = new ArrayList<>(roles.size() - 1); + List menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong); + roles.forEach(item -> { + if (TenantConstants.TENANT_ADMIN_ROLE_KEY.equals(item.getRoleKey())) { + List roleMenus = new ArrayList<>(menuIds.size()); + menuIds.forEach(menuId -> { + SysRoleMenu roleMenu = new SysRoleMenu(); + roleMenu.setRoleId(item.getRoleId()); + roleMenu.setMenuId(menuId); + roleMenus.add(roleMenu); + }); + roleMenuMapper.delete(new LambdaQueryWrapper().eq(SysRoleMenu::getRoleId, item.getRoleId())); + roleMenuMapper.insertBatch(roleMenus); + } else { + roleIds.add(item.getRoleId()); + } + }); + if (!roleIds.isEmpty()) { + roleMenuMapper.delete( + new LambdaQueryWrapper().in(SysRoleMenu::getRoleId, roleIds).notIn(!menuIds.isEmpty(), SysRoleMenu::getMenuId, menuIds)); + } + return true; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserGroupServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserGroupServiceImpl.java new file mode 100644 index 00000000..7da71d51 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserGroupServiceImpl.java @@ -0,0 +1,110 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.SysUserGroupBo; +import org.ruoyi.system.domain.vo.SysUserGroupVo; +import org.ruoyi.system.domain.SysUserGroup; +import org.ruoyi.system.mapper.SysUserGroupMapper; +import org.ruoyi.system.service.ISysUserGroupService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author Lion Li + * @date 2024-08-03 + */ +@RequiredArgsConstructor +@Service +public class SysUserGroupServiceImpl implements ISysUserGroupService { + + private final SysUserGroupMapper baseMapper; + + /** + * 查询【请填写功能名称】 + */ + @Override + public SysUserGroupVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public TableDataInfo queryPageList(SysUserGroupBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public List queryList(SysUserGroupBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysUserGroupBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getGroupName()), SysUserGroup::getGroupName, bo.getGroupName()); + lqw.eq(StringUtils.isNotBlank(bo.getUpdateIp()), SysUserGroup::getUpdateIp, bo.getUpdateIp()); + return lqw; + } + + /** + * 新增【请填写功能名称】 + */ + @Override + public Boolean insertByBo(SysUserGroupBo bo) { + SysUserGroup add = MapstructUtils.convert(bo, SysUserGroup.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改【请填写功能名称】 + */ + @Override + public Boolean updateByBo(SysUserGroupBo bo) { + SysUserGroup update = MapstructUtils.convert(bo, SysUserGroup.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysUserGroup entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除【请填写功能名称】 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserModelServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserModelServiceImpl.java new file mode 100644 index 00000000..1a401c74 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserModelServiceImpl.java @@ -0,0 +1,109 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.SysUserModelBo; +import org.ruoyi.system.domain.vo.SysUserModelVo; +import org.ruoyi.system.domain.SysUserModel; +import org.ruoyi.system.mapper.SysUserModelMapper; +import org.ruoyi.system.service.ISysUserModelService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author Lion Li + * @date 2024-08-03 + */ +@RequiredArgsConstructor +@Service +public class SysUserModelServiceImpl implements ISysUserModelService { + + private final SysUserModelMapper baseMapper; + + /** + * 查询【请填写功能名称】 + */ + @Override + public SysUserModelVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public TableDataInfo queryPageList(SysUserModelBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public List queryList(SysUserModelBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SysUserModelBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getMid() != null, SysUserModel::getMid, bo.getMid()); + lqw.eq(bo.getGid() != null, SysUserModel::getGid, bo.getGid()); + return lqw; + } + + /** + * 新增【请填写功能名称】 + */ + @Override + public Boolean insertByBo(SysUserModelBo bo) { + SysUserModel add = MapstructUtils.convert(bo, SysUserModel.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改【请填写功能名称】 + */ + @Override + public Boolean updateByBo(SysUserModelBo bo) { + SysUserModel update = MapstructUtils.convert(bo, SysUserModel.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SysUserModel entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除【请填写功能名称】 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 00000000..39e51ea1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,560 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.ruoyi.common.core.constant.CacheNames; +import org.ruoyi.common.core.constant.UserConstants; +import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.UserService; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StreamUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.helper.DataBaseHelper; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.SysDept; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.domain.SysUserPost; +import org.ruoyi.system.domain.SysUserRole; +import org.ruoyi.system.domain.bo.SysUserBo; +import org.ruoyi.system.domain.vo.SysPostVo; +import org.ruoyi.system.domain.vo.SysRoleVo; +import org.ruoyi.system.domain.vo.SysUserVo; +import org.ruoyi.system.mapper.*; +import org.ruoyi.system.service.ISysUserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +/** + * 用户 业务层处理 + * + * @author Lion Li + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class SysUserServiceImpl implements ISysUserService, UserService { + + private final SysUserMapper baseMapper; + private final SysDeptMapper deptMapper; + private final SysRoleMapper roleMapper; + private final SysPostMapper postMapper; + private final SysUserRoleMapper userRoleMapper; + private final SysUserPostMapper userPostMapper; + + @Override + public TableDataInfo selectPageUserList(SysUserBo user, PageQuery pageQuery) { + Page page = baseMapper.selectPageUserList(pageQuery.build(), this.buildQueryWrapper(user)); + return TableDataInfo.build(page); + } + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + public List selectUserList(SysUserBo user) { + return baseMapper.selectUserList(this.buildQueryWrapper(user)); + } + + private Wrapper buildQueryWrapper(SysUserBo user) { + Map params = user.getParams(); + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("u.del_flag", UserConstants.USER_NORMAL) + .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId()) + .eq(ObjectUtil.isNotNull(user.getUserGrade()), "u.user_grade", user.getUserGrade()) + .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName()) + .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus()) + .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber()) + .between(params.get("beginTime") != null && params.get("endTime") != null, + "u.create_time", params.get("beginTime"), params.get("endTime")) + .and(ObjectUtil.isNotNull(user.getDeptId()), w -> { + List deptList = deptMapper.selectList(new LambdaQueryWrapper() + .select(SysDept::getDeptId) + .apply(DataBaseHelper.findInSet(user.getDeptId(), "ancestors"))); + List ids = StreamUtils.toList(deptList, SysDept::getDeptId); + ids.add(user.getDeptId()); + w.in("u.dept_id", ids); + }); + return wrapper; + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + public TableDataInfo selectAllocatedList(SysUserBo user, PageQuery pageQuery) { + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("u.del_flag", UserConstants.USER_NORMAL) + .eq(ObjectUtil.isNotNull(user.getRoleId()), "r.role_id", user.getRoleId()) + .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName()) + .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus()) + .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber()); + Page page = baseMapper.selectAllocatedList(pageQuery.build(), wrapper); + return TableDataInfo.build(page); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + public TableDataInfo selectUnallocatedList(SysUserBo user, PageQuery pageQuery) { + List userIds = userRoleMapper.selectUserIdsByRoleId(user.getRoleId()); + QueryWrapper wrapper = Wrappers.query(); + wrapper.eq("u.del_flag", UserConstants.USER_NORMAL) + .and(w -> w.ne("r.role_id", user.getRoleId()).or().isNull("r.role_id")) + .notIn(CollUtil.isNotEmpty(userIds), "u.user_id", userIds) + .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName()) + .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber()); + Page page = baseMapper.selectUnallocatedList(pageQuery.build(), wrapper); + return TableDataInfo.build(page); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUserVo selectUserByUserName(String userName) { + return baseMapper.selectUserByUserName(userName); + } + + /** + * 通过OpenId查询用户 + * + * @param openId 用户名 + * @return 用户对象信息 + */ + @Override + public SysUserVo selectUserByOpenId(String openId) { + return baseMapper.selectUserByOpenId(openId); + } + + + /** + * 通过手机号查询用户 + * + * @param phonenumber 手机号 + * @return 用户对象信息 + */ + @Override + public SysUserVo selectUserByPhonenumber(String phonenumber) { + return baseMapper.selectUserByPhonenumber(phonenumber); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUserVo selectUserById(Long userId) { + return baseMapper.selectUserById(userId); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) { + List list = roleMapper.selectRolesByUserName(userName); + if (CollUtil.isEmpty(list)) { + return StringUtils.EMPTY; + } + return StreamUtils.join(list, SysRoleVo::getRoleName); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) { + List list = postMapper.selectPostsByUserName(userName); + if (CollUtil.isEmpty(list)) { + return StringUtils.EMPTY; + } + return StreamUtils.join(list, SysPostVo::getPostName); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUserBo user) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysUser::getUserName, user.getUserName()) + .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())); + return !exist; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + */ + @Override + public boolean checkPhoneUnique(SysUserBo user) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysUser::getPhonenumber, user.getPhonenumber()) + .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())); + return !exist; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + */ + @Override + public boolean checkEmailUnique(SysUserBo user) { + boolean exist = baseMapper.exists(new LambdaQueryWrapper() + .eq(SysUser::getEmail, user.getEmail()) + .ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())); + return !exist; + } + + /** + * 校验用户是否允许操作 + * + * @param userId 用户ID + */ + @Override + public void checkUserAllowed(Long userId) { + if (ObjectUtil.isNotNull(userId) && LoginHelper.isSuperAdmin(userId)) { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) { + if (ObjectUtil.isNull(userId)) { + return; + } + if (LoginHelper.isSuperAdmin()) { + return; + } + if (ObjectUtil.isNull(baseMapper.selectUserById(userId))) { + throw new ServiceException("没有权限访问用户数据!"); + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertUser(SysUserBo user) { + SysUser sysUser = MapstructUtils.convert(user, SysUser.class); + // 新增用户信息 + int rows = baseMapper.insert(sysUser); + user.setUserId(sysUser.getUserId()); + // 新增用户岗位关联 + insertUserPost(user, false); + // 新增用户与角色管理 + insertUserRole(user, false); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public SysUser registerUser(SysUserBo user, String tenantId) { + user.setCreateBy(user.getUserId()); + user.setUpdateBy(user.getUserId()); + SysUser sysUser = MapstructUtils.convert(user, SysUser.class); + if (sysUser != null) { + sysUser.setTenantId(tenantId); + } + baseMapper.insert(sysUser); + return sysUser; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateUser(SysUserBo user) { + // 新增用户与角色管理 + //insertUserRole(user, true); + // 新增用户与岗位管理 + //insertUserPost(user, true); + SysUser sysUser = MapstructUtils.convert(user, SysUser.class); + // 防止错误更新后导致的数据误删除 + int flag = baseMapper.updateById(sysUser); + if (flag < 1) { + throw new ServiceException("修改用户" + user.getUserName() + "信息失败"); + } + return flag; + } + + /** + * 小程序 - 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public SysUserVo updateXcxUser(SysUserBo user) { + baseMapper.updateXcxUser(user); + return baseMapper.selectUserByOpenId(user.getOpenId()); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void insertUserAuth(Long userId, Long[] roleIds) { + insertUserRole(userId, roleIds, true); + } + + /** + * 修改用户状态 + * + * @param userId 用户ID + * @param status 帐号状态 + * @return 结果 + */ + @Override + public int updateUserStatus(Long userId, String status) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getStatus, status) + .eq(SysUser::getUserId, userId)); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUserBo user) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName()) + .set(SysUser::getPhonenumber, user.getPhonenumber()) + .set(SysUser::getEmail, user.getEmail()) + .set(SysUser::getSex, user.getSex()) + .eq(SysUser::getUserId, user.getUserId())); + } + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(Long userId, String avatar) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getAvatar, avatar) + .eq(SysUser::getUserId, userId)) > 0; + } + + @Override + public boolean updateUserName(Long userId, String nickName) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getNickName, nickName) + .eq(SysUser::getUserId, userId)) > 0; + } + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(Long userId, String password) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .set(SysUser::getPassword, password) + .eq(SysUser::getUserId, userId)); + } + + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + * @param clear 清除已存在的关联数据 + */ + private void insertUserRole(SysUserBo user, boolean clear) { + this.insertUserRole(user.getUserId(), user.getRoleIds(), clear); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + * @param clear 清除已存在的关联数据 + */ + private void insertUserPost(SysUserBo user, boolean clear) { + Long[] posts = user.getPostIds(); + if (ArrayUtil.isNotEmpty(posts)) { + if (clear) { + // 删除用户与岗位关联 + userPostMapper.delete(new LambdaQueryWrapper().eq(SysUserPost::getUserId, user.getUserId())); + } + // 新增用户与岗位管理 + List list = StreamUtils.toList(List.of(posts), postId -> { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + return up; + }); + userPostMapper.insertBatch(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + * @param clear 清除已存在的关联数据 + */ + private void insertUserRole(Long userId, Long[] roleIds, boolean clear) { + if (ArrayUtil.isNotEmpty(roleIds)) { + // 判断是否具有此角色的操作权限 + List roles = roleMapper.selectRoleList(new LambdaQueryWrapper<>()); + if (CollUtil.isEmpty(roles)) { + throw new ServiceException("没有权限访问角色的数据"); + } + List roleList = StreamUtils.toList(roles, SysRoleVo::getRoleId); + if (!LoginHelper.isSuperAdmin(userId)) { + roleList.remove(UserConstants.SUPER_ADMIN_ID); + } + List canDoRoleList = StreamUtils.filter(List.of(roleIds), roleList::contains); + if (CollUtil.isEmpty(canDoRoleList)) { + throw new ServiceException("没有权限访问角色的数据"); + } + if (clear) { + // 删除用户与角色关联 + userRoleMapper.delete(new LambdaQueryWrapper().eq(SysUserRole::getUserId, userId)); + } + // 新增用户与角色管理 + List list = StreamUtils.toList(canDoRoleList, roleId -> { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + return ur; + }); + userRoleMapper.insertBatch(list); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteUserById(Long userId) { + // 删除用户与角色关联 + userRoleMapper.delete(new LambdaQueryWrapper().eq(SysUserRole::getUserId, userId)); + // 删除用户与岗位表 + userPostMapper.delete(new LambdaQueryWrapper().eq(SysUserPost::getUserId, userId)); + // 防止更新失败导致的数据删除 + int flag = baseMapper.deleteById(userId); + if (flag < 1) { + throw new ServiceException("删除用户失败!"); + } + return flag; + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteUserByIds(Long[] userIds) { + for (Long userId : userIds) { + checkUserAllowed(userId); + checkUserDataScope(userId); + } + List ids = List.of(userIds); + // 删除用户与角色关联 + userRoleMapper.delete(new LambdaQueryWrapper().in(SysUserRole::getUserId, ids)); + // 删除用户与岗位表 + userPostMapper.delete(new LambdaQueryWrapper().in(SysUserPost::getUserId, ids)); + // 防止更新失败导致的数据删除 + int flag = baseMapper.deleteBatchIds(ids); + if (flag < 1) { + throw new ServiceException("删除用户失败!"); + } + return flag; + } + + @Cacheable(cacheNames = CacheNames.SYS_USER_NAME, key = "#userId") + @Override + public String selectUserNameById(Long userId) { + SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper() + .select(SysUser::getUserName).eq(SysUser::getUserId, userId)); + return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserName(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/VoiceRoleServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/VoiceRoleServiceImpl.java new file mode 100644 index 00000000..b242b896 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/VoiceRoleServiceImpl.java @@ -0,0 +1,264 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.ruoyi.common.core.domain.model.LoginUser; +import org.ruoyi.common.core.exception.base.BaseException; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.satoken.utils.LoginHelper; +import org.ruoyi.system.domain.VoiceRole; +import org.ruoyi.system.domain.bo.VoiceRoleBo; +import org.ruoyi.system.domain.vo.VoiceRoleVo; +import org.ruoyi.system.mapper.VoiceRoleMapper; +import org.ruoyi.system.request.RoleListDto; +import org.ruoyi.system.request.RoleRequest; +import org.ruoyi.system.request.SimpleGenerateRequest; +import org.ruoyi.system.response.RoleResponse; +import org.ruoyi.system.response.SimpleGenerateDataResponse; +import org.ruoyi.system.response.SimpleGenerateResponse; +import org.ruoyi.system.response.rolelist.ContentResponse; +import org.ruoyi.system.response.rolelist.RoleListResponse; +import org.ruoyi.system.response.rolelist.RoleListVO; +import org.ruoyi.system.service.IChatCostService; +import org.ruoyi.system.service.IVoiceRoleService; +import org.ruoyi.system.util.AudioOkHttpUtil; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.*; + +/** + * 配音角色Service业务层处理 + * + * @author Lion Li + * @date 2024-03-19 + */ +@RequiredArgsConstructor +@Service +@Slf4j +public class VoiceRoleServiceImpl implements IVoiceRoleService { + + private final VoiceRoleMapper baseMapper; + + private final IChatCostService chatService; + + private final AudioOkHttpUtil audioOkHttpUtil; + + /** + * 查询配音角色 + */ + @Override + public VoiceRoleVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 查询配音角色列表 + */ + @Override + public TableDataInfo queryPageList(VoiceRoleBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询配音角色列表 + */ + @Override + public List queryList(VoiceRoleBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(VoiceRoleBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.like(StringUtils.isNotBlank(bo.getName()), VoiceRole::getName, bo.getName()); + lqw.eq(StringUtils.isNotBlank(bo.getDescription()), VoiceRole::getDescription, bo.getDescription()); + lqw.eq(StringUtils.isNotBlank(bo.getAvatar()), VoiceRole::getAvatar, bo.getAvatar()); + lqw.eq(bo.getCreateBy()!=null, VoiceRole::getCreateBy, bo.getCreateBy()); + lqw.eq(StringUtils.isNotBlank(bo.getVoiceId()), VoiceRole::getVoiceId, bo.getVoiceId()); + lqw.eq(StringUtils.isNotBlank(bo.getFileUrl()), VoiceRole::getFileUrl, bo.getFileUrl()); + return lqw; + } + + /** + * 新增配音角色 + */ + @Override + public Boolean insertByBo(RoleRequest roleRequest) { + try { + String prompt = convertFileToBase64(roleRequest.getPrompt()); + roleRequest.setPrompt("data:audio/x-m4a;base64," + prompt); + + String avatar = convertFileToBase64(roleRequest.getAvatar()); + roleRequest.setAvatar("data:image/png;base64," + avatar); + + } catch (IOException e) { + log.error("转换base64出现错误:{}", e.getMessage()); + } + // 创建一个Request对象来配置你的请求 + String json = JSONUtil.toJsonStr(roleRequest); + Request postRequest = audioOkHttpUtil.createPostRequest("api/tts/voice", json); + String body = audioOkHttpUtil.executeRequest(postRequest); + RoleResponse bean = JSONUtil.toBean(body, RoleResponse.class); + VoiceRole addVoiceRole = new VoiceRole(); + addVoiceRole.setName(roleRequest.getName()); + addVoiceRole.setDescription(roleRequest.getDescription()); + addVoiceRole.setAvatar(bean.getData().getMetadata().getAvatar()); + addVoiceRole.setVoiceId(bean.getData().getId()); + addVoiceRole.setFileUrl(bean.getData().getMetadata().getPrompts().get(0).getPromptOriginAudioStorageUrl()); + return baseMapper.insert(addVoiceRole) > 0; + } + + private static String convertFileToBase64(String fileUrl) throws IOException { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder().url(fileUrl).build(); + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) throw new IOException("Failed to download file: " + response); + byte[] fileData = response.body().bytes(); + return Base64.getEncoder().encodeToString(fileData); + } + + /** + * 修改配音角色 + */ + @Override + public Boolean updateByBo(VoiceRoleBo bo) { + VoiceRole update = MapstructUtils.convert(bo, VoiceRole.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(VoiceRole entity) { + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除配音角色 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 实时生成语音 + * + * @param simpleGenerateRequest 生成语音对象 + * @return 生成的语音信息 + */ + @Override + public SimpleGenerateDataResponse simpleGenerate(SimpleGenerateRequest simpleGenerateRequest) { + double charge = calculateCharge(simpleGenerateRequest.getText()); + // 扣除费用并且保存消息记录 + chatService.taskDeduct(simpleGenerateRequest.getModel(), simpleGenerateRequest.getText(), charge); + // 创建一个Request对象来配置你的请求 + String json = JSONUtil.toJsonStr(simpleGenerateRequest); + Request postRequest = audioOkHttpUtil.createPostRequest("api/tts/simple-generate", json); + String body = audioOkHttpUtil.executeRequest(postRequest); + SimpleGenerateResponse bean = JSONUtil.toBean(body, SimpleGenerateResponse.class); + return bean.getData(); + } + + /** + * 查询市场角色 + * + * @return 角色列表 + */ + @Override + public List roleList() { + Request postRequest = audioOkHttpUtil.createGetRequest("api/tts/voice"); + String body = audioOkHttpUtil.executeRequest(postRequest); + RoleListResponse bean = JSONUtil.toBean(body, RoleListResponse.class); + List roleList = new ArrayList<>(); + for (ContentResponse element : bean.getData()) { + String name = element.getName(); + String description = element.getMetadata().getDescription(); + String voicesId = element.getId(); + String avatar = element.getMetadata().getAvatar(); + String previewAudio; + if (element.getMetadata().getPrompts() == null) { + // 从JSON中解析出的数据没有prompts + previewAudio = element.getMetadata().getPreviewAudio(); + } else { + previewAudio = element.getMetadata().getPrompts().get(0).getPromptOriginAudioStorageUrl(); + } + roleList.add(new RoleListVO(name, description, voicesId, previewAudio, avatar)); + } + return roleList; + + } + + /** + * 收藏市场角色 + */ + @Override + public void copyRole(RoleListDto roleListDto) { + // 保存至数据库 + VoiceRole voiceRole = new VoiceRole(); + voiceRole.setName(roleListDto.getName()); + voiceRole.setDescription(roleListDto.getDescription()); + voiceRole.setFileUrl(roleListDto.getPreviewAudio()); + voiceRole.setVoiceId(roleListDto.getVoicesId()); + voiceRole.setAvatar(roleListDto.getAvatar()); + baseMapper.insert(voiceRole); + } + + /** + * 根据文本长度计算扣除的金额。 + * + * @param text 输入的文本 + * @return 扣除的金额 + */ + public static double calculateCharge(String text) { + if (text == null || text.isEmpty()) { + return 0.0; + } + + int length = text.length(); + double charge = 0.0; + + while (length > 0) { + if (length >= 500) { + // 对于每500个字符,扣除0.5元 + charge += (length / 500) * 0.5; + length %= 500; // 处理剩余字符 + } else if (length >= 100) { + // 对于100到499个字符,扣除0.2元 + charge += 0.2; + break; // 处理完毕,退出循环 + } else { + // 对于少于100个字符,扣除0.1元 + charge += 0.1; + break; // 处理完毕,退出循环 + } + } + return charge; + } + + public Long getUserId() { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new BaseException("用户未登录!"); + } + return loginUser.getUserId(); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WeixinUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WeixinUserServiceImpl.java new file mode 100644 index 00000000..9c84602f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WeixinUserServiceImpl.java @@ -0,0 +1,65 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.system.domain.model.ReceiveMessage; +import org.ruoyi.system.service.WeixinUserService; +import org.ruoyi.system.util.WeixinMsgUtil; +import org.ruoyi.system.util.WeixinQrCodeCacheUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Service; + +import java.util.Arrays; + + +@Slf4j +@Service +public class WeixinUserServiceImpl implements WeixinUserService { + + private String token = "panda"; + + @Override + public void checkSignature(String signature, String timestamp, String nonce) { + String[] arr = new String[] {token, timestamp, nonce}; + Arrays.sort(arr); + StringBuilder content = new StringBuilder(); + for (String str : arr) { + content.append(str); + } + String tmpStr = DigestUtils.sha1Hex(content.toString()); + if (tmpStr.equals(signature)) { + log.info("check success"); + return; + } + log.error("check fail"); + throw new RuntimeException("check fail"); + } + + @Override + public String handleWeixinMsg(String requestBody) { + ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody); + // 扫码登录 + if (WeixinMsgUtil.isScanQrCode(receiveMessage)) { + return handleScanLogin(receiveMessage); + } + // 关注 + if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) { + return receiveMessage.getReplyTextMsg("欢迎关注【熊猫办公助手】,请访问https://web.pandarobot.chat/使用AI助手"); + } + return receiveMessage.getReplyTextMsg("收到(自动回复)"); + } + + /** + * 处理扫码登录 + * + * @param receiveMessage + * @return + */ + private String handleScanLogin(ReceiveMessage receiveMessage) { + String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage); + if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) { + String openId = receiveMessage.getFromUserName(); + WeixinQrCodeCacheUtil.put(qrCodeTicket, openId); + } + return receiveMessage.getReplyTextMsg("你已成功登录!"); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobConfigServiceImpl.java new file mode 100644 index 00000000..18887e63 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobConfigServiceImpl.java @@ -0,0 +1,138 @@ +package org.ruoyi.system.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.ruoyi.system.domain.SysUser; +import org.ruoyi.system.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.WxRobConfigBo; +import org.ruoyi.system.domain.vo.WxRobConfigVo; +import org.ruoyi.system.domain.WxRobConfig; +import org.ruoyi.system.mapper.WxRobConfigMapper; +import org.ruoyi.system.service.IWxRobConfigService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author Lion Li + * @date 2024-05-01 + */ +@RequiredArgsConstructor +@Service +public class WxRobConfigServiceImpl implements IWxRobConfigService { + + private final WxRobConfigMapper baseMapper; + + private final SysUserMapper sysUserMapper; + + /** + * 查询【请填写功能名称】 + */ + @Override + public WxRobConfigVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public TableDataInfo queryPageList(WxRobConfigBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + if(CollectionUtil.isEmpty(result.getRecords())){ + return TableDataInfo.build(result); + } + // 获取所有userId + List userIds = result.getRecords().stream() + .map(WxRobConfigVo::getUserId) + .collect(Collectors.toList()); + // 一次性查询所有userName + Map userIdToUserNameMap = getUserNamesByUserIds(userIds); + // 设置userName + result.getRecords().forEach(wxRobConfigVo -> { + wxRobConfigVo.setUserName(userIdToUserNameMap.get(wxRobConfigVo.getUserId())); + }); + return TableDataInfo.build(result); + } + + private Map getUserNamesByUserIds(List userIds) { + // 实现批量查询userName的逻辑,例如通过sysUserMapper查询sys_user表 + List sysUsers = sysUserMapper.selectBatchIds(userIds); + return sysUsers.stream() + .collect(Collectors.toMap(SysUser::getUserId, SysUser::getUserName)); + } + /** + * 查询【请填写功能名称】列表 + */ + @Override + public List queryList(WxRobConfigBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(WxRobConfigBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, WxRobConfig::getUserId, bo.getUserId()); + lqw.eq(StringUtils.isNotBlank(bo.getUniqueKey()), WxRobConfig::getUniqueKey, bo.getUniqueKey()); + lqw.eq(bo.getDefaultFriend() != null, WxRobConfig::getDefaultFriend, bo.getDefaultFriend()); + lqw.eq(bo.getDefaultGroup() != null, WxRobConfig::getDefaultGroup, bo.getDefaultGroup()); + lqw.eq(bo.getEnable() != null, WxRobConfig::getEnable, bo.getEnable()); + return lqw; + } + + /** + * 新增【请填写功能名称】 + */ + @Override + public Boolean insertByBo(WxRobConfigBo bo) { + WxRobConfig add = MapstructUtils.convert(bo, WxRobConfig.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改【请填写功能名称】 + */ + @Override + public Boolean updateByBo(WxRobConfigBo bo) { + WxRobConfig update = MapstructUtils.convert(bo, WxRobConfig.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(WxRobConfig entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除【请填写功能名称】 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobKeywordServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobKeywordServiceImpl.java new file mode 100644 index 00000000..461ca1a7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobKeywordServiceImpl.java @@ -0,0 +1,115 @@ +package org.ruoyi.system.service.impl; + +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.ruoyi.system.domain.bo.WxRobKeywordBo; +import org.ruoyi.system.domain.vo.WxRobKeywordVo; +import org.ruoyi.system.domain.WxRobKeyword; +import org.ruoyi.system.mapper.WxRobKeywordMapper; +import org.ruoyi.system.service.IWxRobKeywordService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author Lion Li + * @date 2024-05-01 + */ +@RequiredArgsConstructor +@Service +public class WxRobKeywordServiceImpl implements IWxRobKeywordService { + + private final WxRobKeywordMapper baseMapper; + + /** + * 查询【请填写功能名称】 + */ + @Override + public WxRobKeywordVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public TableDataInfo queryPageList(WxRobKeywordBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public List queryList(WxRobKeywordBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(WxRobKeywordBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getUniqueKey()), WxRobKeyword::getUniqueKey, bo.getUniqueKey()); + lqw.eq(StringUtils.isNotBlank(bo.getKeyData()), WxRobKeyword::getKeyData, bo.getKeyData()); + lqw.eq(StringUtils.isNotBlank(bo.getValueData()), WxRobKeyword::getValueData, bo.getValueData()); + lqw.eq(StringUtils.isNotBlank(bo.getTypeData()), WxRobKeyword::getTypeData, bo.getTypeData()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), WxRobKeyword::getNickName, bo.getNickName()); + lqw.eq(bo.getToGroup() != null, WxRobKeyword::getToGroup, bo.getToGroup()); + lqw.eq(bo.getEnable() != null, WxRobKeyword::getEnable, bo.getEnable()); + return lqw; + } + + /** + * 新增【请填写功能名称】 + */ + @Override + public Boolean insertByBo(WxRobKeywordBo bo) { + WxRobKeyword add = MapstructUtils.convert(bo, WxRobKeyword.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改【请填写功能名称】 + */ + @Override + public Boolean updateByBo(WxRobKeywordBo bo) { + WxRobKeyword update = MapstructUtils.convert(bo, WxRobKeyword.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(WxRobKeyword entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除【请填写功能名称】 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobRelationServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobRelationServiceImpl.java new file mode 100644 index 00000000..bfb2cb0a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/WxRobRelationServiceImpl.java @@ -0,0 +1,114 @@ +package org.ruoyi.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.ruoyi.common.core.utils.MapstructUtils; +import org.ruoyi.common.core.utils.StringUtils; +import org.ruoyi.common.mybatis.core.page.PageQuery; +import org.ruoyi.common.mybatis.core.page.TableDataInfo; +import org.ruoyi.system.domain.WxRobRelation; +import org.ruoyi.system.domain.bo.WxRobRelationBo; +import org.ruoyi.system.domain.vo.WxRobRelationVo; +import org.ruoyi.system.mapper.WxRobRelationMapper; +import org.ruoyi.system.service.IWxRobRelationService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 【请填写功能名称】Service业务层处理 + * + * @author Lion Li + * @date 2024-05-01 + */ +@RequiredArgsConstructor +@Service +public class WxRobRelationServiceImpl implements IWxRobRelationService { + + private final WxRobRelationMapper baseMapper; + + /** + * 查询【请填写功能名称】 + */ + @Override + public WxRobRelationVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public TableDataInfo queryPageList(WxRobRelationBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询【请填写功能名称】列表 + */ + @Override + public List queryList(WxRobRelationBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(WxRobRelationBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getOutKey()), WxRobRelation::getOutKey, bo.getOutKey()); + lqw.eq(StringUtils.isNotBlank(bo.getUniqueKey()), WxRobRelation::getUniqueKey, bo.getUniqueKey()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), WxRobRelation::getNickName, bo.getNickName()); + lqw.eq(bo.getToGroup() != null, WxRobRelation::getToGroup, bo.getToGroup()); + lqw.eq(bo.getEnable() != null, WxRobRelation::getEnable, bo.getEnable()); + lqw.eq(StringUtils.isNotBlank(bo.getWhiteList()), WxRobRelation::getWhiteList, bo.getWhiteList()); + return lqw; + } + + /** + * 新增【请填写功能名称】 + */ + @Override + public Boolean insertByBo(WxRobRelationBo bo) { + WxRobRelation add = MapstructUtils.convert(bo, WxRobRelation.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改【请填写功能名称】 + */ + @Override + public Boolean updateByBo(WxRobRelationBo bo) { + WxRobRelation update = MapstructUtils.convert(bo, WxRobRelation.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(WxRobRelation entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除【请填写功能名称】 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AesUtils.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AesUtils.java new file mode 100644 index 00000000..09666306 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AesUtils.java @@ -0,0 +1,62 @@ +package org.ruoyi.system.util; + +import lombok.SneakyThrows; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; + +/** + * Java 使用 AES 加密算法进行加密解密 + */ +@Component +public class AesUtils { + + /** + * 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥,否则就会遇到java.security.InvalidKeyException异常) + */ + // @Value("${ase.util.secret}") + public String key; + + /** + * AES算法加密 + * + * @Param:text原文 + * @Param:key密钥 + */ + + @SneakyThrows + public String aesEncrypt(String text) { + // 创建AES加密算法实例(根据传入指定的秘钥进行加密) + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + // 初始化为加密模式,并将密钥注入到算法中 + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + // 将传入的文本加密 + byte[] encrypted = cipher.doFinal(text.getBytes()); + //生成密文 + // 将密文进行Base64编码,方便传输 + return Base64.getEncoder().encodeToString(encrypted); + } + + /** + * AES算法解密 + * + * @Param:base64Encrypted密文 + * @Param:key密钥 + */ + public String aesDecrypt(String base64Encrypted) throws Exception { + // 创建AES解密算法实例 + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + + // 初始化为解密模式,并将密钥注入到算法中 + cipher.init(Cipher.DECRYPT_MODE, keySpec); + // 将Base64编码的密文解码 + byte[] encrypted = Base64.getDecoder().decode(base64Encrypted); + // 解密 + byte[] decrypted = cipher.doFinal(encrypted); + return new String(decrypted); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AudioOkHttpUtil.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AudioOkHttpUtil.java new file mode 100644 index 00000000..d1328c7d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/AudioOkHttpUtil.java @@ -0,0 +1,67 @@ +package org.ruoyi.system.util; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import org.ruoyi.common.core.service.ConfigService; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * @author WangLe + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class AudioOkHttpUtil { + + private final ConfigService configService; + + private static final String AUTHORIZATION = "Authorization"; + + private final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(300, TimeUnit.SECONDS) + .writeTimeout(300, TimeUnit.SECONDS) + .readTimeout(300, TimeUnit.SECONDS) + .build(); + + public String executeRequest(Request request) { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + return response.body() != null ? response.body().string() : null; + } catch (IOException e) { + // 这里应根据实际情况使用适当的日志记录方式 + log.error("请求失败: {}",e.getMessage()); + return null; + } + } + + public Request createPostRequest(String url, String json) { + MediaType JSON = MediaType.get("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(json, JSON); + return new Request.Builder() + .url(getKey("apiHost") + url) + .post(body) + .header("Content-Type", "application/json") + .header(AUTHORIZATION, "Bearer "+getKey("apiKey")) + .build(); + } + + + public Request createGetRequest(String url) { + return new Request.Builder() + .url(getKey("apiHost") + url) + .header(AUTHORIZATION, "Bearer "+getKey("apiKey")) + .build(); + } + + + public String getKey(String key) { + return configService.getConfigValue("audio", key); + } +} + diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/KeyUtils.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/KeyUtils.java new file mode 100644 index 00000000..973b5ebd --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/KeyUtils.java @@ -0,0 +1,24 @@ +package org.ruoyi.system.util; + +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.UUID; + +/** + * @author https://www.wdbyte.com + */ +public class KeyUtils { + + public synchronized static String key6() { + return RandomStringUtils.randomAlphanumeric(6); + } + + public synchronized static String key16() { + return RandomStringUtils.randomAlphanumeric(16); + } + + public static String uuid32() { + return UUID.randomUUID().toString().replace("-", ""); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/OrderNumberGenerator.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/OrderNumberGenerator.java new file mode 100644 index 00000000..fca7914b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/OrderNumberGenerator.java @@ -0,0 +1,24 @@ +package org.ruoyi.system.util; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ThreadLocalRandom; +public class OrderNumberGenerator { + // 订单编号前缀 + private static final String PREFIX = "NO"; + + // 时间格式化 + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); + + // 生成订单编号 + public static String generate() { + // 获取当前日期时间字符串 + String dateTimeStr = DATE_FORMAT.format(new Date()); + + // 生成随机数 (这里举例生成一个5位随机数) + int randomNum = ThreadLocalRandom.current().nextInt(10000, 99999); + + // 拼接订单编号 + return dateTimeStr + randomNum; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinApiUtil.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinApiUtil.java new file mode 100644 index 00000000..49d2b18f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinApiUtil.java @@ -0,0 +1,81 @@ +package org.ruoyi.system.util; + +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.system.domain.model.WeixinQrCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.time.LocalDateTime; + +/** + * @author https://www.wdbyte.com + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class WeixinApiUtil { + + private final ConfigService configService; + + private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; + + private static String ACCESS_TOKEN = null; + private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null; + /** + * 二维码 Ticket 过期时间 + */ + private static int QR_CODE_TICKET_TIMEOUT = 10 * 60; + + /** + * 获取 access token + * + * @return + */ + public synchronized String getAccessToken() { + if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) { + return ACCESS_TOKEN; + } + String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + getKey("appid") + "&secret=" + + getKey("secret"); + String result = HttpUtil.get(api); + JSONObject jsonObject = JSON.parseObject(result); + ACCESS_TOKEN = jsonObject.getString("access_token"); + ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10); + return ACCESS_TOKEN; + } + + /** + * 获取二维码 Ticket + * + * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html + * + * @return + */ + public WeixinQrCode getQrCode() { + String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken(); + String jsonBody = String.format("{\n" + + " \"expire_seconds\": %d,\n" + + " \"action_name\": \"QR_STR_SCENE\",\n" + + " \"action_info\": {\n" + + " \"scene\": {\n" + + " \"scene_str\": \"%s\"\n" + + " }\n" + + " }\n" + + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32()); + String result = HttpUtil.post(api, jsonBody); + log.info("get qr code params:{}", jsonBody); + log.info("get qr code result:{}", result); + WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class); + weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString()); + return weixinQrCode; + } + + public String getKey(String key) { + return configService.getConfigValue("weixin", key); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinMsgUtil.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinMsgUtil.java new file mode 100644 index 00000000..e958d95f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinMsgUtil.java @@ -0,0 +1,66 @@ +package org.ruoyi.system.util; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.ruoyi.system.domain.model.ReceiveMessage; + +/** + * @author https://www.wdbyte.com + */ +public class WeixinMsgUtil { + + // 事件-关注 + private static String EVENT_SUBSCRIBE = "subscribe"; + + /** + * 微信消息转对象 + * + * @param xml + * @return + */ + public static ReceiveMessage msgToReceiveMessage(String xml) { + JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml)); + ReceiveMessage receiveMessage = new ReceiveMessage(); + receiveMessage.setToUserName(jsonObject.getString("ToUserName")); + receiveMessage.setFromUserName(jsonObject.getString("FromUserName")); + receiveMessage.setCreateTime(jsonObject.getString("CreateTime")); + receiveMessage.setMsgType(jsonObject.getString("MsgType")); + receiveMessage.setContent(jsonObject.getString("Content")); + receiveMessage.setMsgId(jsonObject.getString("MsgId")); + receiveMessage.setEvent(jsonObject.getString("Event")); + receiveMessage.setTicket(jsonObject.getString("Ticket")); + return receiveMessage; + } + + /** + * 是否是订阅事件 + * + * @param receiveMessage + * @return + */ + public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) { + return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE); + } + + /** + * 是否是二维码扫描事件 + * + * @param receiveMessage + * @return + */ + public static boolean isScanQrCode(ReceiveMessage receiveMessage) { + return StringUtils.isNotEmpty(receiveMessage.getTicket()); + } + + /** + * 获取扫描的二维码 Ticket + * + * @param receiveMessage + * @return + */ + public static String getQrCodeTicket(ReceiveMessage receiveMessage) { + return receiveMessage.getTicket(); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinQrCodeCacheUtil.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinQrCodeCacheUtil.java new file mode 100644 index 00000000..df01aecd --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/WeixinQrCodeCacheUtil.java @@ -0,0 +1,34 @@ +package org.ruoyi.system.util; + +import java.util.LinkedHashMap; + +/** + * 微信二维码缓存工具类 + * + * @author https://www.wdbyte.com + */ +public class WeixinQrCodeCacheUtil { + private static long MAX_CACHE_SIZE = 10000; + private static LinkedHashMap QR_CODE_TICKET_MAP = new LinkedHashMap<>(); + + /** + * 增加一个 Ticket + * 首次 put:value 为 "" + * 再次 put: value 有 openId,若openId已经存在,则已被扫码 + * + * @param key + * @param value + */ + public synchronized static void put(String key, String value) { + QR_CODE_TICKET_MAP.put(key, value); + if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) { + String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get(); + QR_CODE_TICKET_MAP.remove(first); + } + } + + public synchronized static String get(String key) { + return QR_CODE_TICKET_MAP.remove(key); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/XmlUtil.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/XmlUtil.java new file mode 100644 index 00000000..00d023bd --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/util/XmlUtil.java @@ -0,0 +1,28 @@ +package org.ruoyi.system.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * @author https://www.wdbyte.com + */ +@Slf4j +public class XmlUtil { + + public static String xml2json(String requestBody) { + requestBody = StringUtils.trim(requestBody); + XmlMapper xmlMapper = new XmlMapper(); + JsonNode node = null; + try { + node = xmlMapper.readTree(requestBody.getBytes()); + ObjectMapper jsonMapper = new ObjectMapper(); + return jsonMapper.writeValueAsString(node); + } catch (Exception e) { + log.error("xml 2 json error,msg:" + e.getMessage(), e); + } + return null; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatConfigMapper.xml new file mode 100644 index 00000000..b620c1e1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatConfigMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVisitorUsageMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVisitorUsageMapper.xml new file mode 100644 index 00000000..7a49c859 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVisitorUsageMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVoucherMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVoucherMapper.xml new file mode 100644 index 00000000..e5a80225 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/ChatVoucherMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml new file mode 100644 index 00000000..d1ffcf60 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/PaymentOrdersMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml index 3edbf94e..903abecd 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml index a5c7b0a0..08e9e902 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -2,9 +2,9 @@ - + - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml index b194ad50..a2ace81e 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysModelMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml index f821637f..8426de07 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -2,6 +2,8 @@ - + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeStateMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeStateMapper.xml new file mode 100644 index 00000000..48d72e7f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysNoticeStateMapper.xml @@ -0,0 +1,11 @@ + + + + + + UPDATE sys_notice_state SET read_status = 1 + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml index 91ba8847..3f0d4b60 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml index a547b006..211a2f23 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml @@ -2,6 +2,6 @@ - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml index 4b5bab15..b981c0c5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml @@ -1,5 +1,5 @@ - + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml new file mode 100644 index 00000000..1b7c4a1f --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPackagePlanMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml index 59ba703b..14a382c3 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -2,9 +2,9 @@ - + - + - select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,u.user_balance,u.user_grade, + select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,u.user_balance,u.user_grade,u.domain_name, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id @@ -78,7 +79,7 @@ select u.user_id from sys_user u diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml new file mode 100644 index 00000000..c7af5bf3 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobConfigMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml new file mode 100644 index 00000000..32582d17 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobKeywordMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml new file mode 100644 index 00000000..aab96635 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/WxRobRelationMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/script/docker/database.yml b/script/docker/database.yml index 0368fd27..6d37c803 100644 --- a/script/docker/database.yml +++ b/script/docker/database.yml @@ -26,7 +26,7 @@ services: # 时区上海 TZ: Asia/Shanghai ACCEPT_EULA: "Y" - SA_PASSWORD: "Ruoyi@123" + SA_PASSWORD: "ruoyi@123" ports: - "1433:1433" volumes: diff --git a/script/docker/docker-compose.yml b/script/docker/docker-compose.yml index 14b1d47c..0e1d01df 100644 --- a/script/docker/docker-compose.yml +++ b/script/docker/docker-compose.yml @@ -1,9 +1,5 @@ version: '3' -networks: - mynetwork: - driver: bridge - services: mysql: image: mysql:8.0.33 @@ -22,23 +18,6 @@ services: --collation-server=utf8mb4_general_ci --explicit_defaults_for_timestamp=true --lower_case_table_names=1 - privileged: true - networks: - - mynetwork - - redis: - image: redis:6.2.12 - container_name: redis - ports: - - "6379:6379" - environment: - TZ: Asia/Shanghai - volumes: - - /docker/redis/conf:/redis/config:rw - - /docker/redis/data/:/redis/data/:rw - privileged: true - networks: - - mynetwork ruoyi-server: image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/ai:1.2.1 @@ -49,16 +28,6 @@ services: TZ: Asia/Shanghai # 运行端口号 SERVER_PORT: 6039 - # 中转key - CHAT_API_KEY: ${CHAT_API_KEY} - # 中转地址 - CHAT_API_HOST: ${CHAT_API_HOST} - # 邮箱授权码 - MAIL_PASS: ${MAIL_PASS} - # 易支付商户ID - PAY_PID: ${PAY_PID} - # 易支付商户密钥 - PAY_KEY: ${PAY_KEY} # 数据库连接地址 DB_URL: jdbc:mysql://mysql:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true # 数据库用户名 @@ -69,27 +38,27 @@ services: REDIS_HOST: redis # Redis端口 REDIS_PORT: 6379 + # 数据库索引 + REDIS_DATABASE: 0 + # Redis密码 + REDIS_PASSWORD: + # 连接超时时间 + REDIS_TIMEOUT: 10s volumes: - /docker/server2/logs/:/ruoyi/server/logs/ - privileged: true - networks: - - mynetwork ruoyi-web: image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/web:1.2.1 ports: - "8081:8081" container_name: ruoyi-web - privileged: true - networks: - - mynetwork + ruoyi-admin: image: registry.cn-shanghai.aliyuncs.com/ruoyi-ai/admin:1.2.1 ports: - "8082:8082" container_name: ruoyi-admin - privileged: true - networks: - - mynetwork + + diff --git a/script/docker/ry-vue.sql b/script/docker/ry-vue.sql index 2e440899..5473fabb 100644 --- a/script/docker/ry-vue.sql +++ b/script/docker/ry-vue.sql @@ -1,89 +1,198 @@ /* Navicat MySQL Data Transfer - Source Server : ruoyi-vue + Source Server : ry-vue Source Server Type : MySQL Source Server Version : 50740 Source Host : 127.0.0.1:3306 - Source Schema : ruoyi-vue + Source Schema : ry-vue Target Server Type : MySQL Target Server Version : 50740 File Encoding : 65001 - Date: 15/05/2024 10:49:39 + Date: 27/05/2024 17:34:48 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for chat_config +-- ---------------------------- +DROP TABLE IF EXISTS `chat_config`; +CREATE TABLE `chat_config` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `category` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置类型', + `config_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置名称', + `config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置值', + `config_dict` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '说明', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `version` int(11) NULL DEFAULT NULL COMMENT '版本', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新IP', + `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `unique_category_key`(`category`, `config_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1794956871156207618 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配置信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_config +-- ---------------------------- +INSERT INTO `chat_config` VALUES (1779450794448789505, 'chat', 'apiKey', 'sk-xx', 'API 密钥', 103, '2024-04-14 18:05:05', '1', '1', '2024-04-23 23:56:54', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779450794872414210, 'chat', 'apiHost', 'https://api.pandarobot.chat/', 'API 地址', 103, '2024-04-14 18:05:05', '1', '1', '2024-04-23 23:56:54', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497340548784129, 'pay', 'pid', '1000', '商户PID', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497340938854401, 'pay', 'key', 'xx', '商户密钥', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341135986690, 'pay', 'payUrl', 'https://pay.pandarobot.chat/mapi.php', '支付地址', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341400227842, 'pay', 'notify_url', 'https://www.pandarobot.chat/pay/notifyUrl', '回调地址', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341588971522, 'pay', 'return_url', 'https://www.pandarobot.chat/pay/returnUrl', '跳转通知', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580331835394, 'mail', 'host', 'smtp.163.com', '主机地址', 103, '2024-04-14 22:14:34', '1', '1', '2024-04-28 17:46:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580658991106, 'mail', 'port', '465', '主机端口', 103, '2024-04-14 22:14:34', '1', '1', '2024-04-28 17:46:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580919037953, 'mail', 'from', 'ageerle@163.com', '发送方', 103, '2024-04-14 22:14:34', '1', '1', '2024-04-28 17:46:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513581107781634, 'mail', 'user', 'ageerle@163.com', '用户名', 103, '2024-04-14 22:14:34', '1', '1', '2024-04-28 17:46:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513581309108225, 'mail', 'pass', 'xx', '授权码', 103, '2024-04-14 22:14:34', '1', '1', '2024-04-28 17:46:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779726450625687553, 'mj', 'apiKey', 'sk-xx', 'API 密钥', 103, '2024-04-15 12:20:26', '1', '1', '2024-04-23 23:56:58', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779726451036729346, 'mj', 'apiHost', 'https://api.pandarobot.chat/', 'API 地址', 103, '2024-04-15 12:20:26', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331509679181825, 'mj', 'imagine', '0.3', '文生图', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331509939228674, 'mj', 'blend', '0.3', '图生图', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510199275522, 'mj', 'describe', '0.1', '图生文', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510392213505, 'mj', 'change', '0.3', '变化价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510652260353, 'mj', 'upsample', '0.1', '放大价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510845198338, 'mj', 'inpaint', '0.3', '局部重绘', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331511117828098, 'mj', 'faceSwapping', '0.3', '换脸价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331511306571778, 'mj', 'shorten', '0.1', '提示词分析', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782766864937119746, 'mail', 'amount', '1', '注册额度', 103, '2024-04-23 21:41:57', '1', '1', '2024-04-28 17:46:47', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1784166479104135169, 'audio', 'apiKey', 'xx', 'API 密钥', 103, '2024-04-27 18:23:31', '1', '1', '2024-04-27 18:24:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1784166479615840258, 'audio', 'apiHost', 'https://v1.reecho.cn/', 'API 地址', 103, '2024-04-27 18:23:32', '1', '1', '2024-04-27 18:24:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372188569602, 'review', 'enabled', 'false', '文本审核', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372637360129, 'review', 'apiKey', 'xx', 'apiKey', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372897406977, 'review', 'secretKey', 'xx', 'secretKey', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069350789324801, 'weixin', 'appId', 'xx', '应用ID', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069351246503938, 'weixin', 'appSecret', 'xx', '应用密钥', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069351246503939, 'weixin', 'mchId', 'xx', '商户ID', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792183360796790785, 'weixin', 'notifyUrl', 'https://www.pandarobot.chat/pay/notify/wxOrder', '回调地址', 103, '2024-05-19 21:19:45', '1', '1', '2024-05-19 22:34:40', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792183361065226241, 'weixin', 'enabled', 'false', '开启支付', 103, '2024-05-19 21:19:45', '1', '1', '2024-05-19 22:34:40', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207511704100866, 'sys', 'name', '熊猫助手后台管理系统', '网站名称', 103, '2024-05-19 22:55:43', '1', '1', '2024-05-19 23:24:19', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512089976834, 'sys', 'logoImage', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/05/19/4c106628754b4bd882a4c002eaa317f5.jpg', '网站logo', 103, '2024-05-19 22:55:43', '1', '1', '2024-05-19 23:24:19', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512412938241, 'sys', 'copyright', 'Copyright © 溪溪网络科技工作室 鄂ICP备2023007672号-1', '版权信息', 103, '2024-05-19 22:55:43', '1', '1', '2024-05-19 23:24:20', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512740093954, 'sys', 'customImage', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/05/19/2faba7a5fa174d7c8d573ce3f031ec51.jpg', '客服二维码', 103, '2024-05-19 22:55:43', '1', '1', '2024-05-19 23:24:20', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512740093955, 'sys', 'activate', 'false', '系统激活状态', 103, '2024-05-19 22:55:43', '1', '1', '2024-05-27 13:03:23', NULL, NULL, '0', NULL, 0); + -- ---------------------------- -- Table structure for chat_message -- ---------------------------- DROP TABLE IF EXISTS `chat_message`; CREATE TABLE `chat_message` ( - `id` bigint(20) NOT NULL COMMENT '主键', - `user_id` bigint(20) NOT NULL COMMENT '用户id', - `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', - `deduct_cost` double(20, 10) NULL DEFAULT 0.0000000000 COMMENT '扣除金额\r\n\r\n', - `total_tokens` int(11) NULL DEFAULT NULL COMMENT '累计 Tokens', - `model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`) USING BTREE + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户id', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', + `deduct_cost` double(20, 2) NULL DEFAULT 0.00 COMMENT '扣除金额\r\n\r\n', + `total_tokens` int(20) NULL DEFAULT NULL COMMENT '累计 Tokens', + `model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of chat_message -- ---------------------------- +-- ---------------------------- +-- Table structure for chat_sensitive_word +-- ---------------------------- +DROP TABLE IF EXISTS `chat_sensitive_word`; +CREATE TABLE `chat_sensitive_word` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '敏感词内容', + `status` int(11) NOT NULL COMMENT '状态 1 启用 2 停用', + `is_deleted` int(11) NULL DEFAULT 0 COMMENT '是否删除 0 否 NULL 是', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '敏感词表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_sensitive_word +-- ---------------------------- + -- ---------------------------- -- Table structure for chat_token -- ---------------------------- DROP TABLE IF EXISTS `chat_token`; CREATE TABLE `chat_token` ( - `id` bigint(20) NOT NULL COMMENT '主键', - `user_id` bigint(20) NOT NULL COMMENT '用户', - `token` int(11) NULL DEFAULT NULL COMMENT '待结算token', - `model_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', - PRIMARY KEY (`id`) USING BTREE + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户', + `token` int(10) NULL DEFAULT NULL COMMENT '待结算token', + `model_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'token信息' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of chat_token -- ---------------------------- +-- ---------------------------- +-- Table structure for chat_voucher +-- ---------------------------- +DROP TABLE IF EXISTS `chat_voucher`; +CREATE TABLE `chat_voucher` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '兑换码', + `amount` double(10, 2) NOT NULL COMMENT '兑换金额', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '兑换状态', + `balance_before` double(10, 2) NULL DEFAULT NULL COMMENT '兑换前余额', + `balance_after` double(10, 2) NULL DEFAULT NULL COMMENT '兑换后余额', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1786392743269474307 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户兑换记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_voucher +-- ---------------------------- + -- ---------------------------- -- Table structure for gen_table -- ---------------------------- DROP TABLE IF EXISTS `gen_table`; CREATE TABLE `gen_table` ( - `table_id` bigint(20) NOT NULL COMMENT '编号', - `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', - `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', - `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', - `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', - `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', - `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', - `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', - `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', - `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', - `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', - `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', - `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', - `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', - `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`table_id`) USING BTREE + `table_id` bigint(20) NOT NULL COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -97,45 +206,48 @@ INSERT INTO `gen_table` VALUES (1661288223728783361, 'sys_post', '岗位信息 INSERT INTO `gen_table` VALUES (1661288223821058050, 'sys_role', '角色信息表', NULL, NULL, 'SysRole', 'crud', 'org.dromara.system', 'system', 'role', '角色信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); INSERT INTO `gen_table` VALUES (1661288223925915650, 'sys_user_post', '用户与岗位关联表', NULL, NULL, 'SysUserPost', 'crud', 'org.dromara.system', 'system', 'userPost', '用户与岗位关联', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); INSERT INTO `gen_table` VALUES (1661288223967858689, 'sys_user_role', '用户和角色关联表', NULL, NULL, 'SysUserRole', 'crud', 'org.dromara.system', 'system', 'userRole', '用户和角色关联', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); -INSERT INTO `gen_table` VALUES (1661288224013996034, 'test_demo', '测试单表', NULL, NULL, 'TestDemo', 'crud', 'org.dromara.system', 'system', 'demo', '测试单', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); -INSERT INTO `gen_table` VALUES (1661288224185962497, 'test_tree', '测试树表', NULL, NULL, 'TestTree', 'crud', 'org.dromara.system', 'system', 'tree', '测试树', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); INSERT INTO `gen_table` VALUES (1661288385096241154, 'sys_config', '参数配置表', NULL, NULL, 'SysConfig', 'crud', 'org.dromara.system', 'system', 'config', '参数配置', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:10', 1, '2023-05-20 18:05:10', NULL); -INSERT INTO `gen_table` VALUES (1680196323445579778, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'com.xmzs.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); -INSERT INTO `gen_table` VALUES (1680196323521077249, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'com.xmzs.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); -INSERT INTO `gen_table` VALUES (1680199147407806465, 'sys_file_info', '文件记录表', NULL, NULL, 'SysFileInfo', 'crud', 'com.xmzs.system', 'system', 'fileInfo', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:53:56', 1, '2023-07-15 20:53:56', NULL); -INSERT INTO `gen_table` VALUES (1680481752850145282, 'sd_model_param', '模型参数信息表', NULL, NULL, 'SdModelParam', 'crud', 'com.xmzs.system', 'system', 'modelParam', '模型参数信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-16 15:18:34', 1, '2023-07-16 15:18:34', NULL); -INSERT INTO `gen_table` VALUES (1728684654923988993, 'chat_message', '聊天消息表', NULL, NULL, 'ChatMessage', 'crud', 'com.xmzs.system', 'system', 'message', '聊天消息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-11-26 15:28:10', 1, '2023-11-26 15:28:10', NULL); -INSERT INTO `gen_table` VALUES (1740573614897897473, 'payment_orders', '支付订单表', NULL, NULL, 'PaymentOrders', 'crud', 'com.xmzs.system', 'system', 'orders', '支付订单', 'Lion Li', '0', '/', NULL, 103, 1, '2023-12-27 23:04:45', 1, '2023-12-27 23:04:45', NULL); +INSERT INTO `gen_table` VALUES (1680196323445579778, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'org.ruoyi.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); +INSERT INTO `gen_table` VALUES (1680196323521077249, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'org.ruoyi.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); +INSERT INTO `gen_table` VALUES (1680199147407806465, 'sys_file_info', '文件记录表', NULL, NULL, 'SysFileInfo', 'crud', 'org.ruoyi.system', 'system', 'fileInfo', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:53:56', 1, '2023-07-15 20:53:56', NULL); +INSERT INTO `gen_table` VALUES (1680481752850145282, 'sd_model_param', '模型参数信息表', NULL, NULL, 'SdModelParam', 'crud', 'org.ruoyi.system', 'system', 'modelParam', '模型参数信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-16 15:18:34', 1, '2023-07-16 15:18:34', NULL); +INSERT INTO `gen_table` VALUES (1740573614897897473, 'payment_orders', '支付订单表', NULL, NULL, 'PaymentOrders', 'crud', 'org.ruoyi.system', 'system', 'orders', '支付订单', 'Lion Li', '0', '/', NULL, 103, 1, '2023-12-27 23:04:45', 1, '2023-12-27 23:04:45', NULL); +INSERT INTO `gen_table` VALUES (1775895242171076610, 'sys_model', '系统模型', NULL, NULL, 'SysModel', 'crud', 'org.ruoyi.system', 'system', 'model', '系统模型', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-04 22:27:08', 1, '2024-04-04 22:27:08', NULL); +INSERT INTO `gen_table` VALUES (1785390411861803009, 'wx_rob_config', '微信机器人管理', NULL, NULL, 'WxRobConfig', 'crud', 'org.ruoyi.system', 'system', 'robConfig', 'robot', 'Lion Li', '0', '/', '{\"treeCode\":null,\"treeName\":null,\"treeParentCode\":null,\"parentMenuId\":null}', 103, 1, '2024-05-01 01:10:04', 1, '2024-05-03 21:00:51', NULL); +INSERT INTO `gen_table` VALUES (1785390413745045505, 'wx_rob_keyword', '', NULL, NULL, 'WxRobKeyword', 'crud', 'org.ruoyi.system', 'system', 'robKeyword', '', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-30 23:51:44', 1, '2024-04-30 23:51:44', NULL); +INSERT INTO `gen_table` VALUES (1785390414860730369, 'wx_rob_relation', '', NULL, NULL, 'WxRobRelation', 'crud', 'org.ruoyi.system', 'system', 'robRelation', '', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-30 23:51:44', 1, '2024-04-30 23:51:44', NULL); +INSERT INTO `gen_table` VALUES (1786379560181882881, 'chat_voucher', '用户兑换记录', NULL, NULL, 'ChatVoucher', 'crud', 'org.ruoyi.system', 'system', 'voucher', '用户兑换记录', 'Lion Li', '0', '/', NULL, 103, 1, '2024-05-03 20:57:18', 1, '2024-05-03 20:57:18', NULL); +INSERT INTO `gen_table` VALUES (1789155611035381761, 'sys_notice_state', '用户阅读状态表', NULL, NULL, 'SysNoticeState', 'crud', 'org.ruoyi.system', 'system', 'noticeState', '用户阅读状态', 'Lion Li', '0', '/', NULL, 103, 1, '2024-05-11 12:48:14', 1, '2024-05-11 12:48:14', NULL); -- ---------------------------- -- Table structure for gen_table_column -- ---------------------------- DROP TABLE IF EXISTS `gen_table_column`; CREATE TABLE `gen_table_column` ( - `column_id` bigint(20) NOT NULL COMMENT '编号', - `table_id` bigint(20) NULL DEFAULT NULL COMMENT '归属表编号', - `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', - `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', - `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', - `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', - `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', - `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', - `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', - `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', - `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', - `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', - `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', - `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', - `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', - `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', - `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `sort` int(11) NULL DEFAULT NULL COMMENT '排序', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`column_id`) USING BTREE + `column_id` bigint(20) NOT NULL COMMENT '编号', + `table_id` bigint(20) NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int(11) NULL DEFAULT NULL COMMENT '排序', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -235,33 +347,6 @@ INSERT INTO `gen_table_column` VALUES (1661288223951081474, 1661288223925915650, INSERT INTO `gen_table_column` VALUES (1661288223951081475, 1661288223925915650, 'post_id', '岗位ID', 'bigint(20)', 'Long', 'postId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); INSERT INTO `gen_table_column` VALUES (1661288223993024514, 1661288223967858689, 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); INSERT INTO `gen_table_column` VALUES (1661288223993024515, 1661288223967858689, 'role_id', '角色ID', 'bigint(20)', 'Long', 'roleId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356162, 1661288224013996034, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356163, 1661288224013996034, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356164, 1661288224013996034, 'dept_id', '部门id', 'bigint(20)', 'Long', 'deptId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356165, 1661288224013996034, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356166, 1661288224013996034, 'order_num', '排序号', 'int(11)', 'Long', 'orderNum', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356167, 1661288224013996034, 'test_key', 'key键', 'varchar(255)', 'String', 'testKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356168, 1661288224013996034, 'value', '值', 'varchar(255)', 'String', 'value', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356169, 1661288224013996034, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356170, 1661288224013996034, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 9, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356171, 1661288224013996034, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 10, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356172, 1661288224013996034, 'create_by', '创建人', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 11, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224043356173, 1661288224013996034, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224118853634, 1661288224013996034, 'update_by', '更新人', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224123047938, 1661288224013996034, 'del_flag', '删除标志', 'int(11)', 'Long', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 14, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224206934017, 1661288224185962497, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224206934018, 1661288224185962497, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224206934019, 1661288224185962497, 'parent_id', '父id', 'bigint(20)', 'Long', 'parentId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224206934020, 1661288224185962497, 'dept_id', '部门id', 'bigint(20)', 'Long', 'deptId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224206934021, 1661288224185962497, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711233, 1661288224185962497, 'tree_name', '值', 'varchar(255)', 'String', 'treeName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711234, 1661288224185962497, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711235, 1661288224185962497, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711236, 1661288224185962497, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711237, 1661288224185962497, 'create_by', '创建人', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711238, 1661288224185962497, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711239, 1661288224185962497, 'update_by', '更新人', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); -INSERT INTO `gen_table_column` VALUES (1661288224223711240, 1661288224185962497, 'del_flag', '删除标志', 'int(11)', 'Long', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); INSERT INTO `gen_table_column` VALUES (1661288385121406978, 1661288385096241154, 'config_id', '参数主键', 'bigint(20)', 'Long', 'configId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); INSERT INTO `gen_table_column` VALUES (1661288385121406979, 1661288385096241154, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); INSERT INTO `gen_table_column` VALUES (1661288385121406980, 1661288385096241154, 'config_name', '参数名称', 'varchar(100)', 'String', 'configName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); @@ -353,33 +438,6 @@ INSERT INTO `gen_table_column` VALUES (1680481753240215568, 1680481752850145282, INSERT INTO `gen_table_column` VALUES (1680481753240215569, 1680481752850145282, 'del_flag', '删除标志(0代表存在 1代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 17, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); INSERT INTO `gen_table_column` VALUES (1680481753240215570, 1680481752850145282, 'update_ip', '更新IP', 'varchar(128)', 'String', 'updateIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 18, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); INSERT INTO `gen_table_column` VALUES (1680481753240215571, 1680481752850145282, 'tenant_id', '租户Id', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'input', '', 19, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); -INSERT INTO `gen_table_column` VALUES (1728684655246950402, 1728684654923988993, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950403, 1728684654923988993, 'message_id', '消息 id', 'varchar(64)', 'String', 'messageId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950404, 1728684654923988993, 'parent_message_id', '父级消息 id', 'varchar(64)', 'String', 'parentMessageId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950405, 1728684654923988993, 'parent_answer_message_id', '父级回答消息 id', 'varchar(64)', 'String', 'parentAnswerMessageId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950406, 1728684654923988993, 'parent_question_message_id', '父级问题消息 id', 'varchar(64)', 'String', 'parentQuestionMessageId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950407, 1728684654923988993, 'context_count', '上下文数量', 'bigint(20)', 'Long', 'contextCount', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950408, 1728684654923988993, 'question_context_count', '问题上下文数量', 'bigint(20)', 'Long', 'questionContextCount', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950409, 1728684654923988993, 'message_type', '消息类型枚举', 'int(11)', 'Long', 'messageType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 8, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950410, 1728684654923988993, 'chat_room_id', '聊天室 id', 'bigint(20)', 'Long', 'chatRoomId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950411, 1728684654923988993, 'conversation_id', '对话 id', 'varchar(255)', 'String', 'conversationId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 10, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950412, 1728684654923988993, 'api_type', 'API 类型', 'varchar(20)', 'String', 'apiType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 11, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950413, 1728684654923988993, 'api_key', 'ApiKey', 'varchar(255)', 'String', 'apiKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950414, 1728684654923988993, 'content', '消息内容', 'varchar(5000)', 'String', 'content', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'editor', '', 13, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950415, 1728684654923988993, 'original_data', '消息的原始请求或响应数据', 'text', 'String', 'originalData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 14, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950416, 1728684654923988993, 'response_error_data', '错误的响应数据', 'text', 'String', 'responseErrorData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 15, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950417, 1728684654923988993, 'prompt_tokens', '输入消息的 tokens', 'bigint(20)', 'Long', 'promptTokens', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 16, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950418, 1728684654923988993, 'completion_tokens', '输出消息的 tokens', 'bigint(20)', 'Long', 'completionTokens', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 17, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950419, 1728684654923988993, 'total_tokens', '累计 Tokens', 'bigint(20)', 'Long', 'totalTokens', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 18, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950420, 1728684654923988993, 'model_name', '模型名称', 'varchar(255)', 'String', 'modelName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 19, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950421, 1728684654923988993, 'status', '聊天记录状态', 'int(11)', 'Long', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 20, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950422, 1728684654923988993, 'is_hide', '是否隐藏 0 否 1 是', 'tinyint(4)', 'Integer', 'isHide', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 21, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950423, 1728684654923988993, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 22, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950424, 1728684654923988993, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 23, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950425, 1728684654923988993, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 24, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950426, 1728684654923988993, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 25, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950427, 1728684654923988993, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 26, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); -INSERT INTO `gen_table_column` VALUES (1728684655246950428, 1728684654923988993, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 27, 103, 1, '2023-11-26 15:58:34', 1, '2023-11-26 15:58:34'); INSERT INTO `gen_table_column` VALUES (1740573615225053185, 1740573614897897473, 'id', '主键', 'int(11)', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); INSERT INTO `gen_table_column` VALUES (1740573615225053186, 1740573614897897473, 'order_no', '订单编号', 'varchar(20)', 'String', 'orderNo', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); INSERT INTO `gen_table_column` VALUES (1740573615225053187, 1740573614897897473, 'order_name', '订单名称', 'varchar(100)', 'String', 'orderName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); @@ -392,25 +450,191 @@ INSERT INTO `gen_table_column` VALUES (1740573615225053193, 1740573614897897473, INSERT INTO `gen_table_column` VALUES (1740573615225053194, 1740573614897897473, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); INSERT INTO `gen_table_column` VALUES (1740573615225053195, 1740573614897897473, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); INSERT INTO `gen_table_column` VALUES (1740573615225053196, 1740573614897897473, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1775895242624061441, 1775895242171076610, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061442, 1775895242171076610, 'model_name', '模型名称', 'varchar(50)', 'String', 'modelName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061443, 1775895242171076610, 'model_no', '模型编号', 'varchar(255)', 'String', 'modelNo', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061444, 1775895242171076610, 'model_describe', '模型描述', 'varchar(255)', 'String', 'modelDescribe', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061445, 1775895242171076610, 'model_price', '模型价格', 'double', 'Long', 'modelPrice', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061446, 1775895242171076610, 'model_type', '计费类型', 'char(1)', 'String', 'modelType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 6, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061447, 1775895242171076610, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061448, 1775895242171076610, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061449, 1775895242171076610, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061450, 1775895242171076610, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061451, 1775895242171076610, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061452, 1775895242171076610, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1785390412381896706, 1785390411861803009, 'id', '主键', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896707, 1785390411861803009, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896708, 1785390411861803009, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896709, 1785390411861803009, 'remark', '备注(微信号)', 'varchar(64)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 4, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811265, 1785390411861803009, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 5, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811266, 1785390411861803009, 'update_time', '修改时间', 'datetime', 'Date', 'updateTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 6, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811267, 1785390411861803009, 'to_friend', '指定好友回复开关', 'bit(1)', 'Integer', 'toFriend', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811268, 1785390411861803009, 'to_group', '指定群回复开关', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811269, 1785390411861803009, 'default_friend', '默认好友回复开关', 'bit(1)', 'Integer', 'defaultFriend', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811270, 1785390411861803009, 'default_group', '默认群回复开关', 'bit(1)', 'Integer', 'defaultGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 10, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811271, 1785390411861803009, 'from_out', '对外接口开关', 'bit(1)', 'Integer', 'fromOut', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 11, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811272, 1785390411861803009, 'enable', '机器启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390414135115778, 1785390413745045505, 'id', '', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115779, 1785390413745045505, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115780, 1785390413745045505, 'key_data', '关键词', 'varchar(64)', 'String', 'keyData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115781, 1785390413745045505, 'value_data', '回复内容', 'varchar(1024)', 'String', 'valueData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 4, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115782, 1785390413745045505, 'type_data', '回复类型', 'varchar(64)', 'String', 'typeData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115783, 1785390413745045505, 'nick_name', '目标昵称', 'varchar(64)', 'String', 'nickName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115784, 1785390413745045505, 'to_group', '群1好友0', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115785, 1785390413745045505, 'enable', '启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115786, 1785390413745045505, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390415250800642, 1785390414860730369, 'id', '', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800643, 1785390414860730369, 'out_key', '外接唯一码', 'varchar(16)', 'String', 'outKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800644, 1785390414860730369, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800645, 1785390414860730369, 'nick_name', '目标昵称', 'varchar(64)', 'String', 'nickName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800646, 1785390414860730369, 'to_group', '群1好友0', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800647, 1785390414860730369, 'enable', '启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800648, 1785390414860730369, 'white_list', 'IP白名单', 'varchar(255)', 'String', 'whiteList', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800649, 1785390414860730369, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 8, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1786379560827805698, 1786379560181882881, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805699, 1786379560181882881, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805700, 1786379560181882881, 'code', '兑换码', 'varchar(255)', 'String', 'code', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805701, 1786379560181882881, 'amount', '兑换金额', 'double(10,2)', 'BigDecimal', 'amount', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805702, 1786379560181882881, 'status', '兑换状态', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 5, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805703, 1786379560181882881, 'balance_before', '兑换前余额', 'double(10,2)', 'BigDecimal', 'balanceBefore', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805704, 1786379560181882881, 'balance_after', '兑换后余额', 'double(10,2)', 'BigDecimal', 'balanceAfter', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805705, 1786379560181882881, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805706, 1786379560181882881, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805707, 1786379560181882881, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805708, 1786379560181882881, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 11, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805709, 1786379560181882881, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560890720257, 1786379560181882881, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1789155611425452034, 1789155611035381761, 'id', 'ID', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452035, 1789155611035381761, 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452036, 1789155611035381761, 'notice_id', '公告ID', 'bigint(20)', 'Long', 'noticeId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452037, 1789155611035381761, 'read_status', '阅读状态(0未读 1已读)', 'char(1)', 'String', 'readStatus', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 4, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452038, 1789155611035381761, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 5, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452039, 1789155611035381761, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 6, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452040, 1789155611035381761, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 7, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452041, 1789155611035381761, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452042, 1789155611035381761, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452043, 1789155611035381761, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 10, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); + +-- ---------------------------- +-- Table structure for knowledge_attach +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_attach`; +CREATE TABLE `knowledge_attach` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `doc_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档ID', + `doc_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档名称', + `doc_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档类型', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文档内容', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_kname`(`kid`, `doc_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识库附件' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_attach +-- ---------------------------- + +-- ---------------------------- +-- Table structure for knowledge_fragment +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_fragment`; +CREATE TABLE `knowledge_fragment` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `doc_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文档ID', + `fid` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识片段ID', + `idx` int(11) NOT NULL COMMENT '片段索引下标', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档内容', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5133 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识片段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_fragment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for knowledge_info +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_info`; +CREATE TABLE `knowledge_info` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `uid` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户ID', + `kname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库名称', + `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_kid`(`kid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识库' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for knowledge_share +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_share`; +CREATE TABLE `knowledge_share` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `uid` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `kname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '知识库名称', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识库分享表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_share +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_audio_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_audio_role`; +CREATE TABLE `sys_audio_role` ( + `id` bigint(20) NOT NULL COMMENT 'id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '角色描述', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `voice_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色id', + `file_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '音频地址', + `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `voice_id`(`create_by`, `voice_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配音角色' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_audio_role +-- ---------------------------- -- ---------------------------- -- Table structure for sys_config -- ---------------------------- DROP TABLE IF EXISTS `sys_config`; CREATE TABLE `sys_config` ( - `config_id` bigint(20) NOT NULL COMMENT '参数主键', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', - `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', - `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', - `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`config_id`) USING BTREE + `config_id` bigint(20) NOT NULL COMMENT '参数主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -421,34 +645,29 @@ INSERT INTO `sys_config` VALUES (2, '000000', '用户管理-账号初始密码', INSERT INTO `sys_config` VALUES (3, '000000', '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '深色主题theme-dark,浅色主题theme-light'); INSERT INTO `sys_config` VALUES (5, '000000', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '是否开启注册用户功能(true开启,false关闭)'); INSERT INTO `sys_config` VALUES (11, '000000', 'OSS预览列表资源开关', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, 'true:开启, false:关闭'); -INSERT INTO `sys_config` VALUES (1729685495520854018, '911866', '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); -INSERT INTO `sys_config` VALUES (1729685495520854019, '911866', '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', '初始化密码 123456'); -INSERT INTO `sys_config` VALUES (1729685495520854020, '911866', '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', '深色主题theme-dark,浅色主题theme-light'); -INSERT INTO `sys_config` VALUES (1729685495583768578, '911866', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', '是否开启注册用户功能(true开启,false关闭)'); -INSERT INTO `sys_config` VALUES (1729685495583768579, '911866', 'OSS预览列表资源开关', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', 'true:开启, false:关闭'); -- ---------------------------- -- Table structure for sys_dept -- ---------------------------- DROP TABLE IF EXISTS `sys_dept`; CREATE TABLE `sys_dept` ( - `dept_id` bigint(20) NOT NULL COMMENT '部门id', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', - `ancestors` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', - `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', - `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', - `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', - `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', - `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`dept_id`) USING BTREE + `dept_id` bigint(20) NOT NULL COMMENT '部门id', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -464,29 +683,30 @@ INSERT INTO `sys_dept` VALUES (106, '000000', 101, '0,100,101', '财务部门', INSERT INTO `sys_dept` VALUES (107, '000000', 101, '0,100,101', '运维部门', 5, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); INSERT INTO `sys_dept` VALUES (108, '000000', 102, '0,100,102', '市场部门', 1, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); INSERT INTO `sys_dept` VALUES (109, '000000', 102, '0,100,102', '财务部门', 2, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (1729685491964084226, '911866', 0, '0', '5126', 0, 'admin', NULL, NULL, '0', '2', 103, 1, '2023-11-29 10:15:32', 1, '2023-11-29 10:15:32'); -- ---------------------------- -- Table structure for sys_dict_data -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_data`; CREATE TABLE `sys_dict_data` ( - `dict_code` bigint(20) NOT NULL COMMENT '字典编码', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `dict_sort` int(4) NULL DEFAULT 0 COMMENT '字典排序', - `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', - `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', - `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', - `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', - `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`dict_code`) USING BTREE + `dict_code` bigint(20) NOT NULL COMMENT '字典编码', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dict_sort` int(4) NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -529,19 +749,19 @@ INSERT INTO `sys_dict_data` VALUES (1780264431589601282, '000000', 2, '已支付 -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_type`; CREATE TABLE `sys_dict_type` ( - `dict_id` bigint(20) NOT NULL COMMENT '字典主键', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', - `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`dict_id`) USING BTREE, - UNIQUE INDEX `tenant_id`(`tenant_id`, `dict_type`) USING BTREE + `dict_id` bigint(20) NOT NULL COMMENT '字典主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `tenant_id`(`tenant_id`, `dict_type`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -563,7 +783,7 @@ INSERT INTO `sys_dict_type` VALUES (1729685494468083718, '911866', '通知类型 INSERT INTO `sys_dict_type` VALUES (1729685494468083719, '911866', '通知状态', 'sys_notice_status', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '通知状态列表'); INSERT INTO `sys_dict_type` VALUES (1729685494468083720, '911866', '操作类型', 'sys_oper_type', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '操作类型列表'); INSERT INTO `sys_dict_type` VALUES (1729685494468083721, '911866', '系统状态', 'sys_common_status', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '登录状态列表'); -INSERT INTO `sys_dict_type` VALUES (1775756736895438849, '000000', '用户等级', 'sys_user_grade', '0', 103, 1, '2024-04-04 13:26:13', 1, '2024-04-23 19:25:25', '用户等级'); +INSERT INTO `sys_dict_type` VALUES (1775756736895438849, '000000', '用户等级', 'sys_user_grade', '0', 103, 1, '2024-04-04 13:26:13', 1, '2024-04-04 13:26:13', ''); INSERT INTO `sys_dict_type` VALUES (1776109665045278721, '000000', '模型计费方式', 'sys_model_billing', '0', 103, 1, '2024-04-05 12:48:37', 1, '2024-04-08 11:22:18', '模型计费方式'); INSERT INTO `sys_dict_type` VALUES (1780263881368219649, '000000', '支付状态', 'pay_state', '0', 103, 1, '2024-04-16 23:56:00', 1, '2024-04-16 23:56:00', '支付状态'); @@ -572,29 +792,29 @@ INSERT INTO `sys_dict_type` VALUES (1780263881368219649, '000000', '支付状态 -- ---------------------------- DROP TABLE IF EXISTS `sys_file_info`; CREATE TABLE `sys_file_info` ( - `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文件id', - `url` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件访问地址', - `size` bigint(20) NULL DEFAULT NULL COMMENT '文件大小,单位字节', - `filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称', - `original_filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '原始文件名', - `base_path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '基础存储路径', - `path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '存储路径', - `ext` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件扩展名', - `user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件所属用户', - `file_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', - `attr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '附加属性', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - `version` int(11) NULL DEFAULT NULL COMMENT '版本', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', - `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新IP', - `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户Id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件记录表' ROW_FORMAT = Dynamic; + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文件id', + `url` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件访问地址', + `size` bigint(20) NULL DEFAULT NULL COMMENT '文件大小,单位字节', + `filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称', + `original_filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '原始文件名', + `base_path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '基础存储路径', + `path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '存储路径', + `ext` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件扩展名', + `user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件所属用户', + `file_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', + `attr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '附加属性', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `version` int(11) NULL DEFAULT NULL COMMENT '版本', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新IP', + `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1688133688718106627 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件记录表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_file_info @@ -605,54 +825,51 @@ CREATE TABLE `sys_file_info` ( -- ---------------------------- DROP TABLE IF EXISTS `sys_logininfor`; CREATE TABLE `sys_logininfor` ( - `info_id` bigint(20) NOT NULL COMMENT '访问ID', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', - `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', - `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', - `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', - `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', - `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', - `login_time` datetime NULL DEFAULT NULL COMMENT '访问时间', - PRIMARY KEY (`info_id`) USING BTREE, - INDEX `idx_sys_logininfor_s`(`status`) USING BTREE, - INDEX `idx_sys_logininfor_lt`(`login_time`) USING BTREE + `info_id` bigint(20) NOT NULL COMMENT '访问ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE, + INDEX `idx_sys_logininfor_s`(`status`) USING BTREE, + INDEX `idx_sys_logininfor_lt`(`login_time`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_logininfor -- ---------------------------- -INSERT INTO `sys_logininfor` VALUES (1789929320503357442, '00000', 'pandarobot@163.com', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-05-13 16:03:00'); -INSERT INTO `sys_logininfor` VALUES (1789958553149739010, '00000', 'admin', '0:0:0:0:0:0:0:1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-05-13 17:59:10'); -INSERT INTO `sys_logininfor` VALUES (1789985977795125250, '00000', 'admin', '0:0:0:0:0:0:0:1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-05-13 19:48:08'); -- ---------------------------- -- Table structure for sys_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( - `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', - `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', - `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', - `order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序', - `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', - `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', - `query_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', - `is_frame` int(11) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', - `is_cache` int(11) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', - `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', - `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '显示状态(0显示 1隐藏)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', - `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', - `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', - PRIMARY KEY (`menu_id`) USING BTREE + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `is_frame` int(11) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int(11) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '显示状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -662,13 +879,13 @@ INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, '', 1, 0 INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 3, 'monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'monitor', 103, 1, '2023-05-14 15:19:39', NULL, NULL, '系统监控目录'); INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 2, 'tenant', NULL, '', 1, 0, 'M', '1', '1', '', 'chart', 103, 1, '2023-05-14 15:19:39', 1, '2024-04-04 22:35:33', '租户管理目录'); INSERT INTO `sys_menu` VALUES (100, '用户管理', 1775500307898949634, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 103, 1, '2023-05-14 15:19:39', 1, '2024-04-03 20:27:27', '用户管理菜单'); -INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 103, 1, '2023-05-14 15:19:39', 1, '2024-05-12 01:11:47', '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 103, 1, '2023-05-14 15:19:39', NULL, NULL, '角色管理菜单'); INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 103, 1, '2023-05-14 15:19:39', NULL, NULL, '菜单管理菜单'); -INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 103, 1, '2023-05-14 15:19:39', 1, '2024-05-12 01:10:23', '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '1', '1', 'system:dept:list', 'tree', 103, 1, '2023-05-14 15:19:39', 1, '2024-04-04 22:36:07', '部门管理菜单'); INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '1', '1', 'system:post:list', 'post', 103, 1, '2023-05-14 15:19:39', 1, '2024-04-04 22:36:15', '岗位管理菜单'); INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 103, 1, '2023-05-14 15:19:40', NULL, NULL, '字典管理菜单'); -INSERT INTO `sys_menu` VALUES (106, '系统参数', 1, 10, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 103, 1, '2023-05-14 15:19:40', 1, '2024-05-13 18:07:44', '参数设置菜单'); -INSERT INTO `sys_menu` VALUES (107, '通知公告', 1775500307898949634, 14, 'notice', 'system/notice/index', '', 1, 0, 'C', '1', '0', 'system:notice:list', 'message', 103, 1, '2023-05-14 15:19:40', 1, '2024-05-13 18:07:50', '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (106, '系统参数', 1, 10, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 103, 1, '2023-05-14 15:19:40', 1, '2024-05-19 23:13:49', '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1775500307898949634, 14, 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 103, 1, '2023-05-14 15:19:40', 1, '2024-05-02 22:35:07', '通知公告菜单'); INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 103, 1, '2023-05-14 15:19:40', NULL, NULL, '日志管理菜单'); INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 103, 1, '2023-05-14 15:19:40', NULL, NULL, '在线用户菜单'); INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 103, 1, '2023-05-14 15:19:40', NULL, NULL, '缓存监控菜单'); @@ -755,41 +972,78 @@ INSERT INTO `sys_menu` VALUES (1775895273104068612, '系统模型新增', 177589 INSERT INTO `sys_menu` VALUES (1775895273104068613, '系统模型修改', 1775895273104068610, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:edit', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (1775895273104068614, '系统模型删除', 1775895273104068610, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:remove', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); INSERT INTO `sys_menu` VALUES (1775895273104068615, '系统模型导出', 1775895273104068610, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:export', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); -INSERT INTO `sys_menu` VALUES (1789974539726790658, '机器管理', 1775500307898949634, 9, 'bot', 'system/bot/index', NULL, 1, 0, 'C', '0', '0', NULL, 'people', 103, 1, '2024-05-13 19:02:41', 1, '2024-05-13 19:06:53', ''); +INSERT INTO `sys_menu` VALUES (1779024740525608962, '配置管理', 1775500307898949634, 4, 'chat', 'system/chatconfig/index', NULL, 1, 0, 'C', '0', '0', '', 'dict', 103, 1, '2024-04-13 13:56:30', 1, '2024-04-13 14:47:51', '对话配置信息\r\n菜单'); +INSERT INTO `sys_menu` VALUES (1779024740525608963, '对话配置信息\r\n查询', 1779024740525608962, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:query', '#', 103, 1, '2024-04-13 13:56:30', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1779024740525608964, '对话配置信息\r\n新增', 1779024740525608962, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:add', '#', 103, 1, '2024-04-13 13:56:30', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1779024740525608965, '对话配置信息\r\n修改', 1779024740525608962, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:edit', '#', 103, 1, '2024-04-13 13:56:30', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1779024740525608966, '对话配置信息\r\n删除', 1779024740525608962, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:remove', '#', 103, 1, '2024-04-13 13:56:30', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1779024740525608967, '对话配置信息\r\n导出', 1779024740525608962, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:export', '#', 103, 1, '2024-04-13 13:56:30', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507266, '聊天消息', 1775500307898949634, 0, 'chatMessage', 'system/message/index', NULL, 1, 0, 'C', '0', '0', 'system:message:list', 'log', 103, 1, '2024-04-16 22:24:48', 1, '2024-04-16 23:10:47', '聊天消息菜单'); +INSERT INTO `sys_menu` VALUES (1780240077690507267, '聊天消息查询', 1780240077690507266, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:query', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507268, '聊天消息新增', 1780240077690507266, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:add', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507269, '聊天消息修改', 1780240077690507266, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:edit', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507270, '聊天消息删除', 1780240077690507266, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:remove', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507271, '聊天消息导出', 1780240077690507266, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:export', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018433, '支付订单', 1775500307898949634, 9, 'orders', 'system/orders/index', NULL, 1, 0, 'C', '0', '0', 'system:orders:list', 'documentation', 103, 1, '2024-04-16 23:32:48', 1, '2024-04-16 23:51:51', '支付订单菜单'); +INSERT INTO `sys_menu` VALUES (1780255628576018434, '支付订单查询', 1780255628576018433, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:query', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018435, '支付订单新增', 1780255628576018433, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:add', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018436, '支付订单修改', 1780255628576018433, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:edit', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018437, '支付订单删除', 1780255628576018433, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:remove', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018438, '支付订单导出', 1780255628576018433, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:export', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156481, '兑换管理', 1775500307898949634, 12, 'voucher', 'system/voucher/index', NULL, 1, 0, 'C', '0', '0', 'system:voucher:list', 'druid', 103, 1, '2024-05-03 20:59:54', 1, '2024-05-03 21:11:41', '用户兑换记录菜单'); +INSERT INTO `sys_menu` VALUES (1786379590171156482, '用户兑换记录查询', 1786379590171156481, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:query', '#', 103, 1, '2024-05-03 20:59:54', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156483, '用户兑换记录新增', 1786379590171156481, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:add', '#', 103, 1, '2024-05-03 20:59:54', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156484, '用户兑换记录修改', 1786379590171156481, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:edit', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156485, '用户兑换记录删除', 1786379590171156481, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:remove', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156486, '用户兑换记录导出', 1786379590171156481, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:export', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786380456739528706, '机器管理', 1775500307898949634, 13, 'robConfig', 'system/robConfig/index', NULL, 1, 0, 'C', '0', '0', 'system:robConfig:list', 'people', 103, 1, '2024-05-03 21:02:57', 1, '2024-05-03 21:13:16', 'robot菜单'); +INSERT INTO `sys_menu` VALUES (1786380456739528707, 'robot查询', 1786380456739528706, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:robConfig:query', '#', 103, 1, '2024-05-03 21:02:57', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786380456739528708, 'robot新增', 1786380456739528706, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:robConfig:add', '#', 103, 1, '2024-05-03 21:02:57', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786380456739528709, 'robot修改', 1786380456739528706, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:robConfig:edit', '#', 103, 1, '2024-05-03 21:02:58', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786380456739528710, 'robot删除', 1786380456739528706, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:robConfig:remove', '#', 103, 1, '2024-05-03 21:02:58', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786380456739528711, 'robot导出', 1786380456739528706, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:robConfig:export', '#', 103, 1, '2024-05-03 21:02:58', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122561, '套餐管理', 1775500307898949634, 1, 'packagePlan', 'system/packagePlan/index', NULL, 1, 0, 'C', '0', '0', 'system:packagePlan:list', 'tool', 103, 1, '2024-05-05 19:13:53', 1, '2024-05-05 19:38:56', '套餐管理菜单'); +INSERT INTO `sys_menu` VALUES (1787078000285122562, '套餐管理查询', 1787078000285122561, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:packagePlan:query', '#', 103, 1, '2024-05-05 19:13:53', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122563, '套餐管理新增', 1787078000285122561, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:packagePlan:add', '#', 103, 1, '2024-05-05 19:13:53', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122564, '套餐管理修改', 1787078000285122561, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:packagePlan:edit', '#', 103, 1, '2024-05-05 19:13:53', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122565, '套餐管理删除', 1787078000285122561, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:packagePlan:remove', '#', 103, 1, '2024-05-05 19:13:53', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122566, '套餐管理导出', 1787078000285122561, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:packagePlan:export', '#', 103, 1, '2024-05-05 19:13:53', NULL, NULL, ''); -- ---------------------------- -- Table structure for sys_model -- ---------------------------- DROP TABLE IF EXISTS `sys_model`; CREATE TABLE `sys_model` ( - `id` bigint(20) NOT NULL COMMENT '主键', - `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', - `model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型描述', - `model_price` double NULL DEFAULT NULL COMMENT '模型价格', - `model_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计费类型', - `model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否显示', - `system_prompt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统提示词', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`) USING BTREE + `id` bigint(20) NOT NULL COMMENT '主键', + `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + `model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型描述', + `model_price` double NULL DEFAULT NULL COMMENT '模型价格', + `model_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计费类型', + `model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否显示', + `system_prompt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统提示词', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统模型' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_model -- ---------------------------- -INSERT INTO `sys_model` VALUES (1776114028757180417, 'gpt-3.5-turbo', 'gpt-3.5-turbo', 0.05, '1', '0', NULL, 103, 1, '2024-04-20 23:40:31', 1, '2024-04-23 21:45:19', 'GPT3.5最新模型,用于文本生成、对话系统、内容摘要等'); -INSERT INTO `sys_model` VALUES (1781709495515783171, 'gpt-4-all', 'gpt-4-all', 0.2, '2', '0', NULL, 103, 1, '2024-04-20 23:40:41', 1, '2024-04-23 21:45:23', '同时拥有联网查询,高级数据分析,画图 DALL.E功能,GPT 会自动识别并调取相关能力工具'); -INSERT INTO `sys_model` VALUES (1781709617662304258, 'gpt-4-turbo-2024-04-09', 'gpt-4-turbo-2024-04-09', 0.2, '1', '0', '使用中文回复', 103, 1, '2024-04-20 23:40:50', 1, '2024-04-23 21:45:28', '最新版GPT-4,相对GPT-3.5更先进、拥有更多的参数和更强大的语言处理能力'); +INSERT INTO `sys_model` VALUES (1776114028757180417, 'gpt-3.5-turbo', 'gpt-3', 0.05, '1', '0', NULL, 103, 1, '2024-04-20 23:40:31', 1, '2024-04-23 21:45:19', 'GPT3.5最新模型,用于文本生成、对话系统、内容摘要等'); +INSERT INTO `sys_model` VALUES (1781709495515783171, 'gpt-4-all', 'gpt-all', 0.2, '2', '0', NULL, 103, 1, '2024-04-20 23:40:41', 1, '2024-04-23 21:45:23', '同时拥有联网查询,高级数据分析,画图 DALL.E功能,GPT 会自动识别并调取相关能力工具'); +INSERT INTO `sys_model` VALUES (1781709617662304258, 'gpt-4-turbo-2024-04-09', 'gpt-4', 0.2, '1', '0', '使用中文回复', 103, 1, '2024-04-20 23:40:50', 1, '2024-04-23 21:45:28', '最新版GPT-4,相对GPT-3.5更先进、拥有更多的参数和更强大的语言处理能力'); INSERT INTO `sys_model` VALUES (1781715781896646657, 'suno-v3', 'suno-v3', 0.5, '2', '0', NULL, 103, 1, '2024-04-21 00:05:20', 1, '2024-04-23 19:34:36', 'SunoAI 推出的文生歌模型,支持灵感模式、歌词模式,也支持生成纯音乐'); INSERT INTO `sys_model` VALUES (1781728235120791553, 'stable-diffusion', 'stable-diffusion', 0.1, '2', '0', NULL, 103, 1, '2024-04-21 00:54:49', 1, '2024-04-23 19:34:40', '高级图像生成和处理模型,擅长创建逼真的视觉效果'); INSERT INTO `sys_model` VALUES (1781728766161620993, 'claude-3-opus-20240229', 'claude-3-opus-20240229', 0.2, '1', '0', NULL, 103, 1, '2024-04-21 00:56:55', 1, '2024-04-23 19:34:43', 'Claude模型的最新版本,具有最先进的语言处理技术'); INSERT INTO `sys_model` VALUES (1782734319650418690, 'gpt-4-1106-vision-preview', 'gpt-4-1106-vision-preview', 0.2, '2', '1', '', 103, 1, '2024-04-23 19:32:38', 1, '2024-04-23 19:38:38', 'GPT-4 的一个包含视觉处理能力的预览版本,结合了视觉信息处理的能力'); -INSERT INTO `sys_model` VALUES (1782736322308943873, 'dall-e-3', 'dall-e-3', 0.3, '2', '1', '', 103, 1, '2024-04-23 19:40:36', 1, '2024-04-23 21:45:40', 'DALL·E 是一个专注于图像生成的模型'); +INSERT INTO `sys_model` VALUES (1782736322308943873, 'dall-e-3', 'dall3', 0.3, '2', '1', '', 103, 1, '2024-04-23 19:40:36', 1, '2024-04-23 21:45:40', 'DALL·E 是一个专注于图像生成的模型'); INSERT INTO `sys_model` VALUES (1782736729471004673, 'gpt-4-gizmo', 'gpt-4-gizmo', 0.2, '2', '1', NULL, 103, 1, '2024-04-23 19:42:13', 1, '2024-04-23 19:42:13', 'gpts商店中的模型,使用方式:gpt-4-gizmo-g-xxx'); +INSERT INTO `sys_model` VALUES (1782792839548735490, 'mj-type1', 'Midjourney1', 0.1, '2', '1', NULL, 103, 1, '2024-04-23 23:25:10', 1, '2024-04-23 23:28:04', '图生文、放大、提示词分析费用'); +INSERT INTO `sys_model` VALUES (1782793100841291778, 'mj-type2', 'Midjourney2', 0.3, '2', '1', NULL, 103, 1, '2024-04-23 23:26:13', 1, '2024-04-23 23:28:13', '文生图,图生图,变化、换脸费用'); INSERT INTO `sys_model` VALUES (1790237707719991298, 'gpt-4o-2024-05-13', 'gpt-4o-2024-05-13', 0.2, '1', '0', NULL, 103, 1, '2024-05-14 12:28:25', 1, '2024-05-14 12:28:25', ' OpenAI 最先进的多模式模型,比 GPT-4 Turbo 更快、更便宜,具有更强的视觉功能'); -- ---------------------------- @@ -797,92 +1051,99 @@ INSERT INTO `sys_model` VALUES (1790237707719991298, 'gpt-4o-2024-05-13', 'gpt-4 -- ---------------------------- DROP TABLE IF EXISTS `sys_notice`; CREATE TABLE `sys_notice` ( - `notice_id` bigint(20) NOT NULL COMMENT '公告ID', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', - `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', - `notice_content` longblob NULL COMMENT '公告内容', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`notice_id`) USING BTREE + `notice_id` bigint(20) NOT NULL COMMENT '公告ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_notice -- ---------------------------- -INSERT INTO `sys_notice` VALUES (1, '000000', '温馨提醒:2018-07-01 新版本发布啦', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '管理员'); -INSERT INTO `sys_notice` VALUES (2, '000000', '维护通知:2018-07-01 系统凌晨维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '管理员'); +INSERT INTO `sys_notice` VALUES (1789324923280932865, '000000', '公告', '1', 0x3C68313E3C7370616E207374796C653D226261636B67726F756E642D636F6C6F723A20726762283235352C203235352C2030293B223EE696B0E78988E69CACE58685E5AEB93C2F7370616E3E3C2F68313E3C703EE79BAEE5898DE694AFE68C81E69C80E5A49A3C7370616E207374796C653D226261636B67726F756E642D636F6C6F723A20726762283233302C20302C2030293B223E3530E4BABA3C2F7370616E3EE799BBE5BD95EFBC8CE4B880E4B8AAE794A8E688B7E58FAAE883BDE7BB91E5AE9AE4B880E4B8AAE5BEAEE4BFA1EFBC8CE4B894E4B88DE694AFE68C81E58887E68DA2E6A8A1E59E8BE380823C2F703E3C703EE5BC80E58F91E8AEA1E58892EFBC9A3C2F703E3C703E312E20E58FAFE4BBA5E98089E68BA9E79FA5E8AF86E5BA933C2F703E3C703E322E20E58FAFE4BBA5E58887E68DA2E6A8A1E59E8B3C2F703E3C703E332E3C733E20E5908CE4B880E4B8AAE794A8E688B7E58FAFE4BBA5E799BBE5BD95E5A49AE4B8AAE5BEAEE4BFA13C2F733E3C2F703E, '0', 103, 1, '2024-05-12 00:01:20', 1, '2024-05-12 00:01:20', ''); + +-- ---------------------------- +-- Table structure for sys_notice_state +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice_state`; +CREATE TABLE `sys_notice_state` ( + `id` bigint(20) NOT NULL COMMENT 'ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `notice_id` bigint(20) NOT NULL COMMENT '公告ID', + `read_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '阅读状态(0未读 1已读)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户阅读状态表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice_state +-- ---------------------------- -- ---------------------------- -- Table structure for sys_oper_log -- ---------------------------- DROP TABLE IF EXISTS `sys_oper_log`; CREATE TABLE `sys_oper_log` ( - `oper_id` bigint(20) NOT NULL COMMENT '日志主键', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', - `business_type` int(11) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', - `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', - `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', - `operator_type` int(11) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', - `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', - `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', - `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', - `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', - `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', - `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', - `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', - `status` int(11) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', - `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', - `oper_time` datetime NULL DEFAULT NULL COMMENT '操作时间', - `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间', - PRIMARY KEY (`oper_id`) USING BTREE, - INDEX `idx_sys_oper_log_bt`(`business_type`) USING BTREE, - INDEX `idx_sys_oper_log_s`(`status`) USING BTREE, - INDEX `idx_sys_oper_log_ot`(`oper_time`) USING BTREE + `oper_id` bigint(20) NOT NULL COMMENT '日志主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int(2) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int(1) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int(1) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime NULL DEFAULT NULL COMMENT '操作时间', + `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间', + PRIMARY KEY (`oper_id`) USING BTREE, + INDEX `idx_sys_oper_log_bt`(`business_type`) USING BTREE, + INDEX `idx_sys_oper_log_s`(`status`) USING BTREE, + INDEX `idx_sys_oper_log_ot`(`oper_time`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_oper_log -- ---------------------------- -INSERT INTO `sys_oper_log` VALUES (1789958779478577153, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:39\",\"updateBy\":null,\"updateTime\":null,\"menuId\":6,\"parentId\":0,\"menuName\":\"租户管理\",\"orderNum\":2,\"path\":\"tenant\",\"component\":null,\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"M\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"\",\"icon\":\"chart\",\"remark\":\"租户管理目录\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:00:04', 164); -INSERT INTO `sys_oper_log` VALUES (1789959085327224833, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:39\",\"updateBy\":null,\"updateTime\":null,\"menuId\":6,\"parentId\":0,\"menuName\":\"租户管理\",\"orderNum\":2,\"path\":\"tenant\",\"component\":null,\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"M\",\"visible\":\"1\",\"status\":\"1\",\"perms\":\"\",\"icon\":\"chart\",\"remark\":\"租户管理目录\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:01:16', 137); -INSERT INTO `sys_oper_log` VALUES (1789959387665240065, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:39\",\"updateBy\":null,\"updateTime\":null,\"menuId\":101,\"parentId\":1,\"menuName\":\"角色管理\",\"orderNum\":2,\"path\":\"role\",\"component\":\"system/role/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:role:list\",\"icon\":\"peoples\",\"remark\":\"角色管理菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:02:29', 143); -INSERT INTO `sys_oper_log` VALUES (1789959414731083778, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:39\",\"updateBy\":null,\"updateTime\":null,\"menuId\":103,\"parentId\":1,\"menuName\":\"部门管理\",\"orderNum\":4,\"path\":\"dept\",\"component\":\"system/dept/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:dept:list\",\"icon\":\"tree\",\"remark\":\"部门管理菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:02:35', 136); -INSERT INTO `sys_oper_log` VALUES (1789959430166122498, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:39\",\"updateBy\":null,\"updateTime\":null,\"menuId\":104,\"parentId\":1,\"menuName\":\"岗位管理\",\"orderNum\":5,\"path\":\"post\",\"component\":\"system/post/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:post:list\",\"icon\":\"post\",\"remark\":\"岗位管理菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:02:39', 139); -INSERT INTO `sys_oper_log` VALUES (1789959465704460290, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:40\",\"updateBy\":null,\"updateTime\":null,\"menuId\":107,\"parentId\":1,\"menuName\":\"通知公告\",\"orderNum\":8,\"path\":\"notice\",\"component\":\"system/notice/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:notice:list\",\"icon\":\"message\",\"remark\":\"通知公告菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:02:47', 144); -INSERT INTO `sys_oper_log` VALUES (1789959516686225410, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:40\",\"updateBy\":null,\"updateTime\":null,\"menuId\":118,\"parentId\":1,\"menuName\":\"文件管理\",\"orderNum\":10,\"path\":\"oss\",\"component\":\"system/oss/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:oss:list\",\"icon\":\"upload\",\"remark\":\"文件管理菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:02:59', 134); -INSERT INTO `sys_oper_log` VALUES (1789959599150436353, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:40\",\"updateBy\":null,\"updateTime\":null,\"menuId\":106,\"parentId\":1,\"menuName\":\"参数设置\",\"orderNum\":7,\"path\":\"config\",\"component\":\"system/config/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:config:list\",\"icon\":\"edit\",\"remark\":\"参数设置菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:03:19', 146); -INSERT INTO `sys_oper_log` VALUES (1789960080274853890, '00000', '菜单管理', 3, 'com.xmzs.system.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '', '/system/menu/1780240077690507266', '0:0:0:0:0:0:0:1', '内网IP', '{}', '{\"code\":601,\"msg\":\"存在子菜单,不允许删除\",\"data\":null}', 0, '', '2024-05-13 18:05:14', 65); -INSERT INTO `sys_oper_log` VALUES (1789960711316279297, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:40\",\"updateBy\":null,\"updateTime\":null,\"menuId\":106,\"parentId\":1,\"menuName\":\"系统参数\",\"orderNum\":10,\"path\":\"config\",\"component\":\"system/config/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"0\",\"status\":\"0\",\"perms\":\"system:config:list\",\"icon\":\"edit\",\"remark\":\"参数设置菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:07:44', 123); -INSERT INTO `sys_oper_log` VALUES (1789960734875684866, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2023-05-14 15:19:40\",\"updateBy\":null,\"updateTime\":null,\"menuId\":107,\"parentId\":\"1775500307898949634\",\"menuName\":\"通知公告\",\"orderNum\":14,\"path\":\"notice\",\"component\":\"system/notice/index\",\"queryParam\":\"\",\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"1\",\"status\":\"0\",\"perms\":\"system:notice:list\",\"icon\":\"message\",\"remark\":\"通知公告菜单\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 18:07:50', 121); -INSERT INTO `sys_oper_log` VALUES (1789974540146221057, '00000', '菜单管理', 1, 'com.xmzs.system.controller.system.SysMenuController.add()', 'POST', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":null,\"createBy\":null,\"createTime\":null,\"updateBy\":null,\"updateTime\":null,\"menuId\":null,\"parentId\":\"1775500307898949634\",\"menuName\":\"机器管理\",\"orderNum\":10,\"path\":\"bot\",\"component\":null,\"queryParam\":null,\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"M\",\"visible\":\"0\",\"status\":\"0\",\"icon\":\"people\",\"remark\":null}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 19:02:41', 174); -INSERT INTO `sys_oper_log` VALUES (1789975595391164417, '00000', '菜单管理', 2, 'com.xmzs.system.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '', '/system/menu', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":103,\"createBy\":null,\"createTime\":\"2024-05-13 19:02:41\",\"updateBy\":null,\"updateTime\":null,\"menuId\":\"1789974539726790658\",\"parentId\":\"1775500307898949634\",\"menuName\":\"机器管理\",\"orderNum\":9,\"path\":\"bot\",\"component\":\"system/bot/index\",\"queryParam\":null,\"isFrame\":\"1\",\"isCache\":\"0\",\"menuType\":\"C\",\"visible\":\"0\",\"status\":\"0\",\"icon\":\"people\",\"remark\":\"\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-13 19:06:53', 140); -INSERT INTO `sys_oper_log` VALUES (1790237708319776770, '00000', '系统模型', 1, 'com.xmzs.system.controller.system.SysModelController.add()', 'POST', 1, 'admin', '', '/system/model', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":null,\"createBy\":null,\"createTime\":null,\"updateBy\":null,\"updateTime\":null,\"id\":\"1790237707719991298\",\"modelName\":\"gpt-4o-2024-05-13\",\"modelDescribe\":\"gpt-4o-2024-05-13\",\"modelPrice\":0.2,\"modelType\":\"1\",\"modelShow\":\"0\",\"systemPrompt\":null,\"remark\":\" OpenAI 最先进的多模式模型,比 GPT-4 Turbo 更快、更便宜,具有更强的视觉功能\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-05-14 12:28:25', 146); -- ---------------------------- -- Table structure for sys_oss -- ---------------------------- DROP TABLE IF EXISTS `sys_oss`; CREATE TABLE `sys_oss` ( - `oss_id` bigint(20) NOT NULL COMMENT '对象存储主键', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名', - `original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原名', - `file_suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件后缀名', - `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'URL地址', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '上传人', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人', - `service` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'minio' COMMENT '服务商', - PRIMARY KEY (`oss_id`) USING BTREE + `oss_id` bigint(20) NOT NULL COMMENT '对象存储主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名', + `original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原名', + `file_suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件后缀名', + `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'URL地址', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '上传人', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + `service` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'minio' COMMENT '服务商', + PRIMARY KEY (`oss_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'OSS对象存储表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -894,27 +1155,27 @@ CREATE TABLE `sys_oss` ( -- ---------------------------- DROP TABLE IF EXISTS `sys_oss_config`; CREATE TABLE `sys_oss_config` ( - `oss_config_id` bigint(20) NOT NULL COMMENT '主建', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `config_key` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置key', - `access_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'accessKey', - `secret_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '秘钥', - `bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '桶名称', - `prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '前缀', - `endpoint` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '访问站点', - `domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '自定义域名', - `is_https` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否https(Y=是,N=否)', - `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '域', - `access_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '桶权限类型(0=private 1=public 2=custom)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否默认(0=是,1=否)', - `ext1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '扩展字段', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`oss_config_id`) USING BTREE + `oss_config_id` bigint(20) NOT NULL COMMENT '主建', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `config_key` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置key', + `access_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'accessKey', + `secret_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '秘钥', + `bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '桶名称', + `prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '前缀', + `endpoint` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '访问站点', + `domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '自定义域名', + `is_https` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否https(Y=是,N=否)', + `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '域', + `access_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '桶权限类型(0=private 1=public 2=custom)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否默认(0=是,1=否)', + `ext1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '扩展字段', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`oss_config_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '对象存储配置表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -923,29 +1184,55 @@ CREATE TABLE `sys_oss_config` ( INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-07-13 23:28:18', NULL); INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', NULL); INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-07-13 23:35:23', NULL); -INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '0', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-11-13 23:58:09', ''); +INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'panda-1253683406', 'panda', 'cos.ap-guangzhou.myqcloud.com', '', 'N', 'ap-guangzhou', '1', '0', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-11-13 23:58:09', ''); INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', NULL); +-- ---------------------------- +-- Table structure for sys_package_plan +-- ---------------------------- +DROP TABLE IF EXISTS `sys_package_plan`; +CREATE TABLE `sys_package_plan` ( + `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称', + `price` double(10, 2) NOT NULL COMMENT '套餐价格', + `duration` int(11) NOT NULL COMMENT '有效时间', + `plan_detail` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计划详情', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1787085903419211779 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '套餐管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_package_plan +-- ---------------------------- +INSERT INTO `sys_package_plan` VALUES (1787085620534378498, '初级套餐', 9.90, 30, '解锁全部模型, dall·e 3, midjourney, gpt-3.5-turbo-1106, gpt-4-1106-preview, gpt-4-1106-vision-preview', 103, 1, '2024-05-05 19:43:09', 1, '2024-05-20 00:05:43', '初级套餐'); +INSERT INTO `sys_package_plan` VALUES (1787085808271425538, '中级套餐', 30.00, 30, '解锁全部模型, dall·e 3, midjourney, gpt-3.5-turbo-1106, gpt-4-1106-preview, gpt-4-1106-vision-preview', 103, 1, '2024-05-05 19:43:54', 1, '2024-05-05 22:29:42', '中级套餐'); +INSERT INTO `sys_package_plan` VALUES (1787085903419211778, '高级套餐', 60.00, 30, '解锁全部模型, dall·e 3, midjourney, gpt-3.5-turbo-1106, gpt-4-1106-preview, gpt-4-1106-vision-preview', 103, 1, '2024-05-05 19:44:16', 1, '2024-05-05 22:29:45', '高级套餐'); + -- ---------------------------- -- Table structure for sys_pay_order -- ---------------------------- DROP TABLE IF EXISTS `sys_pay_order`; CREATE TABLE `sys_pay_order` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', - `order_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号', - `order_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单名称', - `amount` decimal(10, 2) NOT NULL COMMENT '金额', - `payment_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付状态', - `payment_method` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式', - `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '支付订单表' ROW_FORMAT = Dynamic; + `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `order_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号', + `order_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单名称', + `amount` decimal(10, 2) NOT NULL COMMENT '金额', + `payment_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付状态', + `payment_method` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1794031846814027779 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '支付订单表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_pay_order @@ -956,19 +1243,19 @@ CREATE TABLE `sys_pay_order` ( -- ---------------------------- DROP TABLE IF EXISTS `sys_post`; CREATE TABLE `sys_post` ( - `post_id` bigint(20) NOT NULL COMMENT '岗位ID', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', - `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', - `post_sort` int(11) NOT NULL COMMENT '显示顺序', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`post_id`) USING BTREE + `post_id` bigint(20) NOT NULL COMMENT '岗位ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int(4) NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -984,23 +1271,23 @@ INSERT INTO `sys_post` VALUES (4, '000000', 'user', '普通员工', 4, '0', 103, -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', - `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', - `role_sort` int(11) NOT NULL COMMENT '显示顺序', - `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', - `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', - `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`role_id`) USING BTREE + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int(4) NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -1018,9 +1305,9 @@ INSERT INTO `sys_role` VALUES (1729685491108446210, '911866', '管理员', 'admi -- ---------------------------- DROP TABLE IF EXISTS `sys_role_dept`; CREATE TABLE `sys_role_dept` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `dept_id` bigint(20) NOT NULL COMMENT '部门ID', - PRIMARY KEY (`role_id`, `dept_id`) USING BTREE + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -1036,9 +1323,9 @@ INSERT INTO `sys_role_dept` VALUES (1729685491108446210, 1729685491964084226); -- ---------------------------- DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', - PRIMARY KEY (`role_id`, `menu_id`) USING BTREE + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; -- ---------------------------- @@ -1204,126 +1491,1104 @@ INSERT INTO `sys_role_menu` VALUES (1729685491108446210, 1689243466220355585); -- ---------------------------- DROP TABLE IF EXISTS `sys_tenant`; CREATE TABLE `sys_tenant` ( - `id` bigint(20) NOT NULL COMMENT 'id', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户编号', - `contact_user_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系人', - `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', - `company_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业名称', - `license_number` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '统一社会信用代码', - `address` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', - `intro` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业简介', - `domain` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '域名', - `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - `package_id` bigint(20) NULL DEFAULT NULL COMMENT '租户套餐编号', - `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间', - `account_count` int(11) NULL DEFAULT -1 COMMENT '用户数量(-1不限制)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '租户状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`) USING BTREE + `id` bigint(20) NOT NULL COMMENT 'id', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户编号', + `contact_user_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系人', + `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `company_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业名称', + `license_number` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '统一社会信用代码', + `address` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', + `intro` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业简介', + `domain` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '域名', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `package_id` bigint(20) NULL DEFAULT NULL COMMENT '租户套餐编号', + `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间', + `account_count` int(11) NULL DEFAULT -1 COMMENT '用户数量(-1不限制)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '租户状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_tenant -- ---------------------------- INSERT INTO `sys_tenant` VALUES (1, '000000', '管理组', '15888888888', 'XXX有限公司', NULL, NULL, '多租户通用后台管理管理系统', NULL, NULL, NULL, NULL, -1, '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_tenant` VALUES (1729685490647072769, '911866', '陈', '11111111111', '5126', '', '', '', '', '', 1729685389795033090, NULL, 1, '0', '2', 103, 1, '2023-11-29 10:15:32', 1, '2023-11-29 10:15:32'); -- ---------------------------- -- Table structure for sys_tenant_package -- ---------------------------- DROP TABLE IF EXISTS `sys_tenant_package`; CREATE TABLE `sys_tenant_package` ( - `package_id` bigint(20) NOT NULL COMMENT '租户套餐id', - `package_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '套餐名称', - `menu_ids` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联菜单id', - `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`package_id`) USING BTREE + `package_id` bigint(20) NOT NULL COMMENT '租户套餐id', + `package_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '套餐名称', + `menu_ids` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联菜单id', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`package_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户套餐表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_tenant_package -- ---------------------------- -INSERT INTO `sys_tenant_package` VALUES (1729685389795033090, '绘画', '1689205943360188417, 1689243466220355585, 1689201668374556674, 1689243465037561858', '', 1, '0', '2', 103, 1, '2023-11-29 10:15:08', 1, '2023-11-29 10:15:08'); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信用户标识', - `user_grade` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户等级', - `user_balance` double(20, 2) NULL DEFAULT 0.00 COMMENT '账户余额', - `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', - `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', - `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', - `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', - `user_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'sys_user' COMMENT '用户类型(sys_user系统用户)', - `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱', - `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', - `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', - `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像地址', - `wx_avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信头像地址', - `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', - `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`user_id`) USING BTREE + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信用户标识', + `user_grade` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户等级', + `user_balance` double(20, 2) NULL DEFAULT 0.00 COMMENT '账户余额', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'sys_user' COMMENT '用户类型(sys_user系统用户)', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像地址', + `wx_avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信头像地址', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1, NULL, '0', 99.00, '00000', 103, 'admin', '熊猫助手', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', NULL, NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '0:0:0:0:0:0:0:1', '2024-05-13 19:48:08', 103, 1, '2023-05-14 15:19:39', 1, '2024-05-13 19:48:08', '管理员'); -INSERT INTO `sys_user` VALUES (1714176194496339970, NULL, '1', 115.55, '00000', NULL, 'pandarobot@163.com', '问答助手', 'sys_user', '', '', '0', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/04/02/ce28414c5eda47c793730a3ea3f006ac.jpg', NULL, '$2a$10$vUINJgUxBFt7L9OdNjxg0.TEGx4r1243rptfXDekLTeiNtlXU0GEK', '0', '0', '127.0.0.1', '2024-05-13 16:03:00', 103, 1713440206715650049, '2023-10-17 15:07:07', 1714176194496339970, '2024-05-13 16:03:00', NULL); +INSERT INTO `sys_user` VALUES (1, NULL, '1', 90.00, '00000', 103, 'admin', '熊猫助手', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', NULL, NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '0:0:0:0:0:0:0:1', '2024-05-19 23:22:53', 103, 1, '2023-05-14 15:19:39', 1, '2024-05-19 23:22:53', '管理员'); +INSERT INTO `sys_user` VALUES (1784521057603538945, NULL, '1', 99.99, '00000', NULL, 'ageerle@163.com', '熊猫助手', 'sys_user', 'ageerle@163.com', '15888888888', '0', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/05/01/62bddbd6c367496fbc6145a3c03ecbc4.jpg', NULL, '$2a$10$PShVg1L1GTMwmrfDZY3yAu5/S5qt6AtzFzPB2CAolJmYIKEOs85X6', '0', '0', '127.0.0.1', '2024-05-27 17:31:13', NULL, NULL, '2024-04-28 17:52:30', 1784521057603538945, '2024-05-27 17:31:13', NULL); -- ---------------------------- -- Table structure for sys_user_post -- ---------------------------- DROP TABLE IF EXISTS `sys_user_post`; CREATE TABLE `sys_user_post` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `post_id` bigint(20) NOT NULL COMMENT '岗位ID', - PRIMARY KEY (`user_id`, `post_id`) USING BTREE + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `post_id` bigint(20) NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_post -- ---------------------------- INSERT INTO `sys_user_post` VALUES (1, 1); +INSERT INTO `sys_user_post` VALUES (2, 2); +INSERT INTO `sys_user_post` VALUES (1661660085084250114, 2); +INSERT INTO `sys_user_post` VALUES (1661660804847788034, 1); -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - PRIMARY KEY (`user_id`, `role_id`) USING BTREE + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (2, 2); +INSERT INTO `sys_user_role` VALUES (3, 3); +INSERT INTO `sys_user_role` VALUES (4, 4); +INSERT INTO `sys_user_role` VALUES (1661646824293031937, 1661661183933177857); +INSERT INTO `sys_user_role` VALUES (1661660085084250114, 1661661183933177857); +INSERT INTO `sys_user_role` VALUES (1661660804847788034, 2); +INSERT INTO `sys_user_role` VALUES (1713427806956404738, 1); +INSERT INTO `sys_user_role` VALUES (1713439839684689921, 1); +INSERT INTO `sys_user_role` VALUES (1713440206715650049, 1); +INSERT INTO `sys_user_role` VALUES (1713724795803299841, 1); +INSERT INTO `sys_user_role` VALUES (1714176194496339970, 1); +INSERT INTO `sys_user_role` VALUES (1714267685998907393, 1); +INSERT INTO `sys_user_role` VALUES (1714269581270667265, 1); +INSERT INTO `sys_user_role` VALUES (1714270420659949569, 1); +INSERT INTO `sys_user_role` VALUES (1714455864827723777, 1); +INSERT INTO `sys_user_role` VALUES (1714536425072115714, 1); +INSERT INTO `sys_user_role` VALUES (1714819715117105153, 1); +INSERT INTO `sys_user_role` VALUES (1714820415783976961, 1); +INSERT INTO `sys_user_role` VALUES (1714820611611836417, 1); +INSERT INTO `sys_user_role` VALUES (1714820755698761729, 1); +INSERT INTO `sys_user_role` VALUES (1714823588305190914, 1); +INSERT INTO `sys_user_role` VALUES (1714829502936530945, 1); +INSERT INTO `sys_user_role` VALUES (1714835527643185154, 1); +INSERT INTO `sys_user_role` VALUES (1714835835278606337, 1); +INSERT INTO `sys_user_role` VALUES (1714898663033290754, 1); +INSERT INTO `sys_user_role` VALUES (1714942733206175746, 1); +INSERT INTO `sys_user_role` VALUES (1714943378361434113, 1); +INSERT INTO `sys_user_role` VALUES (1714943388671033346, 1); +INSERT INTO `sys_user_role` VALUES (1714945928464711682, 1); +INSERT INTO `sys_user_role` VALUES (1714946100850606082, 1); +INSERT INTO `sys_user_role` VALUES (1714952355237347329, 1); +INSERT INTO `sys_user_role` VALUES (1714954192279584770, 1); +INSERT INTO `sys_user_role` VALUES (1714960721598758913, 1); +INSERT INTO `sys_user_role` VALUES (1714961357132283906, 1); +INSERT INTO `sys_user_role` VALUES (1714963426656403458, 1); +INSERT INTO `sys_user_role` VALUES (1714980339130318850, 1); +INSERT INTO `sys_user_role` VALUES (1714985002550444034, 1); +INSERT INTO `sys_user_role` VALUES (1714996959085084674, 1); +INSERT INTO `sys_user_role` VALUES (1715000784541990913, 1); +INSERT INTO `sys_user_role` VALUES (1715160830886297602, 1); +INSERT INTO `sys_user_role` VALUES (1715174792021426177, 1); +INSERT INTO `sys_user_role` VALUES (1715176760861278209, 1); +INSERT INTO `sys_user_role` VALUES (1715187418688405506, 1); +INSERT INTO `sys_user_role` VALUES (1715263570077564930, 1); +INSERT INTO `sys_user_role` VALUES (1715273299113820162, 1); +INSERT INTO `sys_user_role` VALUES (1715289765028577281, 1); +INSERT INTO `sys_user_role` VALUES (1715642509052624897, 1); +INSERT INTO `sys_user_role` VALUES (1715645217792868353, 1); +INSERT INTO `sys_user_role` VALUES (1715655140035543041, 1); +INSERT INTO `sys_user_role` VALUES (1715688813166346242, 1); +INSERT INTO `sys_user_role` VALUES (1715695623109623810, 1); +INSERT INTO `sys_user_role` VALUES (1716076523383177217, 1); +INSERT INTO `sys_user_role` VALUES (1716077329079615490, 1); +INSERT INTO `sys_user_role` VALUES (1716316658037178370, 1); +INSERT INTO `sys_user_role` VALUES (1716375479287824386, 1); +INSERT INTO `sys_user_role` VALUES (1716376929359380482, 1); +INSERT INTO `sys_user_role` VALUES (1716449431389487106, 1); +INSERT INTO `sys_user_role` VALUES (1716626232627707906, 1); +INSERT INTO `sys_user_role` VALUES (1716668774639484929, 1); +INSERT INTO `sys_user_role` VALUES (1716723582348050434, 1); +INSERT INTO `sys_user_role` VALUES (1717010625036828674, 1); +INSERT INTO `sys_user_role` VALUES (1717112818712723458, 1); +INSERT INTO `sys_user_role` VALUES (1717171039955599361, 1); +INSERT INTO `sys_user_role` VALUES (1717382776042569730, 1); +INSERT INTO `sys_user_role` VALUES (1717383874597896194, 1); +INSERT INTO `sys_user_role` VALUES (1717463477270102018, 1); +INSERT INTO `sys_user_role` VALUES (1717550755342467074, 1); +INSERT INTO `sys_user_role` VALUES (1718643906618605569, 1); +INSERT INTO `sys_user_role` VALUES (1719357065528623105, 1); +INSERT INTO `sys_user_role` VALUES (1719629669720145921, 1); +INSERT INTO `sys_user_role` VALUES (1719631746265530370, 1); +INSERT INTO `sys_user_role` VALUES (1719969371128086529, 1); +INSERT INTO `sys_user_role` VALUES (1719994192431955970, 1); +INSERT INTO `sys_user_role` VALUES (1720001597920264194, 1); +INSERT INTO `sys_user_role` VALUES (1720054174099718145, 1); +INSERT INTO `sys_user_role` VALUES (1720373256426635265, 1); +INSERT INTO `sys_user_role` VALUES (1720615324298264578, 1); +INSERT INTO `sys_user_role` VALUES (1720966085100191746, 1); +INSERT INTO `sys_user_role` VALUES (1721433118342397954, 1); +INSERT INTO `sys_user_role` VALUES (1721798759096270850, 1); +INSERT INTO `sys_user_role` VALUES (1721869407395332097, 1); +INSERT INTO `sys_user_role` VALUES (1721869952080232450, 1); +INSERT INTO `sys_user_role` VALUES (1722083875718737921, 1); +INSERT INTO `sys_user_role` VALUES (1722126825769185282, 1); +INSERT INTO `sys_user_role` VALUES (1722453238653169665, 1); +INSERT INTO `sys_user_role` VALUES (1722501722198552577, 1); +INSERT INTO `sys_user_role` VALUES (1722546398997819394, 1); +INSERT INTO `sys_user_role` VALUES (1722635856464097281, 1); +INSERT INTO `sys_user_role` VALUES (1722652602847768578, 1); +INSERT INTO `sys_user_role` VALUES (1722787874222682114, 1); +INSERT INTO `sys_user_role` VALUES (1722799180870889473, 1); +INSERT INTO `sys_user_role` VALUES (1722872660475817986, 1); +INSERT INTO `sys_user_role` VALUES (1722874592401600514, 1); +INSERT INTO `sys_user_role` VALUES (1722883137289367554, 1); +INSERT INTO `sys_user_role` VALUES (1722918534182645762, 1); +INSERT INTO `sys_user_role` VALUES (1723173295586848769, 1); +INSERT INTO `sys_user_role` VALUES (1723222687891107841, 1); +INSERT INTO `sys_user_role` VALUES (1723224404040921089, 1); +INSERT INTO `sys_user_role` VALUES (1723225015520112641, 1); +INSERT INTO `sys_user_role` VALUES (1723278284531478529, 1); +INSERT INTO `sys_user_role` VALUES (1723330835209564161, 1); +INSERT INTO `sys_user_role` VALUES (1723708198137147393, 1); +INSERT INTO `sys_user_role` VALUES (1723754683843260417, 1); +INSERT INTO `sys_user_role` VALUES (1723878185250369537, 1); +INSERT INTO `sys_user_role` VALUES (1723940614634254337, 1); +INSERT INTO `sys_user_role` VALUES (1723975861757325314, 1); +INSERT INTO `sys_user_role` VALUES (1724306907803725826, 1); +INSERT INTO `sys_user_role` VALUES (1724308252862492673, 1); +INSERT INTO `sys_user_role` VALUES (1724382895124295681, 1); +INSERT INTO `sys_user_role` VALUES (1724727778758406145, 1); +INSERT INTO `sys_user_role` VALUES (1724815478295425026, 1); +INSERT INTO `sys_user_role` VALUES (1725026071145107458, 1); +INSERT INTO `sys_user_role` VALUES (1725026978817658881, 1); +INSERT INTO `sys_user_role` VALUES (1725043562961457154, 1); +INSERT INTO `sys_user_role` VALUES (1725058936893362178, 1); +INSERT INTO `sys_user_role` VALUES (1725363117009162242, 1); +INSERT INTO `sys_user_role` VALUES (1725538633251049474, 1); +INSERT INTO `sys_user_role` VALUES (1725564937467875329, 1); +INSERT INTO `sys_user_role` VALUES (1725891713243021314, 1); +INSERT INTO `sys_user_role` VALUES (1725905000621932546, 1); +INSERT INTO `sys_user_role` VALUES (1726440708294049793, 1); +INSERT INTO `sys_user_role` VALUES (1726443526979584002, 1); +INSERT INTO `sys_user_role` VALUES (1726445663797116929, 1); +INSERT INTO `sys_user_role` VALUES (1726452867329687553, 1); +INSERT INTO `sys_user_role` VALUES (1726472827451998209, 1); +INSERT INTO `sys_user_role` VALUES (1726479651370696705, 1); +INSERT INTO `sys_user_role` VALUES (1726487492674195458, 1); +INSERT INTO `sys_user_role` VALUES (1726496513055784961, 1); +INSERT INTO `sys_user_role` VALUES (1726498781398302722, 1); +INSERT INTO `sys_user_role` VALUES (1726506873632587778, 1); +INSERT INTO `sys_user_role` VALUES (1726529248394739714, 1); +INSERT INTO `sys_user_role` VALUES (1726578079102664705, 1); +INSERT INTO `sys_user_role` VALUES (1726582181383634946, 1); +INSERT INTO `sys_user_role` VALUES (1726583555672506369, 1); +INSERT INTO `sys_user_role` VALUES (1726596448690372609, 1); +INSERT INTO `sys_user_role` VALUES (1726599361261207553, 1); +INSERT INTO `sys_user_role` VALUES (1726604511749079041, 1); +INSERT INTO `sys_user_role` VALUES (1726606973822304258, 1); +INSERT INTO `sys_user_role` VALUES (1726609379524083713, 1); +INSERT INTO `sys_user_role` VALUES (1726616151265640450, 1); +INSERT INTO `sys_user_role` VALUES (1726775811478126594, 1); +INSERT INTO `sys_user_role` VALUES (1726795490141667329, 1); +INSERT INTO `sys_user_role` VALUES (1726798403169681410, 1); +INSERT INTO `sys_user_role` VALUES (1726830794655399937, 1); +INSERT INTO `sys_user_role` VALUES (1726862038013313026, 1); +INSERT INTO `sys_user_role` VALUES (1726919220696186882, 1); +INSERT INTO `sys_user_role` VALUES (1727140184050630658, 1); +INSERT INTO `sys_user_role` VALUES (1727506163368722433, 1); +INSERT INTO `sys_user_role` VALUES (1727518983086931969, 1); +INSERT INTO `sys_user_role` VALUES (1727580969606840321, 1); +INSERT INTO `sys_user_role` VALUES (1727590505323429890, 1); +INSERT INTO `sys_user_role` VALUES (1727918393172164609, 1); +INSERT INTO `sys_user_role` VALUES (1728249002000121857, 1); +INSERT INTO `sys_user_role` VALUES (1728680561446486017, 1); +INSERT INTO `sys_user_role` VALUES (1728964404182577153, 1); +INSERT INTO `sys_user_role` VALUES (1729020459675611137, 1); +INSERT INTO `sys_user_role` VALUES (1729051002043691009, 1); +INSERT INTO `sys_user_role` VALUES (1729423744832172033, 1); +INSERT INTO `sys_user_role` VALUES (1729429590291050497, 1); +INSERT INTO `sys_user_role` VALUES (1729685493222375426, 1729685491108446210); +INSERT INTO `sys_user_role` VALUES (1730050324466036738, 1); +INSERT INTO `sys_user_role` VALUES (1730102403335254018, 1); +INSERT INTO `sys_user_role` VALUES (1730129923250122754, 1); +INSERT INTO `sys_user_role` VALUES (1730155108925763586, 1); +INSERT INTO `sys_user_role` VALUES (1730273428207366145, 1); +INSERT INTO `sys_user_role` VALUES (1730498722784669697, 1); +INSERT INTO `sys_user_role` VALUES (1730815105229713410, 1); +INSERT INTO `sys_user_role` VALUES (1730858886951923714, 1); +INSERT INTO `sys_user_role` VALUES (1731357405659824130, 1); +INSERT INTO `sys_user_role` VALUES (1731475532557090818, 1); +INSERT INTO `sys_user_role` VALUES (1731480953627901953, 1); +INSERT INTO `sys_user_role` VALUES (1731502381106495490, 1); +INSERT INTO `sys_user_role` VALUES (1731524458442162177, 1); +INSERT INTO `sys_user_role` VALUES (1731524630094053377, 1); +INSERT INTO `sys_user_role` VALUES (1731524650293821441, 1); +INSERT INTO `sys_user_role` VALUES (1731529253710233601, 1); +INSERT INTO `sys_user_role` VALUES (1731559936046432258, 1); +INSERT INTO `sys_user_role` VALUES (1731564032228884482, 1); +INSERT INTO `sys_user_role` VALUES (1731565926737281026, 1); +INSERT INTO `sys_user_role` VALUES (1731566918589513729, 1); +INSERT INTO `sys_user_role` VALUES (1731567740094283778, 1); +INSERT INTO `sys_user_role` VALUES (1731575439263563777, 1); +INSERT INTO `sys_user_role` VALUES (1731583864055824385, 1); +INSERT INTO `sys_user_role` VALUES (1731588155382464513, 1); +INSERT INTO `sys_user_role` VALUES (1731589827840212993, 1); +INSERT INTO `sys_user_role` VALUES (1731635461435719682, 1); +INSERT INTO `sys_user_role` VALUES (1731668049902731266, 1); +INSERT INTO `sys_user_role` VALUES (1731922694168412162, 1); +INSERT INTO `sys_user_role` VALUES (1731944975456305153, 1); +INSERT INTO `sys_user_role` VALUES (1731949019394506753, 1); +INSERT INTO `sys_user_role` VALUES (1731951425054343170, 1); +INSERT INTO `sys_user_role` VALUES (1732000242621513729, 1); +INSERT INTO `sys_user_role` VALUES (1732027163380056066, 1); +INSERT INTO `sys_user_role` VALUES (1732289382269353985, 1); +INSERT INTO `sys_user_role` VALUES (1732289439282528258, 1); +INSERT INTO `sys_user_role` VALUES (1732289699585228801, 1); +INSERT INTO `sys_user_role` VALUES (1732290827173527553, 1); +INSERT INTO `sys_user_role` VALUES (1732291549344595969, 1); +INSERT INTO `sys_user_role` VALUES (1732293265184030721, 1); +INSERT INTO `sys_user_role` VALUES (1732329664117506049, 1); +INSERT INTO `sys_user_role` VALUES (1732334104450990081, 1); +INSERT INTO `sys_user_role` VALUES (1732578671045672962, 1); +INSERT INTO `sys_user_role` VALUES (1732584047426174978, 1); +INSERT INTO `sys_user_role` VALUES (1732608690321129474, 1); +INSERT INTO `sys_user_role` VALUES (1732678147815014401, 1); +INSERT INTO `sys_user_role` VALUES (1732731410102910977, 1); +INSERT INTO `sys_user_role` VALUES (1733005266763939841, 1); +INSERT INTO `sys_user_role` VALUES (1733016149837774850, 1); +INSERT INTO `sys_user_role` VALUES (1733053523871432705, 1); +INSERT INTO `sys_user_role` VALUES (1733061400367497218, 1); +INSERT INTO `sys_user_role` VALUES (1733167090469732353, 1); +INSERT INTO `sys_user_role` VALUES (1733298702729641986, 1); +INSERT INTO `sys_user_role` VALUES (1733488544511983617, 1); +INSERT INTO `sys_user_role` VALUES (1733720554119659521, 1); +INSERT INTO `sys_user_role` VALUES (1733846657777827842, 1); +INSERT INTO `sys_user_role` VALUES (1733859832720031745, 1); +INSERT INTO `sys_user_role` VALUES (1734137817339559938, 1); +INSERT INTO `sys_user_role` VALUES (1734227535762849793, 1); +INSERT INTO `sys_user_role` VALUES (1734492373726560257, 1); +INSERT INTO `sys_user_role` VALUES (1734508040978726914, 1); +INSERT INTO `sys_user_role` VALUES (1734513545461661697, 1); +INSERT INTO `sys_user_role` VALUES (1734581580998451202, 1); +INSERT INTO `sys_user_role` VALUES (1734751884580298754, 1); +INSERT INTO `sys_user_role` VALUES (1734781716483612674, 1); +INSERT INTO `sys_user_role` VALUES (1734833221987278849, 1); +INSERT INTO `sys_user_role` VALUES (1734834063154946050, 1); +INSERT INTO `sys_user_role` VALUES (1734880697666576386, 1); +INSERT INTO `sys_user_role` VALUES (1734891995888427009, 1); +INSERT INTO `sys_user_role` VALUES (1735132534701367297, 1); +INSERT INTO `sys_user_role` VALUES (1735242647239991298, 1); +INSERT INTO `sys_user_role` VALUES (1735486862444273666, 1); +INSERT INTO `sys_user_role` VALUES (1735487912727355394, 1); +INSERT INTO `sys_user_role` VALUES (1735542352767426561, 1); +INSERT INTO `sys_user_role` VALUES (1735551915889598466, 1); +INSERT INTO `sys_user_role` VALUES (1735616653411557377, 1); +INSERT INTO `sys_user_role` VALUES (1735835864146714626, 1); +INSERT INTO `sys_user_role` VALUES (1735953007769100289, 1); +INSERT INTO `sys_user_role` VALUES (1735960189784891393, 1); +INSERT INTO `sys_user_role` VALUES (1736265950381547522, 1); +INSERT INTO `sys_user_role` VALUES (1736577606684844034, 1); +INSERT INTO `sys_user_role` VALUES (1736638822375563266, 1); +INSERT INTO `sys_user_role` VALUES (1736779069306511361, 1); +INSERT INTO `sys_user_role` VALUES (1737028378602053634, 1); +INSERT INTO `sys_user_role` VALUES (1737271234797314050, 1); +INSERT INTO `sys_user_role` VALUES (1737315322405920770, 1); +INSERT INTO `sys_user_role` VALUES (1737445221154234370, 1); +INSERT INTO `sys_user_role` VALUES (1737452907568635906, 1); +INSERT INTO `sys_user_role` VALUES (1737453186955419649, 1); +INSERT INTO `sys_user_role` VALUES (1737717777685880833, 1); +INSERT INTO `sys_user_role` VALUES (1737768515594166274, 1); +INSERT INTO `sys_user_role` VALUES (1738108912170246145, 1); +INSERT INTO `sys_user_role` VALUES (1738118086488825858, 1); +INSERT INTO `sys_user_role` VALUES (1738520430804279297, 1); +INSERT INTO `sys_user_role` VALUES (1738802060248817666, 1); +INSERT INTO `sys_user_role` VALUES (1738812447119712257, 1); +INSERT INTO `sys_user_role` VALUES (1738941480197234689, 1); +INSERT INTO `sys_user_role` VALUES (1738963430776840194, 1); +INSERT INTO `sys_user_role` VALUES (1739121784341995522, 1); +INSERT INTO `sys_user_role` VALUES (1739166931951886338, 1); +INSERT INTO `sys_user_role` VALUES (1739272055240073217, 1); +INSERT INTO `sys_user_role` VALUES (1739451838930427905, 1); +INSERT INTO `sys_user_role` VALUES (1739452037375533057, 1); +INSERT INTO `sys_user_role` VALUES (1739452376946384898, 1); +INSERT INTO `sys_user_role` VALUES (1739484503888961537, 1); +INSERT INTO `sys_user_role` VALUES (1739485282335006722, 1); +INSERT INTO `sys_user_role` VALUES (1739577551431999490, 1); +INSERT INTO `sys_user_role` VALUES (1739825609910591489, 1); +INSERT INTO `sys_user_role` VALUES (1739916453439152130, 1); +INSERT INTO `sys_user_role` VALUES (1740188388454629378, 1); +INSERT INTO `sys_user_role` VALUES (1741339991320580097, 1); +INSERT INTO `sys_user_role` VALUES (1741803737633542145, 1); +INSERT INTO `sys_user_role` VALUES (1741823858229923841, 1); +INSERT INTO `sys_user_role` VALUES (1741845883943227393, 1); +INSERT INTO `sys_user_role` VALUES (1742179775941201921, 1); +INSERT INTO `sys_user_role` VALUES (1742437553771458562, 1); +INSERT INTO `sys_user_role` VALUES (1742451201315254273, 1); +INSERT INTO `sys_user_role` VALUES (1742469913120419841, 1); +INSERT INTO `sys_user_role` VALUES (1742798283280568321, 1); +INSERT INTO `sys_user_role` VALUES (1742798987701342210, 1); +INSERT INTO `sys_user_role` VALUES (1742799476950126594, 1); +INSERT INTO `sys_user_role` VALUES (1742799839619010562, 1); +INSERT INTO `sys_user_role` VALUES (1742801019527057410, 1); +INSERT INTO `sys_user_role` VALUES (1742804073915699202, 1); +INSERT INTO `sys_user_role` VALUES (1742821280687149058, 1); +INSERT INTO `sys_user_role` VALUES (1742821467476283394, 1); +INSERT INTO `sys_user_role` VALUES (1742822775600009217, 1); +INSERT INTO `sys_user_role` VALUES (1742823890928357377, 1); +INSERT INTO `sys_user_role` VALUES (1742838225297821697, 1); +INSERT INTO `sys_user_role` VALUES (1742902317295423490, 1); +INSERT INTO `sys_user_role` VALUES (1742910854243373058, 1); +INSERT INTO `sys_user_role` VALUES (1742961994725150721, 1); +INSERT INTO `sys_user_role` VALUES (1742969861079388161, 1); +INSERT INTO `sys_user_role` VALUES (1743068363130228737, 1); +INSERT INTO `sys_user_role` VALUES (1743075924621479938, 1); +INSERT INTO `sys_user_role` VALUES (1743079200725225474, 1); +INSERT INTO `sys_user_role` VALUES (1743085878682144769, 1); +INSERT INTO `sys_user_role` VALUES (1743110774967586818, 1); +INSERT INTO `sys_user_role` VALUES (1743162481042870274, 1); +INSERT INTO `sys_user_role` VALUES (1743166491284033537, 1); +INSERT INTO `sys_user_role` VALUES (1743251016219447297, 1); +INSERT INTO `sys_user_role` VALUES (1743469820367142914, 1); +INSERT INTO `sys_user_role` VALUES (1743514389280522242, 1); +INSERT INTO `sys_user_role` VALUES (1743519646916083714, 1); +INSERT INTO `sys_user_role` VALUES (1743670356026654722, 1); +INSERT INTO `sys_user_role` VALUES (1743892570516815874, 1); +INSERT INTO `sys_user_role` VALUES (1743952049409146882, 1); +INSERT INTO `sys_user_role` VALUES (1744268693259993089, 1); +INSERT INTO `sys_user_role` VALUES (1744351384550567938, 1); +INSERT INTO `sys_user_role` VALUES (1744561041202278402, 1); +INSERT INTO `sys_user_role` VALUES (1744574752277196801, 1); +INSERT INTO `sys_user_role` VALUES (1744619123995373569, 1); +INSERT INTO `sys_user_role` VALUES (1744627110742913025, 1); +INSERT INTO `sys_user_role` VALUES (1744634408357916673, 1); +INSERT INTO `sys_user_role` VALUES (1744645281965207554, 1); +INSERT INTO `sys_user_role` VALUES (1744724410316156930, 1); +INSERT INTO `sys_user_role` VALUES (1744892307919400962, 1); +INSERT INTO `sys_user_role` VALUES (1744903174606090241, 1); +INSERT INTO `sys_user_role` VALUES (1744904968014983169, 1); +INSERT INTO `sys_user_role` VALUES (1744905787204497410, 1); +INSERT INTO `sys_user_role` VALUES (1744911513595473921, 1); +INSERT INTO `sys_user_role` VALUES (1744912178359103490, 1); +INSERT INTO `sys_user_role` VALUES (1744912486720139266, 1); +INSERT INTO `sys_user_role` VALUES (1744915552240463874, 1); +INSERT INTO `sys_user_role` VALUES (1744923917133869058, 1); +INSERT INTO `sys_user_role` VALUES (1744971513579761666, 1); +INSERT INTO `sys_user_role` VALUES (1744984070818426882, 1); +INSERT INTO `sys_user_role` VALUES (1744984147393835010, 1); +INSERT INTO `sys_user_role` VALUES (1744992401243041793, 1); +INSERT INTO `sys_user_role` VALUES (1745011131444424706, 1); +INSERT INTO `sys_user_role` VALUES (1745061549180514306, 1); +INSERT INTO `sys_user_role` VALUES (1745346479991091201, 1); +INSERT INTO `sys_user_role` VALUES (1745346822607007745, 1); +INSERT INTO `sys_user_role` VALUES (1745368346374217730, 1); +INSERT INTO `sys_user_role` VALUES (1745424741765259266, 1); +INSERT INTO `sys_user_role` VALUES (1745426757090582530, 1); +INSERT INTO `sys_user_role` VALUES (1745620173124575234, 1); +INSERT INTO `sys_user_role` VALUES (1745623876426571777, 1); +INSERT INTO `sys_user_role` VALUES (1745654577691664386, 1); +INSERT INTO `sys_user_role` VALUES (1745663259879972865, 1); +INSERT INTO `sys_user_role` VALUES (1745686038692012034, 1); +INSERT INTO `sys_user_role` VALUES (1745738268480675842, 1); +INSERT INTO `sys_user_role` VALUES (1745790952546017281, 1); +INSERT INTO `sys_user_role` VALUES (1746397384551211009, 1); +INSERT INTO `sys_user_role` VALUES (1746400980533551105, 1); +INSERT INTO `sys_user_role` VALUES (1746522414111039489, 1); +INSERT INTO `sys_user_role` VALUES (1746873386528223234, 1); +INSERT INTO `sys_user_role` VALUES (1747067318369333249, 1); +INSERT INTO `sys_user_role` VALUES (1747071365822361602, 1); +INSERT INTO `sys_user_role` VALUES (1747153912031948801, 1); +INSERT INTO `sys_user_role` VALUES (1747197655195922434, 1); +INSERT INTO `sys_user_role` VALUES (1747519480203390977, 1); +INSERT INTO `sys_user_role` VALUES (1747521265550831618, 1); +INSERT INTO `sys_user_role` VALUES (1747523421662162945, 1); +INSERT INTO `sys_user_role` VALUES (1747797864993075201, 1); +INSERT INTO `sys_user_role` VALUES (1747800427213697025, 1); +INSERT INTO `sys_user_role` VALUES (1747910191046275073, 1); +INSERT INTO `sys_user_role` VALUES (1747923453217419265, 1); +INSERT INTO `sys_user_role` VALUES (1748187110132232193, 1); +INSERT INTO `sys_user_role` VALUES (1748260926648823809, 1); +INSERT INTO `sys_user_role` VALUES (1748276826697445377, 1); +INSERT INTO `sys_user_role` VALUES (1748312313952808962, 1); +INSERT INTO `sys_user_role` VALUES (1748635584837529601, 1); +INSERT INTO `sys_user_role` VALUES (1748642479459610625, 1); +INSERT INTO `sys_user_role` VALUES (1748663294624346114, 1); +INSERT INTO `sys_user_role` VALUES (1748703876608503810, 1); +INSERT INTO `sys_user_role` VALUES (1748704145589219329, 1); +INSERT INTO `sys_user_role` VALUES (1748708285178523649, 1); +INSERT INTO `sys_user_role` VALUES (1748728575929430017, 1); +INSERT INTO `sys_user_role` VALUES (1748761666442047490, 1); +INSERT INTO `sys_user_role` VALUES (1748925826178035713, 1); +INSERT INTO `sys_user_role` VALUES (1749259130492235778, 1); +INSERT INTO `sys_user_role` VALUES (1749280237328871426, 1); +INSERT INTO `sys_user_role` VALUES (1749289400549322754, 1); +INSERT INTO `sys_user_role` VALUES (1749327661225291778, 1); +INSERT INTO `sys_user_role` VALUES (1749365593797636097, 1); +INSERT INTO `sys_user_role` VALUES (1749407786692325378, 1); +INSERT INTO `sys_user_role` VALUES (1749519043344805890, 1); +INSERT INTO `sys_user_role` VALUES (1749683041063219202, 1); +INSERT INTO `sys_user_role` VALUES (1749683546774646786, 1); +INSERT INTO `sys_user_role` VALUES (1749691765567860737, 1); +INSERT INTO `sys_user_role` VALUES (1749705571236917249, 1); +INSERT INTO `sys_user_role` VALUES (1749740828837359618, 1); +INSERT INTO `sys_user_role` VALUES (1749741179162406914, 1); +INSERT INTO `sys_user_role` VALUES (1749741340039131137, 1); +INSERT INTO `sys_user_role` VALUES (1749747618241130497, 1); +INSERT INTO `sys_user_role` VALUES (1749747701439344641, 1); +INSERT INTO `sys_user_role` VALUES (1749786825391157250, 1); +INSERT INTO `sys_user_role` VALUES (1749789665819963394, 1); +INSERT INTO `sys_user_role` VALUES (1749797707705823234, 1); +INSERT INTO `sys_user_role` VALUES (1749974903762210818, 1); +INSERT INTO `sys_user_role` VALUES (1749982777750081537, 1); +INSERT INTO `sys_user_role` VALUES (1749990634667134978, 1); +INSERT INTO `sys_user_role` VALUES (1749991325137653761, 1); +INSERT INTO `sys_user_role` VALUES (1749992779328016386, 1); +INSERT INTO `sys_user_role` VALUES (1749993573204905985, 1); +INSERT INTO `sys_user_role` VALUES (1749994406877351937, 1); +INSERT INTO `sys_user_role` VALUES (1749995279187726337, 1); +INSERT INTO `sys_user_role` VALUES (1749995486029828097, 1); +INSERT INTO `sys_user_role` VALUES (1749995707686211586, 1); +INSERT INTO `sys_user_role` VALUES (1750000406883749890, 1); +INSERT INTO `sys_user_role` VALUES (1750000942706085889, 1); +INSERT INTO `sys_user_role` VALUES (1750005079111913473, 1); +INSERT INTO `sys_user_role` VALUES (1750428606466117633, 1); +INSERT INTO `sys_user_role` VALUES (1750553534423126017, 1); +INSERT INTO `sys_user_role` VALUES (1750690119441469441, 1); +INSERT INTO `sys_user_role` VALUES (1750723725312413698, 1); +INSERT INTO `sys_user_role` VALUES (1750724537434525697, 1); +INSERT INTO `sys_user_role` VALUES (1750743381616119810, 1); +INSERT INTO `sys_user_role` VALUES (1750822931356192769, 1); +INSERT INTO `sys_user_role` VALUES (1750823004563574785, 1); +INSERT INTO `sys_user_role` VALUES (1751548639330177026, 1); +INSERT INTO `sys_user_role` VALUES (1751796140318658561, 1); +INSERT INTO `sys_user_role` VALUES (1751889049818763265, 1); +INSERT INTO `sys_user_role` VALUES (1751896081141600258, 1); +INSERT INTO `sys_user_role` VALUES (1751949653564723201, 1); +INSERT INTO `sys_user_role` VALUES (1751955373517443073, 1); +INSERT INTO `sys_user_role` VALUES (1751980511470292993, 1); +INSERT INTO `sys_user_role` VALUES (1752128867307884546, 1); +INSERT INTO `sys_user_role` VALUES (1752128948195037185, 1); +INSERT INTO `sys_user_role` VALUES (1752138835683708930, 1); +INSERT INTO `sys_user_role` VALUES (1752148500127682561, 1); +INSERT INTO `sys_user_role` VALUES (1752276638077816834, 1); +INSERT INTO `sys_user_role` VALUES (1752299834210521089, 1); +INSERT INTO `sys_user_role` VALUES (1752306117726703618, 1); +INSERT INTO `sys_user_role` VALUES (1752504006021222402, 1); +INSERT INTO `sys_user_role` VALUES (1752602885546840066, 1); +INSERT INTO `sys_user_role` VALUES (1752724639351050242, 1); +INSERT INTO `sys_user_role` VALUES (1753215436756357122, 1); +INSERT INTO `sys_user_role` VALUES (1753402656570216449, 1); +INSERT INTO `sys_user_role` VALUES (1753486557368029185, 1); +INSERT INTO `sys_user_role` VALUES (1753797902466551809, 1); +INSERT INTO `sys_user_role` VALUES (1753967757819908098, 1); +INSERT INTO `sys_user_role` VALUES (1754016754462887938, 1); +INSERT INTO `sys_user_role` VALUES (1754029247868440577, 1); +INSERT INTO `sys_user_role` VALUES (1754413960445562882, 1); +INSERT INTO `sys_user_role` VALUES (1754424078633537538, 1); +INSERT INTO `sys_user_role` VALUES (1754764137119354881, 1); +INSERT INTO `sys_user_role` VALUES (1755042084761899009, 1); +INSERT INTO `sys_user_role` VALUES (1755047141691625473, 1); +INSERT INTO `sys_user_role` VALUES (1756274975479173121, 1); +INSERT INTO `sys_user_role` VALUES (1756308183021260801, 1); +INSERT INTO `sys_user_role` VALUES (1757325877958938626, 1); +INSERT INTO `sys_user_role` VALUES (1758445439802675202, 1); +INSERT INTO `sys_user_role` VALUES (1759032628991234049, 1); +INSERT INTO `sys_user_role` VALUES (1759050804781125634, 1); +INSERT INTO `sys_user_role` VALUES (1759089524834045954, 1); +INSERT INTO `sys_user_role` VALUES (1759092949802029057, 1); +INSERT INTO `sys_user_role` VALUES (1759100324189573121, 1); +INSERT INTO `sys_user_role` VALUES (1759103449889771521, 1); +INSERT INTO `sys_user_role` VALUES (1759147026191749121, 1); +INSERT INTO `sys_user_role` VALUES (1759413482020147202, 1); +INSERT INTO `sys_user_role` VALUES (1759427862430486529, 1); +INSERT INTO `sys_user_role` VALUES (1759428010174844929, 1); +INSERT INTO `sys_user_role` VALUES (1759496088514465794, 1); +INSERT INTO `sys_user_role` VALUES (1759764705965510657, 1); +INSERT INTO `sys_user_role` VALUES (1759777481207320578, 1); +INSERT INTO `sys_user_role` VALUES (1759806155667279873, 1); +INSERT INTO `sys_user_role` VALUES (1759812015655227394, 1); +INSERT INTO `sys_user_role` VALUES (1759815447778693121, 1); +INSERT INTO `sys_user_role` VALUES (1759832486966726658, 1); +INSERT INTO `sys_user_role` VALUES (1759858071113830402, 1); +INSERT INTO `sys_user_role` VALUES (1759863475847827458, 1); +INSERT INTO `sys_user_role` VALUES (1759868018195173378, 1); +INSERT INTO `sys_user_role` VALUES (1759869729374736385, 1); +INSERT INTO `sys_user_role` VALUES (1760186079276175362, 1); +INSERT INTO `sys_user_role` VALUES (1760319626808922114, 1); +INSERT INTO `sys_user_role` VALUES (1760347236137963522, 1); +INSERT INTO `sys_user_role` VALUES (1760358546837868546, 1); +INSERT INTO `sys_user_role` VALUES (1760377107434180609, 1); +INSERT INTO `sys_user_role` VALUES (1760472305161998338, 1); +INSERT INTO `sys_user_role` VALUES (1760472829932343298, 1); +INSERT INTO `sys_user_role` VALUES (1760477732188721153, 1); +INSERT INTO `sys_user_role` VALUES (1760502088176504833, 1); +INSERT INTO `sys_user_role` VALUES (1760508166310203394, 1); +INSERT INTO `sys_user_role` VALUES (1760511294409543681, 1); +INSERT INTO `sys_user_role` VALUES (1760562604135682049, 1); +INSERT INTO `sys_user_role` VALUES (1760841877480280066, 1); +INSERT INTO `sys_user_role` VALUES (1760896840365510658, 1); +INSERT INTO `sys_user_role` VALUES (1760903600501428226, 1); +INSERT INTO `sys_user_role` VALUES (1761404022634844162, 1); +INSERT INTO `sys_user_role` VALUES (1761954868732891138, 1); +INSERT INTO `sys_user_role` VALUES (1761955584197267458, 1); +INSERT INTO `sys_user_role` VALUES (1762003524345401345, 1); +INSERT INTO `sys_user_role` VALUES (1762004833618366465, 1); +INSERT INTO `sys_user_role` VALUES (1762010183880937474, 1); +INSERT INTO `sys_user_role` VALUES (1762298283890839554, 1); +INSERT INTO `sys_user_role` VALUES (1762363188014747649, 1); +INSERT INTO `sys_user_role` VALUES (1762389902388367361, 1); +INSERT INTO `sys_user_role` VALUES (1762401081961746434, 1); +INSERT INTO `sys_user_role` VALUES (1762481911417540610, 1); +INSERT INTO `sys_user_role` VALUES (1762482221645041665, 1); +INSERT INTO `sys_user_role` VALUES (1762482243174404097, 1); +INSERT INTO `sys_user_role` VALUES (1762483838461153282, 1); +INSERT INTO `sys_user_role` VALUES (1762487212380262401, 1); +INSERT INTO `sys_user_role` VALUES (1762498553535008770, 1); +INSERT INTO `sys_user_role` VALUES (1762636163465138177, 1); +INSERT INTO `sys_user_role` VALUES (1762655625413185537, 1); +INSERT INTO `sys_user_role` VALUES (1762656108559257601, 1); +INSERT INTO `sys_user_role` VALUES (1762673833499217922, 1); +INSERT INTO `sys_user_role` VALUES (1762677825344163842, 1); +INSERT INTO `sys_user_role` VALUES (1762677876015550465, 1); +INSERT INTO `sys_user_role` VALUES (1762678082262061057, 1); +INSERT INTO `sys_user_role` VALUES (1762678138012749825, 1); +INSERT INTO `sys_user_role` VALUES (1762678144652333057, 1); +INSERT INTO `sys_user_role` VALUES (1762678174192816129, 1); +INSERT INTO `sys_user_role` VALUES (1762678472563019777, 1); +INSERT INTO `sys_user_role` VALUES (1762678534596775938, 1); +INSERT INTO `sys_user_role` VALUES (1762678534894571521, 1); +INSERT INTO `sys_user_role` VALUES (1762678581635895298, 1); +INSERT INTO `sys_user_role` VALUES (1762678844920745985, 1); +INSERT INTO `sys_user_role` VALUES (1762679194973163522, 1); +INSERT INTO `sys_user_role` VALUES (1762679425299173378, 1); +INSERT INTO `sys_user_role` VALUES (1762679810776682498, 1); +INSERT INTO `sys_user_role` VALUES (1762679862656028674, 1); +INSERT INTO `sys_user_role` VALUES (1762679937360777217, 1); +INSERT INTO `sys_user_role` VALUES (1762680184698884098, 1); +INSERT INTO `sys_user_role` VALUES (1762680290076577794, 1); +INSERT INTO `sys_user_role` VALUES (1762680350055124993, 1); +INSERT INTO `sys_user_role` VALUES (1762681014038614017, 1); +INSERT INTO `sys_user_role` VALUES (1762681042207559681, 1); +INSERT INTO `sys_user_role` VALUES (1762681082732924929, 1); +INSERT INTO `sys_user_role` VALUES (1762681088869191682, 1); +INSERT INTO `sys_user_role` VALUES (1762681283195490306, 1); +INSERT INTO `sys_user_role` VALUES (1762681876752420865, 1); +INSERT INTO `sys_user_role` VALUES (1762681980129431553, 1); +INSERT INTO `sys_user_role` VALUES (1762682038488977410, 1); +INSERT INTO `sys_user_role` VALUES (1762682208211488769, 1); +INSERT INTO `sys_user_role` VALUES (1762683406603833346, 1); +INSERT INTO `sys_user_role` VALUES (1762683500048732162, 1); +INSERT INTO `sys_user_role` VALUES (1762683740843724801, 1); +INSERT INTO `sys_user_role` VALUES (1762683806404890625, 1); +INSERT INTO `sys_user_role` VALUES (1762684131715108865, 1); +INSERT INTO `sys_user_role` VALUES (1762684408442703874, 1); +INSERT INTO `sys_user_role` VALUES (1762684686994821121, 1); +INSERT INTO `sys_user_role` VALUES (1762686405808017409, 1); +INSERT INTO `sys_user_role` VALUES (1762687370061729794, 1); +INSERT INTO `sys_user_role` VALUES (1762687537527705602, 1); +INSERT INTO `sys_user_role` VALUES (1762687814947360769, 1); +INSERT INTO `sys_user_role` VALUES (1762688734347186177, 1); +INSERT INTO `sys_user_role` VALUES (1762690035701305346, 1); +INSERT INTO `sys_user_role` VALUES (1762690104575971330, 1); +INSERT INTO `sys_user_role` VALUES (1762691273243283457, 1); +INSERT INTO `sys_user_role` VALUES (1762691277462753282, 1); +INSERT INTO `sys_user_role` VALUES (1762692468406013954, 1); +INSERT INTO `sys_user_role` VALUES (1762693304498573314, 1); +INSERT INTO `sys_user_role` VALUES (1762693710704332801, 1); +INSERT INTO `sys_user_role` VALUES (1762694382220791809, 1); +INSERT INTO `sys_user_role` VALUES (1762696242545610754, 1); +INSERT INTO `sys_user_role` VALUES (1762696275626086402, 1); +INSERT INTO `sys_user_role` VALUES (1762696945854894082, 1); +INSERT INTO `sys_user_role` VALUES (1762698940057702402, 1); +INSERT INTO `sys_user_role` VALUES (1762699511732948994, 1); +INSERT INTO `sys_user_role` VALUES (1762701338956320769, 1); +INSERT INTO `sys_user_role` VALUES (1762701352860438530, 1); +INSERT INTO `sys_user_role` VALUES (1762703221934575617, 1); +INSERT INTO `sys_user_role` VALUES (1762705239214444546, 1); +INSERT INTO `sys_user_role` VALUES (1762705858788642817, 1); +INSERT INTO `sys_user_role` VALUES (1762706220585111553, 1); +INSERT INTO `sys_user_role` VALUES (1762707979655237633, 1); +INSERT INTO `sys_user_role` VALUES (1762709372369686529, 1); +INSERT INTO `sys_user_role` VALUES (1762717698755186689, 1); +INSERT INTO `sys_user_role` VALUES (1762719280540471297, 1); +INSERT INTO `sys_user_role` VALUES (1762719395619590146, 1); +INSERT INTO `sys_user_role` VALUES (1762721161459322881, 1); +INSERT INTO `sys_user_role` VALUES (1762721300685049857, 1); +INSERT INTO `sys_user_role` VALUES (1762724284441612290, 1); +INSERT INTO `sys_user_role` VALUES (1762728759105474561, 1); +INSERT INTO `sys_user_role` VALUES (1762732886506131458, 1); +INSERT INTO `sys_user_role` VALUES (1762744418904354818, 1); +INSERT INTO `sys_user_role` VALUES (1762749711537188865, 1); +INSERT INTO `sys_user_role` VALUES (1762749741056700418, 1); +INSERT INTO `sys_user_role` VALUES (1762750396991320065, 1); +INSERT INTO `sys_user_role` VALUES (1762752966828797954, 1); +INSERT INTO `sys_user_role` VALUES (1762753464445218817, 1); +INSERT INTO `sys_user_role` VALUES (1762753558548623362, 1); +INSERT INTO `sys_user_role` VALUES (1762755306625478657, 1); +INSERT INTO `sys_user_role` VALUES (1762756726481268737, 1); +INSERT INTO `sys_user_role` VALUES (1762756744172843010, 1); +INSERT INTO `sys_user_role` VALUES (1762760948073410562, 1); +INSERT INTO `sys_user_role` VALUES (1762768424588062721, 1); +INSERT INTO `sys_user_role` VALUES (1762770353779159041, 1); +INSERT INTO `sys_user_role` VALUES (1762770690174922754, 1); +INSERT INTO `sys_user_role` VALUES (1762773352299671554, 1); +INSERT INTO `sys_user_role` VALUES (1762809323107954689, 1); +INSERT INTO `sys_user_role` VALUES (1762839585439133698, 1); +INSERT INTO `sys_user_role` VALUES (1762854389474177026, 1); +INSERT INTO `sys_user_role` VALUES (1762962461110611969, 1); +INSERT INTO `sys_user_role` VALUES (1763011242199920642, 1); +INSERT INTO `sys_user_role` VALUES (1763014994155843586, 1); +INSERT INTO `sys_user_role` VALUES (1763017291741048833, 1); +INSERT INTO `sys_user_role` VALUES (1763021759299760129, 1); +INSERT INTO `sys_user_role` VALUES (1763033286434140162, 1); +INSERT INTO `sys_user_role` VALUES (1763034914528735233, 1); +INSERT INTO `sys_user_role` VALUES (1763039329885138945, 1); +INSERT INTO `sys_user_role` VALUES (1763046791925248001, 1); +INSERT INTO `sys_user_role` VALUES (1763059898533851137, 1); +INSERT INTO `sys_user_role` VALUES (1763074956366229505, 1); +INSERT INTO `sys_user_role` VALUES (1763083906738335746, 1); +INSERT INTO `sys_user_role` VALUES (1763087371808059394, 1); +INSERT INTO `sys_user_role` VALUES (1763110723763351554, 1); +INSERT INTO `sys_user_role` VALUES (1763119583433633794, 1); +INSERT INTO `sys_user_role` VALUES (1763121912195100674, 1); +INSERT INTO `sys_user_role` VALUES (1763150617374142466, 1); +INSERT INTO `sys_user_role` VALUES (1763219512067928065, 1); +INSERT INTO `sys_user_role` VALUES (1763232955600777217, 1); +INSERT INTO `sys_user_role` VALUES (1763234635201425410, 1); +INSERT INTO `sys_user_role` VALUES (1763246126281568257, 1); +INSERT INTO `sys_user_role` VALUES (1763323873230106626, 1); +INSERT INTO `sys_user_role` VALUES (1763384782623387650, 1); +INSERT INTO `sys_user_role` VALUES (1763386804647014401, 1); +INSERT INTO `sys_user_role` VALUES (1763396269777661953, 1); +INSERT INTO `sys_user_role` VALUES (1763405607485353985, 1); +INSERT INTO `sys_user_role` VALUES (1763432831823425537, 1); +INSERT INTO `sys_user_role` VALUES (1763453676952268802, 1); +INSERT INTO `sys_user_role` VALUES (1763456811204653057, 1); +INSERT INTO `sys_user_role` VALUES (1763461579713064962, 1); +INSERT INTO `sys_user_role` VALUES (1763491204732379137, 1); +INSERT INTO `sys_user_role` VALUES (1763497378051612674, 1); +INSERT INTO `sys_user_role` VALUES (1763559058706096130, 1); +INSERT INTO `sys_user_role` VALUES (1763577018824876033, 1); +INSERT INTO `sys_user_role` VALUES (1763633124087521281, 1); +INSERT INTO `sys_user_role` VALUES (1763886812869775362, 1); +INSERT INTO `sys_user_role` VALUES (1763913997563285506, 1); +INSERT INTO `sys_user_role` VALUES (1764173595432013826, 1); +INSERT INTO `sys_user_role` VALUES (1764261292183998465, 1); +INSERT INTO `sys_user_role` VALUES (1764287995094585346, 1); +INSERT INTO `sys_user_role` VALUES (1764461290695774209, 1); +INSERT INTO `sys_user_role` VALUES (1764474718197993473, 1); +INSERT INTO `sys_user_role` VALUES (1764482496870305794, 1); +INSERT INTO `sys_user_role` VALUES (1764495637402439682, 1); +INSERT INTO `sys_user_role` VALUES (1764498159743619073, 1); +INSERT INTO `sys_user_role` VALUES (1764498751559913473, 1); +INSERT INTO `sys_user_role` VALUES (1764514945641828354, 1); +INSERT INTO `sys_user_role` VALUES (1764519088087453698, 1); +INSERT INTO `sys_user_role` VALUES (1764520899728986114, 1); +INSERT INTO `sys_user_role` VALUES (1764525084016988161, 1); +INSERT INTO `sys_user_role` VALUES (1764539443405475842, 1); +INSERT INTO `sys_user_role` VALUES (1764564174649249794, 1); +INSERT INTO `sys_user_role` VALUES (1764583176607977474, 1); +INSERT INTO `sys_user_role` VALUES (1764607755468505089, 1); +INSERT INTO `sys_user_role` VALUES (1764634462757920770, 1); +INSERT INTO `sys_user_role` VALUES (1764827973771915265, 1); +INSERT INTO `sys_user_role` VALUES (1764831906313596929, 1); +INSERT INTO `sys_user_role` VALUES (1764857801929715713, 1); +INSERT INTO `sys_user_role` VALUES (1764882243925913602, 1); +INSERT INTO `sys_user_role` VALUES (1764897874259816449, 1); +INSERT INTO `sys_user_role` VALUES (1764945289142677505, 1); +INSERT INTO `sys_user_role` VALUES (1764973230396354562, 1); +INSERT INTO `sys_user_role` VALUES (1765026702110044161, 1); +INSERT INTO `sys_user_role` VALUES (1765029529888829441, 1); +INSERT INTO `sys_user_role` VALUES (1765032464647532546, 1); +INSERT INTO `sys_user_role` VALUES (1765189908342321154, 1); +INSERT INTO `sys_user_role` VALUES (1765214567611838465, 1); +INSERT INTO `sys_user_role` VALUES (1765219002413035521, 1); +INSERT INTO `sys_user_role` VALUES (1765220951434801153, 1); +INSERT INTO `sys_user_role` VALUES (1765248990147325954, 1); +INSERT INTO `sys_user_role` VALUES (1765249652247572481, 1); +INSERT INTO `sys_user_role` VALUES (1765256689840893953, 1); +INSERT INTO `sys_user_role` VALUES (1765258070287003649, 1); +INSERT INTO `sys_user_role` VALUES (1765276219292069890, 1); +INSERT INTO `sys_user_role` VALUES (1765276256986279938, 1); +INSERT INTO `sys_user_role` VALUES (1765288006737539074, 1); +INSERT INTO `sys_user_role` VALUES (1765312970979094529, 1); +INSERT INTO `sys_user_role` VALUES (1765626857976840193, 1); +INSERT INTO `sys_user_role` VALUES (1765662415604236289, 1); +INSERT INTO `sys_user_role` VALUES (1765673187432546306, 1); +INSERT INTO `sys_user_role` VALUES (1765733893087510530, 1); +INSERT INTO `sys_user_role` VALUES (1765927148689326081, 1); +INSERT INTO `sys_user_role` VALUES (1765946481549279233, 1); +INSERT INTO `sys_user_role` VALUES (1765987575418880002, 1); +INSERT INTO `sys_user_role` VALUES (1765991619675848705, 1); +INSERT INTO `sys_user_role` VALUES (1765997037533822977, 1); +INSERT INTO `sys_user_role` VALUES (1766008273063411714, 1); +INSERT INTO `sys_user_role` VALUES (1766011496348286978, 1); +INSERT INTO `sys_user_role` VALUES (1766017335771561986, 1); +INSERT INTO `sys_user_role` VALUES (1766020112446947329, 1); +INSERT INTO `sys_user_role` VALUES (1766085955713269762, 1); +INSERT INTO `sys_user_role` VALUES (1766102635604639746, 1); +INSERT INTO `sys_user_role` VALUES (1766323008493355009, 1); +INSERT INTO `sys_user_role` VALUES (1766387294112612353, 1); +INSERT INTO `sys_user_role` VALUES (1766842982618136577, 1); +INSERT INTO `sys_user_role` VALUES (1767018925722730497, 1); +INSERT INTO `sys_user_role` VALUES (1767098572703563778, 1); +INSERT INTO `sys_user_role` VALUES (1767193870939488258, 1); +INSERT INTO `sys_user_role` VALUES (1767371461667356673, 1); +INSERT INTO `sys_user_role` VALUES (1767472876167397377, 1); +INSERT INTO `sys_user_role` VALUES (1767484503956684801, 1); +INSERT INTO `sys_user_role` VALUES (1767494435045146626, 1); +INSERT INTO `sys_user_role` VALUES (1767502928200368129, 1); +INSERT INTO `sys_user_role` VALUES (1767790695329333250, 1); +INSERT INTO `sys_user_role` VALUES (1767797421759823874, 1); +INSERT INTO `sys_user_role` VALUES (1767867514107756545, 1); +INSERT INTO `sys_user_role` VALUES (1768123513418842114, 1); +INSERT INTO `sys_user_role` VALUES (1768125846164897794, 1); +INSERT INTO `sys_user_role` VALUES (1768137512021688322, 1); +INSERT INTO `sys_user_role` VALUES (1768172797870768129, 1); +INSERT INTO `sys_user_role` VALUES (1768257272084463617, 1); +INSERT INTO `sys_user_role` VALUES (1768452168263172097, 1); +INSERT INTO `sys_user_role` VALUES (1768487959811096578, 1); +INSERT INTO `sys_user_role` VALUES (1768522172358754306, 1); +INSERT INTO `sys_user_role` VALUES (1768523379651411969, 1); +INSERT INTO `sys_user_role` VALUES (1768528826072596482, 1); +INSERT INTO `sys_user_role` VALUES (1768554562896560130, 1); +INSERT INTO `sys_user_role` VALUES (1768560191165988866, 1); +INSERT INTO `sys_user_role` VALUES (1768560307197214722, 1); +INSERT INTO `sys_user_role` VALUES (1768561334289989633, 1); +INSERT INTO `sys_user_role` VALUES (1768565063735083009, 1); +INSERT INTO `sys_user_role` VALUES (1768570261782167553, 1); +INSERT INTO `sys_user_role` VALUES (1768598711431626753, 1); +INSERT INTO `sys_user_role` VALUES (1768635967806668802, 1); +INSERT INTO `sys_user_role` VALUES (1768887604487946241, 1); +INSERT INTO `sys_user_role` VALUES (1768911351987077122, 1); +INSERT INTO `sys_user_role` VALUES (1769186172289449986, 1); +INSERT INTO `sys_user_role` VALUES (1769408371134857218, 1); +INSERT INTO `sys_user_role` VALUES (1769520576635371521, 1); +INSERT INTO `sys_user_role` VALUES (1769561862704758786, 1); +INSERT INTO `sys_user_role` VALUES (1769569234722521089, 1); +INSERT INTO `sys_user_role` VALUES (1769607528399273986, 1); +INSERT INTO `sys_user_role` VALUES (1769617177890553857, 1); +INSERT INTO `sys_user_role` VALUES (1769663440459694082, 1); +INSERT INTO `sys_user_role` VALUES (1769908456541233154, 1); +INSERT INTO `sys_user_role` VALUES (1769957357877043201, 1); +INSERT INTO `sys_user_role` VALUES (1770021611783168002, 1); +INSERT INTO `sys_user_role` VALUES (1770063295095087106, 1); +INSERT INTO `sys_user_role` VALUES (1770063700436819970, 1); +INSERT INTO `sys_user_role` VALUES (1770281104395837442, 1); +INSERT INTO `sys_user_role` VALUES (1770288338521661441, 1); +INSERT INTO `sys_user_role` VALUES (1770322814056333313, 1); +INSERT INTO `sys_user_role` VALUES (1770338641849679874, 1); +INSERT INTO `sys_user_role` VALUES (1770351581952802817, 1); +INSERT INTO `sys_user_role` VALUES (1770357305466486786, 1); +INSERT INTO `sys_user_role` VALUES (1770364755406028802, 1); +INSERT INTO `sys_user_role` VALUES (1770381062524436482, 1); +INSERT INTO `sys_user_role` VALUES (1770470677998534657, 1); +INSERT INTO `sys_user_role` VALUES (1770642413331218434, 1); +INSERT INTO `sys_user_role` VALUES (1770648858382630914, 1); +INSERT INTO `sys_user_role` VALUES (1770715116272680962, 1); +INSERT INTO `sys_user_role` VALUES (1770720646688997377, 1); +INSERT INTO `sys_user_role` VALUES (1770726609303175170, 1); +INSERT INTO `sys_user_role` VALUES (1770757521378181121, 1); +INSERT INTO `sys_user_role` VALUES (1770759021907214338, 1); +INSERT INTO `sys_user_role` VALUES (1771002145573240833, 1); +INSERT INTO `sys_user_role` VALUES (1771019340902629377, 1); +INSERT INTO `sys_user_role` VALUES (1771085212270788610, 1); +INSERT INTO `sys_user_role` VALUES (1771091102206066689, 1); +INSERT INTO `sys_user_role` VALUES (1771105696307806210, 1); +INSERT INTO `sys_user_role` VALUES (1771529088861274114, 1); +INSERT INTO `sys_user_role` VALUES (1772148936234565634, 1); +INSERT INTO `sys_user_role` VALUES (1772170742823714818, 1); +INSERT INTO `sys_user_role` VALUES (1772173596070313986, 1); +INSERT INTO `sys_user_role` VALUES (1772181791232819201, 1); +INSERT INTO `sys_user_role` VALUES (1772807697592832001, 1); +INSERT INTO `sys_user_role` VALUES (1772821509767254018, 1); +INSERT INTO `sys_user_role` VALUES (1772947270113251330, 1); +INSERT INTO `sys_user_role` VALUES (1773149840576434178, 1); +INSERT INTO `sys_user_role` VALUES (1773180693536919554, 1); +INSERT INTO `sys_user_role` VALUES (1773192472325345282, 1); +INSERT INTO `sys_user_role` VALUES (1773200350612377601, 1); +INSERT INTO `sys_user_role` VALUES (1773307685607395329, 1); +INSERT INTO `sys_user_role` VALUES (1773529379840282625, 1); +INSERT INTO `sys_user_role` VALUES (1773543535003914241, 1); +INSERT INTO `sys_user_role` VALUES (1773615949826052097, 1); +INSERT INTO `sys_user_role` VALUES (1773714968015278082, 1); +INSERT INTO `sys_user_role` VALUES (1773741523022123010, 1); +INSERT INTO `sys_user_role` VALUES (1773774290929848321, 1); +INSERT INTO `sys_user_role` VALUES (1773969452180258818, 1); +INSERT INTO `sys_user_role` VALUES (1774094144111198210, 1); +INSERT INTO `sys_user_role` VALUES (1774326191970926594, 1); +INSERT INTO `sys_user_role` VALUES (1774595110106685441, 1); +INSERT INTO `sys_user_role` VALUES (1774603290157113346, 1); +INSERT INTO `sys_user_role` VALUES (1774671916088287233, 1); +INSERT INTO `sys_user_role` VALUES (1774712059876728833, 1); +INSERT INTO `sys_user_role` VALUES (1775005868787359746, 1); +INSERT INTO `sys_user_role` VALUES (1775039514470637569, 1); +INSERT INTO `sys_user_role` VALUES (1775046202846208002, 1); +INSERT INTO `sys_user_role` VALUES (1775055115012399106, 1); +INSERT INTO `sys_user_role` VALUES (1775058985780371458, 1); +INSERT INTO `sys_user_role` VALUES (1775066829695082497, 1); +INSERT INTO `sys_user_role` VALUES (1775078808497283074, 1); +INSERT INTO `sys_user_role` VALUES (1775109977754427393, 1); +INSERT INTO `sys_user_role` VALUES (1775109977771204609, 1); +INSERT INTO `sys_user_role` VALUES (1775192704981786626, 1); +INSERT INTO `sys_user_role` VALUES (1775421589681987586, 1); +INSERT INTO `sys_user_role` VALUES (1776124571507613697, 1); +INSERT INTO `sys_user_role` VALUES (1776550027549597698, 1); +INSERT INTO `sys_user_role` VALUES (1776815081159254018, 1); +INSERT INTO `sys_user_role` VALUES (1776827459129171969, 1); +INSERT INTO `sys_user_role` VALUES (1776861348769947650, 1); +INSERT INTO `sys_user_role` VALUES (1776864185373548546, 1); +INSERT INTO `sys_user_role` VALUES (1776871215274516482, 1); +INSERT INTO `sys_user_role` VALUES (1776872376396275714, 1); +INSERT INTO `sys_user_role` VALUES (1776889562355589122, 1); +INSERT INTO `sys_user_role` VALUES (1777118704363757570, 1); +INSERT INTO `sys_user_role` VALUES (1777126438664527874, 1); +INSERT INTO `sys_user_role` VALUES (1777157190659727362, 1); +INSERT INTO `sys_user_role` VALUES (1777217669537062914, 1); +INSERT INTO `sys_user_role` VALUES (1777220647320936449, 1); +INSERT INTO `sys_user_role` VALUES (1777252116550508545, 1); +INSERT INTO `sys_user_role` VALUES (1777260896986193921, 1); +INSERT INTO `sys_user_role` VALUES (1777296499484254210, 1); +INSERT INTO `sys_user_role` VALUES (1777301747972038657, 1); +INSERT INTO `sys_user_role` VALUES (1777363539016409089, 1); +INSERT INTO `sys_user_role` VALUES (1777483372982820866, 1); +INSERT INTO `sys_user_role` VALUES (1777537906459402242, 1); +INSERT INTO `sys_user_role` VALUES (1777610641428570114, 1); +INSERT INTO `sys_user_role` VALUES (1777613556604067842, 1); +INSERT INTO `sys_user_role` VALUES (1777718773123244034, 1); +INSERT INTO `sys_user_role` VALUES (1777743939492503554, 1); +INSERT INTO `sys_user_role` VALUES (1777887539056467969, 1); +INSERT INTO `sys_user_role` VALUES (1777887799262699521, 1); +INSERT INTO `sys_user_role` VALUES (1777890253115088897, 1); +INSERT INTO `sys_user_role` VALUES (1777909423068274689, 1); +INSERT INTO `sys_user_role` VALUES (1777930481544585218, 1); +INSERT INTO `sys_user_role` VALUES (1777954050559303681, 1); +INSERT INTO `sys_user_role` VALUES (1778078614597525506, 1); +INSERT INTO `sys_user_role` VALUES (1778307871026307073, 1); +INSERT INTO `sys_user_role` VALUES (1778341191034462209, 1); +INSERT INTO `sys_user_role` VALUES (1778352526686281729, 1); +INSERT INTO `sys_user_role` VALUES (1778591039688138754, 1); +INSERT INTO `sys_user_role` VALUES (1778625241280274433, 1); +INSERT INTO `sys_user_role` VALUES (1778645603636338689, 1); +INSERT INTO `sys_user_role` VALUES (1779329016437530626, 1); +INSERT INTO `sys_user_role` VALUES (1779509451201306625, 1); +INSERT INTO `sys_user_role` VALUES (1781359789389049858, 1); +INSERT INTO `sys_user_role` VALUES (1781463900025450497, 1); +INSERT INTO `sys_user_role` VALUES (1781519961809940482, 1); +INSERT INTO `sys_user_role` VALUES (1781570458679963650, 1); +INSERT INTO `sys_user_role` VALUES (1781679536911609858, 1); +INSERT INTO `sys_user_role` VALUES (1781680345497923586, 1); +INSERT INTO `sys_user_role` VALUES (1781938051479711745, 1); +INSERT INTO `sys_user_role` VALUES (1781979644345659393, 1); +INSERT INTO `sys_user_role` VALUES (1781982608724537345, 1); +INSERT INTO `sys_user_role` VALUES (1782339521316294658, 1); +INSERT INTO `sys_user_role` VALUES (1782584811885596674, 1); +INSERT INTO `sys_user_role` VALUES (1782597966938411009, 1); +INSERT INTO `sys_user_role` VALUES (1782598345608564738, 1); +INSERT INTO `sys_user_role` VALUES (1782599696132509698, 1); +INSERT INTO `sys_user_role` VALUES (1782655923667505153, 1); +INSERT INTO `sys_user_role` VALUES (1782658558470557698, 1); +INSERT INTO `sys_user_role` VALUES (1782697212870037505, 1); +INSERT INTO `sys_user_role` VALUES (1782711689380270082, 1); +INSERT INTO `sys_user_role` VALUES (1782733890905083906, 1); +INSERT INTO `sys_user_role` VALUES (1782734018948796418, 1); +INSERT INTO `sys_user_role` VALUES (1782741134992379906, 1); +INSERT INTO `sys_user_role` VALUES (1782926062560382978, 1); +INSERT INTO `sys_user_role` VALUES (1782941277477834753, 1); +INSERT INTO `sys_user_role` VALUES (1782982532157050881, 1); +INSERT INTO `sys_user_role` VALUES (1783068876598317057, 1); +INSERT INTO `sys_user_role` VALUES (1783086777506107393, 1); +INSERT INTO `sys_user_role` VALUES (1783144268357079041, 1); +INSERT INTO `sys_user_role` VALUES (1783297415947915265, 1); +INSERT INTO `sys_user_role` VALUES (1783310569679523841, 1); +INSERT INTO `sys_user_role` VALUES (1783326930816372738, 1); +INSERT INTO `sys_user_role` VALUES (1783358421143293953, 1); +INSERT INTO `sys_user_role` VALUES (1783421941125910530, 1); +INSERT INTO `sys_user_role` VALUES (1783439451980206081, 1); +INSERT INTO `sys_user_role` VALUES (1783471940098494466, 1); +INSERT INTO `sys_user_role` VALUES (1783777388311777281, 1); +INSERT INTO `sys_user_role` VALUES (1783796572785643521, 1); +INSERT INTO `sys_user_role` VALUES (1783877442208960514, 1); +INSERT INTO `sys_user_role` VALUES (1784199358216048642, 1); +INSERT INTO `sys_user_role` VALUES (1784389326918029313, 1); +INSERT INTO `sys_user_role` VALUES (1784400528377286657, 1); +INSERT INTO `sys_user_role` VALUES (1784435756558880770, 1); +INSERT INTO `sys_user_role` VALUES (1784457537797656577, 1); +INSERT INTO `sys_user_role` VALUES (1784521057603538945, 1); +INSERT INTO `sys_user_role` VALUES (1784522252246724609, 1); +INSERT INTO `sys_user_role` VALUES (1784548227567202306, 1); +INSERT INTO `sys_user_role` VALUES (1784569508068995073, 1); +INSERT INTO `sys_user_role` VALUES (1784777389905162242, 1); +INSERT INTO `sys_user_role` VALUES (1784783910114308097, 1); +INSERT INTO `sys_user_role` VALUES (1784821184902344705, 1); +INSERT INTO `sys_user_role` VALUES (1784838825360633858, 1); +INSERT INTO `sys_user_role` VALUES (1784870260805087233, 1); +INSERT INTO `sys_user_role` VALUES (1784910451020279810, 1); +INSERT INTO `sys_user_role` VALUES (1785130539233193985, 1); +INSERT INTO `sys_user_role` VALUES (1785240710601125890, 1); +INSERT INTO `sys_user_role` VALUES (1785360485289439233, 1); +INSERT INTO `sys_user_role` VALUES (1785588726424023041, 1); +INSERT INTO `sys_user_role` VALUES (1785975035152019458, 1); +INSERT INTO `sys_user_role` VALUES (1786448824117735425, 1); +INSERT INTO `sys_user_role` VALUES (1787036511853850625, 1); +INSERT INTO `sys_user_role` VALUES (1787040098730356738, 1); +INSERT INTO `sys_user_role` VALUES (1787442869522636802, 1); +INSERT INTO `sys_user_role` VALUES (1787802087576530946, 1); +INSERT INTO `sys_user_role` VALUES (1787878100067119105, 1); +INSERT INTO `sys_user_role` VALUES (1788016335816716290, 1); +INSERT INTO `sys_user_role` VALUES (1788135951385718786, 1); +INSERT INTO `sys_user_role` VALUES (1788136924611047425, 1); +INSERT INTO `sys_user_role` VALUES (1788564791958401026, 1); +INSERT INTO `sys_user_role` VALUES (1788861563763126273, 1); +INSERT INTO `sys_user_role` VALUES (1789104577664217090, 1); +INSERT INTO `sys_user_role` VALUES (1789215891946434561, 1); +INSERT INTO `sys_user_role` VALUES (1789891068120231937, 1); +INSERT INTO `sys_user_role` VALUES (1789916787885961218, 1); +INSERT INTO `sys_user_role` VALUES (1790285085844664322, 1); +INSERT INTO `sys_user_role` VALUES (1790395963663413250, 1); +INSERT INTO `sys_user_role` VALUES (1790626495441698817, 1); +INSERT INTO `sys_user_role` VALUES (1790733204311015425, 1); +INSERT INTO `sys_user_role` VALUES (1790747738857832449, 1); +INSERT INTO `sys_user_role` VALUES (1790893072141549570, 1); +INSERT INTO `sys_user_role` VALUES (1790953693902045186, 1); +INSERT INTO `sys_user_role` VALUES (1790986267617689601, 1); +INSERT INTO `sys_user_role` VALUES (1791058271444172801, 1); +INSERT INTO `sys_user_role` VALUES (1791123542645178370, 1); +INSERT INTO `sys_user_role` VALUES (1791170948304764929, 1); +INSERT INTO `sys_user_role` VALUES (1791173160204533762, 1); +INSERT INTO `sys_user_role` VALUES (1791181681805524994, 1); +INSERT INTO `sys_user_role` VALUES (1791184448041287681, 1); +INSERT INTO `sys_user_role` VALUES (1791281872491544578, 1); +INSERT INTO `sys_user_role` VALUES (1791281970680201217, 1); +INSERT INTO `sys_user_role` VALUES (1791283037744693249, 1); +INSERT INTO `sys_user_role` VALUES (1791285337913589762, 1); +INSERT INTO `sys_user_role` VALUES (1791289816255856641, 1); +INSERT INTO `sys_user_role` VALUES (1791296357612683266, 1); +INSERT INTO `sys_user_role` VALUES (1791299213191315457, 1); +INSERT INTO `sys_user_role` VALUES (1791308308178829314, 1); +INSERT INTO `sys_user_role` VALUES (1791318977032781826, 1); +INSERT INTO `sys_user_role` VALUES (1791371260403687425, 1); +INSERT INTO `sys_user_role` VALUES (1791387421707116546, 1); +INSERT INTO `sys_user_role` VALUES (1791447204858470402, 1); +INSERT INTO `sys_user_role` VALUES (1791729117863124993, 1); +INSERT INTO `sys_user_role` VALUES (1793165965818912770, 1); +INSERT INTO `sys_user_role` VALUES (1793568337082740737, 1); +INSERT INTO `sys_user_role` VALUES (1794560044937154561, 1); +INSERT INTO `sys_user_role` VALUES (1794749939555143681, 1); + +-- ---------------------------- +-- Table structure for wx_rob_config +-- ---------------------------- +DROP TABLE IF EXISTS `wx_rob_config`; +CREATE TABLE `wx_rob_config` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '所属用户', + `bot_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '机器人名称', + `unique_key` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '机器唯一码', + `default_friend` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '\0' COMMENT '默认好友回复开关', + `default_group` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '\0' COMMENT '默认群回复开关', + `enable` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '机器人状态 0正常 1启用', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `udx_wx_rob_config_uniquekey`(`unique_key`) USING BTREE, + UNIQUE INDEX `udx_wx_name`(`bot_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1791353611728023554 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信机器人配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of wx_rob_config +-- ---------------------------- + +-- ---------------------------- +-- Table structure for wx_rob_keyword +-- ---------------------------- +DROP TABLE IF EXISTS `wx_rob_keyword`; +CREATE TABLE `wx_rob_keyword` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `unique_key` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '机器唯一码', + `key_data` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '关键词', + `value_data` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '回复内容', + `type_data` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '回复类型', + `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '目标昵称', + `to_group` bit(1) NOT NULL DEFAULT b'1' COMMENT '群1好友0', + `enable` bit(1) NOT NULL DEFAULT b'1' COMMENT '启用1禁用0', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_wx_rob_keyword_unikey`(`unique_key`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信关键词' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of wx_rob_keyword +-- ---------------------------- SET FOREIGN_KEY_CHECKS = 1; diff --git a/script/sql/ruoyi-ai.sql b/script/sql/ruoyi-ai.sql new file mode 100644 index 00000000..645be400 --- /dev/null +++ b/script/sql/ruoyi-ai.sql @@ -0,0 +1,2550 @@ +/* + Navicat MySQL Data Transfer + + Source Server : ruoyi-ai + Source Server Type : MySQL + Source Server Version : 50740 + Source Host : 127.0.0.1:3306 + Source Schema : ruoyi-ai + + Target Server Type : MySQL + Target Server Version : 50740 + File Encoding : 65001 + + Date: 27/12/2024 23:51:51 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for chat_audio_role +-- ---------------------------- +DROP TABLE IF EXISTS `chat_audio_role`; +CREATE TABLE `chat_audio_role` ( + `id` bigint(20) NOT NULL COMMENT 'id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '角色描述', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `voice_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色id', + `file_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '音频地址', + `create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `voice_id`(`create_by`, `voice_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配音角色' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_audio_role +-- ---------------------------- + +-- ---------------------------- +-- Table structure for chat_config +-- ---------------------------- +DROP TABLE IF EXISTS `chat_config`; +CREATE TABLE `chat_config` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `category` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置类型', + `config_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置名称', + `config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '配置值', + `config_dict` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '说明', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `version` int(11) NULL DEFAULT NULL COMMENT '版本', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新IP', + `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `unique_category_key`(`category`, `config_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1818270017966837762 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '配置信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_config +-- ---------------------------- +INSERT INTO `chat_config` VALUES (1779450794448789505, 'chat', 'apiKey', 'sk-xx', 'API 密钥', 103, '2024-04-14 18:05:05', '1', '1', '2024-04-23 23:56:54', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779450794872414210, 'chat', 'apiHost', 'https://api.pandarobot.chat/', 'API 地址', 103, '2024-04-14 18:05:05', '1', '1', '2024-04-23 23:56:54', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497340548784129, 'pay', 'pid', '1000', '商户PID', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497340938854401, 'pay', 'key', 'xx', '商户密钥', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341135986690, 'pay', 'payUrl', 'https://pay.pandarobot.chat/mapi.php', '支付地址', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341400227842, 'pay', 'notify_url', 'https://www.pandarobot.chat/pay/notifyUrl', '回调地址', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779497341588971522, 'pay', 'return_url', 'https://www.pandarobot.chat/pay/returnUrl', '跳转通知', 103, '2024-04-14 21:10:02', '1', '1', '2024-04-28 17:46:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580331835394, 'mail', 'host', 'smtp.163.com', '主机地址', 103, '2024-04-14 22:14:34', '1', '1', '2024-07-17 17:28:51', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580658991106, 'mail', 'port', '465', '主机端口', 103, '2024-04-14 22:14:34', '1', '1', '2024-07-17 17:28:51', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513580919037953, 'mail', 'from', 'ageerle@163.com', '发送方', 103, '2024-04-14 22:14:34', '1', '1', '2024-07-17 17:28:51', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513581107781634, 'mail', 'user', 'ageerle@163.com', '用户名', 103, '2024-04-14 22:14:34', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779513581309108225, 'mail', 'pass', 'xx', '邮箱授权码', 103, '2024-04-14 22:14:34', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779726450625687553, 'mj', 'apiKey', 'sk-xx', 'API 密钥', 103, '2024-04-15 12:20:26', '1', '1', '2024-04-23 23:56:58', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1779726451036729346, 'mj', 'apiHost', 'https://api.pandarobot.chat/', 'API 地址', 103, '2024-04-15 12:20:26', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331509679181825, 'mj', 'imagine', '0.3', '文生图', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331509939228674, 'mj', 'blend', '0.3', '图生图', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510199275522, 'mj', 'describe', '0.1', '图生文', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510392213505, 'mj', 'change', '0.3', '变化价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510652260353, 'mj', 'upsample', '0.1', '放大价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331510845198338, 'mj', 'inpaint', '0.3', '局部重绘', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331511117828098, 'mj', 'faceSwapping', '0.3', '换脸价格', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782331511306571778, 'mj', 'shorten', '0.1', '提示词分析', 103, '2024-04-22 16:52:01', '1', '1', '2024-04-23 23:56:59', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1782766864937119746, 'mail', 'amount', '1', '用户注册额度', 103, '2024-04-23 21:41:57', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1784166479104135169, 'audio', 'apiKey', 'sk-695ed4a7950a17ff7edbf1aad439c779', 'API 密钥', 103, '2024-04-27 18:23:31', '1', '1', '2024-04-27 18:24:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1784166479615840258, 'audio', 'apiHost', 'https://v1.reecho.cn/', 'API 地址', 103, '2024-04-27 18:23:32', '1', '1', '2024-04-27 18:24:31', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372188569602, 'review', 'enabled', 'false', '文本审核', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372637360129, 'review', 'apiKey', 'mchvcc4Yx5X6jwEF38WCWVHI', 'apiKey', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1786058372897406977, 'review', 'secretKey', 'CO2vtzaas58lsiTn9bMEUK9Xi1EyKXdR', 'secretKey', 103, '2024-05-02 23:41:14', '1', '1', '2024-05-03 01:18:50', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069350789324801, 'weixin', 'appId', 'xx', '应用ID', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069351246503938, 'weixin', 'appSecret', 'xx', '应用密钥', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792069351246503939, 'weixin', 'mchId', 'xx', '商户ID', 103, '2024-05-19 13:46:43', '1', '1', '2024-05-19 22:34:39', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792183360796790785, 'weixin', 'notifyUrl', 'https://www.pandarobot.chat/pay/notify/wxOrder', '回调地址', 103, '2024-05-19 21:19:45', '1', '1', '2024-05-19 22:34:40', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792183361065226241, 'weixin', 'enabled', 'true', '开启支付', 103, '2024-05-19 21:19:45', '1', '1', '2024-05-19 22:34:40', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207511704100866, 'sys', 'name', '熊猫助手', '网站名称', 103, '2024-05-19 22:55:43', '1', '1', '2024-08-11 12:03:04', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512089976834, 'sys', 'logoImage', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/05/19/4c106628754b4bd882a4c002eaa317f5.jpg', '网站logo', 103, '2024-05-19 22:55:43', '1', '1', '2024-08-11 12:03:04', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512412938241, 'sys', 'copyright', 'Copyright © 2024', '版权信息', 103, '2024-05-19 22:55:43', '1', '1', '2024-08-11 12:03:04', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512740093954, 'sys', 'customImage', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/05/19/2faba7a5fa174d7c8d573ce3f031ec51.jpg', '客服二维码', 103, '2024-05-19 22:55:43', '1', '1', '2024-08-11 12:03:04', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1792207512740093955, 'sys', 'activate', 'true', '系统激活状态', 103, '2024-05-19 22:55:43', '1', '1', '2024-06-04 04:26:14', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1795022320576143362, 'sys', 'authcode', '1716475338010', '证书编号', 103, '2024-05-27 17:20:46', NULL, NULL, '2024-05-27 17:20:46', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1795022320576143363, 'stripe', 'success', 'http://xx:6039/success', '成功回调', 103, '2024-05-27 17:20:46', NULL, '1', '2024-08-11 12:02:41', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1795022320576143364, 'stripe', 'cancel', 'http://xx:6039/cancel', '取消回调', 103, '2024-05-27 17:20:46', NULL, '1', '2024-08-11 12:02:41', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1795022320576143365, 'stripe', 'key', 'xx', '支付密钥', 103, '2024-05-27 17:20:46', NULL, '1', '2024-08-11 12:02:42', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1795022320576143366, 'stripe', 'secret', 'xx', '回调密钥', 103, '2024-05-27 17:20:46', NULL, '1', '2024-08-11 12:02:42', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1811317731650797570, 'mail', 'free', '3', '免费对话次数', 103, '2024-07-11 16:32:55', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1811317732300914689, 'mail', 'mailModel', '

    您此次的验证码为:{code},有效期为30分钟,请尽快填写!


    ', '邮箱模板', 103, '2024-07-11 16:32:55', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1813506141979254785, 'mail', 'mailTitle', '【熊猫助手】验证码', '邮箱标题', 103, '2024-07-17 17:28:52', '1', '1', '2024-07-17 17:28:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1818270017648070657, 'stripe', 'prompt', 'This system is for demonstration only and does not currently support this feature!', '提示语', 103, '2024-07-30 20:58:49', '1', '1', '2024-08-11 12:02:42', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_config` VALUES (1818270017966837761, 'stripe', 'enabled', 'false', '开启支付', 103, '2024-07-30 20:58:49', '1', '1', '2024-08-11 12:02:42', NULL, NULL, '0', NULL, 0); + +-- ---------------------------- +-- Table structure for chat_gpts +-- ---------------------------- +DROP TABLE IF EXISTS `chat_gpts`; +CREATE TABLE `chat_gpts` ( + `id` bigint(20) NOT NULL COMMENT 'id', + `gid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'gpts应用id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'gpts应用名称', + `logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'gpts图标', + `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'gpts描述', + `author_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '作者id', + `author_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '作者名称', + `use_cnt` int(11) NULL DEFAULT 0 COMMENT '点赞', + `bad` int(11) NULL DEFAULT 0 COMMENT '差评', + `type` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + + + + + + + + + + -+------------------------- +-- Records of chat_gpts +-- ---------------------------- +INSERT INTO `chat_gpts` VALUES (1810602934286237698, 'gpt-4-gizmo-g-GmEINlwwR', 'Language Teacher', 'https://gpt-logo.gptshunt.com/MzMzMzUyMzMyMjE4MmE1OTJm.png', 'Ms. Smith, the AI-powered Language Teacher, is a revolutionary GPT-based bot that offers personalized language learning experiences in over 20 languages, including Spanish, German, French, English, Chinese, Korean, Japanese, and more', NULL, NULL, 0, 0, NULL, 103, '2024-07-09 17:12:34', '1', '1', '2024-07-12 15:40:13', 'Ms. Smith, the AI-powered Language Teacher, is a revolutionary GPT-based bot that offers personalized language learning experiences in over 20 languages, including Spanish, German, French, English, Chinese, Korean, Japanese, and more\n', NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811668058426515458, 'gpt-4-gizmo-g-pmuQfob8d', 'image generator', 'https://gpt-logo.gptshunt.com/MTEwYzE0MzAwNzBlMDM1OTA1.png', 'Image Generator is a cutting-edge AI bot that leverages the power of GPT technology to generate captivating images from text descriptions. This versatile tool empowers users to create, refine, and personalize visual content ', NULL, NULL, 0, 0, NULL, 103, '2024-07-12 15:44:59', '1', '1', '2024-07-12 15:44:59', '', NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811668415990931458, 'gpt-4-gizmo-g-B3hgivKK9', 'Write For Me', 'https://gpt-logo.gptshunt.com/MjM1MjA5MDYwODE3MmEyYTU4.png', 'Write For Me is an innovative AI-powered bot that harnesses the capabilities of GPT technology to generate tailored, engaging content with a focus on quality, relevance, and precise word count.', NULL, NULL, 0, 0, NULL, 103, '2024-07-12 15:46:24', '1', '1', '2024-07-12 15:46:24', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811670922074988545, 'gpt-4-gizmo-g-bo0FiWLY7', 'Consensus ', 'https://gpt-logo.gptshunt.com/MDMwZTUxMjcwODM2MmQzODU2.png', 'an AI-powered research assistant, revolutionizes the way you access and utilize academic knowledge. By leveraging advanced GPT technology, this bot allows users to search through a vast database of over 200 million academic papers', NULL, NULL, 0, 0, NULL, 103, '2024-07-12 15:56:22', '1', '1', '2024-07-12 15:56:22', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811815442062188545, 'gpt-4-gizmo-g-kZ0eYXlJe', 'Scholar GPT', 'https://gpt-logo.gptshunt.com/MGEzYjUxMDQzODM5MGQyYjA0.png', 'is an innovative AI-powered bot that revolutionizes the way researchers access and analyze academic resources. provides seamless access to over 200 million resources from renowned databases such as Google Scholar, PubMed, JSTOR, and Arxiv.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:30:38', '1', '1', '2024-07-13 01:30:38', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811817323840872450, 'gpt-4-gizmo-g-alKfVrz9K', 'Canva', 'https://gpt-logo.gptshunt.com/MDAwZDJhMDczNzEzMWI1ODJh.png', 'graphic design bot, revolutionizes the way you create stunning visuals., Canva empowers users to effortlessly design a wide range of materials, from captivating social media posts and eye-catching logos to professional presentations and more.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:38:07', '1', '1', '2024-07-13 01:38:07', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811817605668741121, 'gpt-4-gizmo-g-2DQzU5UZl', 'Code Copilot', 'https://gpt-logo.gptshunt.com/NTMyNTMwMWIzNDU0MzQzYjBk.png', 'is a cutting-edge AI-powered bot that revolutionizes the way developers write code. By leveraging advanced GPT technology, this intelligent assistant acts as a virtual 10x programmer, providing real-time suggestions, code completion, and error detection.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:39:14', '1', '1', '2024-07-13 01:39:14', NULL, NULL, '2', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811818375105421313, 'gpt-4-gizmo-g-gFt1ghYJl', 'Logo Creator', 'https://gpt-logo.gptshunt.com/MDYyNzE1NTAwNjA5MzgyYjBk.png', 'is an AI-powered bot that leverages advanced GPT technology to generate professional logo designs and app icons tailored to your specific needs.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:42:17', '1', '1', '2024-07-13 01:42:17', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811819494154117121, 'gpt-4-gizmo-g-k3IqoCe1l', 'Code Generator', 'https://gpt-logo.gptshunt.com/MGE1MjI4MTAwZTIyMDQ1MDBk.png', 'is an advanced AI-powered bot that leverages cutting-edge GPT technology to revolutionize the way developers work with code. This intelligent assistant offers a comprehensive suite of features designed to streamline', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:46:44', '1', '1', '2024-07-13 01:46:44', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811822597507063810, 'gpt-4-gizmo-g-a9JivI0y2', 'Consistent Character GPT', 'https://gpt-logo.gptshunt.com/MDA1ODJiMDgxNzI4NTExODUz.png', 'Consistent Character GPT is a cutting-edge AI bot that leverages the power of GPT technology to generate high-quality character designs with unparalleled speed and consistency.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 01:59:04', '1', '1', '2024-07-13 01:59:04', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811822865149796353, 'gpt-4-gizmo-g-ZctQCI6MG', 'Photo Multiverse', 'https://gpt-logo.gptshunt.com/M2IwMjE1MzAyMjI4NTcyYzI2.png', 'is a cutting-edge AI bot that leverages advanced GPT technology to transform your photos into captivating AI personas.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 02:00:08', '1', '1', '2024-07-13 02:00:08', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811823552902406145, 'gpt-4-gizmo-g-pCq5xaCri', 'LOGO', 'https://gpt-logo.gptshunt.com/MTEyMjEwNTQxOTAwMjIxMzA4.jpg', 'is an AI-powered bot, is a seasoned expert in brand logo design, boasting an impressive 20 years of experience in the field. Leveraging the power of GPT technology, LOGO has been trained on a vast array of designer materials to deliver top-notch results.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 02:02:52', '1', '1', '2024-07-13 02:02:52', NULL, NULL, '0', NULL, 0); +INSERT INTO `chat_gpts` VALUES (1811823857308213249, 'gpt-4-gizmo-g-I9f8LxD5P', 'Video Maker', 'https://gpt-logo.gptshunt.com/Mjg1ODA3NTkyZDE5MjU1NDMx.png', 'is a cutting-edge AI-powered bot that leverages advanced GPT technology to create captivating animated videos with ease.', NULL, NULL, 0, 0, NULL, 103, '2024-07-13 02:04:04', '1', '1', '2024-07-13 02:04:04', NULL, NULL, '0', NULL, 0); + +-- ---------------------------- +-- Table structure for chat_message +-- ---------------------------- +DROP TABLE IF EXISTS `chat_message`; +CREATE TABLE `chat_message` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户id', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', + `deduct_cost` double(20, 2) NULL DEFAULT 0.00 COMMENT '扣除金额\r\n\r\n', + `total_tokens` int(20) NULL DEFAULT 0 COMMENT '累计 Tokens', + `model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_message +-- ---------------------------- + +-- ---------------------------- +-- Table structure for chat_model +-- ---------------------------- +DROP TABLE IF EXISTS `chat_model`; +CREATE TABLE `chat_model` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + `model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型描述', + `model_price` double NULL DEFAULT NULL COMMENT '模型价格', + `model_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计费类型', + `model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否显示', + `system_prompt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统提示词', + `api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求地址', + `api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密钥', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天模型' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_model +-- ---------------------------- +INSERT INTO `chat_model` VALUES (1781709495515783171, 'gpt-4-all', 'gpt-all', 0.2, '2', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-20 23:40:41', 1, '2024-12-27 22:28:36', 'gpt-all'); +INSERT INTO `chat_model` VALUES (1781715781896646657, 'suno-v3', 'suno-v3', 0.3, '2', '0', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-21 00:05:20', 1, '2024-12-27 22:28:40', 'suno-v3'); +INSERT INTO `chat_model` VALUES (1781728235120791553, 'stable-diffusion', 'stable-diffusion', 0.1, '2', '0', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-21 00:54:49', 1, '2024-12-27 22:28:46', 'stable-diffusion'); +INSERT INTO `chat_model` VALUES (1782734319650418690, 'gpt-4-1106-vision-preview', 'gpt-4-1106-vision-preview', 0.2, '2', '1', '', 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 19:32:38', 1, '2024-12-27 22:28:52', 'gpt-4-1106-vision-preview'); +INSERT INTO `chat_model` VALUES (1782736322308943873, 'dall-e-3', 'dall3', 0.3, '2', '1', '', 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 19:40:36', 1, '2024-12-27 22:29:01', 'dall3'); +INSERT INTO `chat_model` VALUES (1782736729471004673, 'gpt-4-gizmo', 'gpt-4-gizmo', 0.2, '2', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 19:42:13', 1, '2024-12-27 22:29:06', 'gpt-4-gizmo'); +INSERT INTO `chat_model` VALUES (1782792839548735490, 'midjourney', 'midjourney', 0.5, '2', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 23:25:10', 1, '2024-12-27 22:29:11', 'midjourney'); +INSERT INTO `chat_model` VALUES (1782792839548735491, 'suno', 'suno', 0.3, '2', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 23:25:10', 1, '2024-12-27 22:29:15', 'suno'); +INSERT INTO `chat_model` VALUES (1782792839548735492, 'luma', 'luma', 1, '2', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-04-23 23:25:10', 1, '2024-12-27 22:29:19', 'luma'); +INSERT INTO `chat_model` VALUES (1811030708604317697, 'gemini-1.5-pro', 'gemini-1.5-pro', 0.2, '1', '1', '', 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-07-10 21:32:23', 1, '2024-12-27 22:29:24', 'gemini-1.5-pro'); +INSERT INTO `chat_model` VALUES (1813306888443305986, 'claude-3-5-sonnet-20240620', 'claude-3-5-sonnet-20240620', 0.2, '1', '1', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-07-17 04:17:06', 1, '2024-12-27 22:29:28', 'claude-3-5-sonnet-20240620'); +INSERT INTO `chat_model` VALUES (1814227154275082242, 'o1-mini-2024-09-12', 'o1-mini-2024-09-12', 0.01, '1', '0', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-07-19 17:13:55', 1, '2024-12-27 22:29:32', 'o1-mini-2024-09-12'); +INSERT INTO `chat_model` VALUES (1828324413241466881, 'chatgpt-4o-latest', 'chatgpt-4o-latest', 0.05, '1', '0', NULL, 'https://api.pandarobot.chat/', 'xx', 103, 1, '2024-08-27 14:51:23', 1, '2024-12-27 22:29:37', 'chatgpt-4o-latest'); + +-- ---------------------------- +-- Table structure for chat_package_plan +-- ---------------------------- +DROP TABLE IF EXISTS `chat_package_plan`; +CREATE TABLE `chat_package_plan` ( + `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称', + `price` double(10, 2) NOT NULL COMMENT '套餐价格', + `duration` int(11) NOT NULL COMMENT '有效时间', + `plan_detail` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计划详情', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1819934166912442370 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '套餐管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_package_plan +-- ---------------------------- +INSERT INTO `chat_package_plan` VALUES (1787085620534378498, '初级套餐', 4.90, 30, 'o1-mini-2024-09-12', 103, 1, '2024-05-05 19:43:09', 1, '2024-08-27 15:00:35', '3'); +INSERT INTO `chat_package_plan` VALUES (1787085808271425538, '中级套餐', 9.90, 30, 'o1-mini-2024-09-12', 103, 1, '2024-05-05 19:43:54', 1, '2024-08-27 15:01:32', '3'); +INSERT INTO `chat_package_plan` VALUES (1787085903419211778, '高级套餐', 14.90, 30, 'o1-mini-2024-09-12', 103, 1, '2024-05-05 19:44:16', 1, '2024-08-27 15:02:16', '3'); +INSERT INTO `chat_package_plan` VALUES (1819933853652459522, 'Visitor', 0.00, 365, 'o1-mini-2024-09-12', 103, 1, '2024-08-04 11:10:18', 1, '2024-08-27 15:32:01', '1'); +INSERT INTO `chat_package_plan` VALUES (1819934166912442369, 'Free', 0.00, 365, 'o1-mini-2024-09-12', 103, 1, '2024-08-04 11:11:33', 1, '2024-08-27 15:31:51', '2'); + +-- ---------------------------- +-- Table structure for chat_pay_order +-- ---------------------------- +DROP TABLE IF EXISTS `chat_pay_order`; +CREATE TABLE `chat_pay_order` ( + `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `order_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号', + `order_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单名称', + `amount` decimal(10, 2) NOT NULL COMMENT '金额', + `payment_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付状态', + `payment_method` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '支付订单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_pay_order +-- ---------------------------- + +-- ---------------------------- +-- Table structure for chat_rob_config +-- ---------------------------- +DROP TABLE IF EXISTS `chat_rob_config`; +CREATE TABLE `chat_rob_config` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '所属用户', + `bot_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '机器人名称', + `unique_key` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '机器唯一码', + `default_friend` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '\0' COMMENT '默认好友回复开关', + `default_group` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '\0' COMMENT '默认群回复开关', + `enable` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '机器人状态 0正常 1启用', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `udx_wx_rob_config_uniquekey`(`unique_key`) USING BTREE, + UNIQUE INDEX `udx_wx_name`(`bot_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天机器人配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_rob_config +-- ---------------------------- + +-- ---------------------------- +-- Table structure for chat_usage_token +-- ---------------------------- +DROP TABLE IF EXISTS `chat_usage_token`; +CREATE TABLE `chat_usage_token` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户', + `token` int(10) NULL DEFAULT NULL COMMENT '待结算token', + `model_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模型名称', + `total_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '累计使用token', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户token使用详情' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_usage_token +-- ---------------------------- + +-- ---------------------------- +-- Table structure for chat_voucher +-- ---------------------------- +DROP TABLE IF EXISTS `chat_voucher`; +CREATE TABLE `chat_voucher` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '兑换码', + `amount` double(10, 2) NOT NULL COMMENT '兑换金额', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '兑换状态', + `balance_before` double(10, 2) NULL DEFAULT NULL COMMENT '兑换前余额', + `balance_after` double(10, 2) NULL DEFAULT NULL COMMENT '兑换后余额', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户兑换记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of chat_voucher +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint(20) NOT NULL COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- +INSERT INTO `gen_table` VALUES (1661288222902505474, 'sys_notice', '通知公告表', NULL, NULL, 'SysNotice', 'crud', 'org.dromara.system', 'system', 'notice', '通知公告', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223338713089, 'sys_oper_log', '操作日志记录', NULL, NULL, 'SysOperLog', 'crud', 'org.dromara.system', 'system', 'operLog', '操作日志记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223477125122, 'sys_oss', 'OSS对象存储表', NULL, NULL, 'SysOss', 'crud', 'org.dromara.system', 'system', 'oss', 'OSS对象存储', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223586177025, 'sys_oss_config', '对象存储配置表', NULL, NULL, 'SysOssConfig', 'crud', 'org.dromara.system', 'system', 'ossConfig', '对象存储配置', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223728783361, 'sys_post', '岗位信息表', NULL, NULL, 'SysPost', 'crud', 'org.dromara.system', 'system', 'post', '岗位信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223821058050, 'sys_role', '角色信息表', NULL, NULL, 'SysRole', 'crud', 'org.dromara.system', 'system', 'role', '角色信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223925915650, 'sys_user_post', '用户与岗位关联表', NULL, NULL, 'SysUserPost', 'crud', 'org.dromara.system', 'system', 'userPost', '用户与岗位关联', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288223967858689, 'sys_user_role', '用户和角色关联表', NULL, NULL, 'SysUserRole', 'crud', 'org.dromara.system', 'system', 'userRole', '用户和角色关联', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:11', 1, '2023-05-20 18:05:11', NULL); +INSERT INTO `gen_table` VALUES (1661288385096241154, 'sys_config', '参数配置表', NULL, NULL, 'SysConfig', 'crud', 'org.dromara.system', 'system', 'config', '参数配置', 'Lion Li', '0', '/', NULL, 103, 1, '2023-05-20 18:05:10', 1, '2023-05-20 18:05:10', NULL); +INSERT INTO `gen_table` VALUES (1680196323445579778, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'com.xmzs.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); +INSERT INTO `gen_table` VALUES (1680196323521077249, 'sys_file_detail', '文件记录表', NULL, NULL, 'SysFileDetail', 'crud', 'com.xmzs.system', 'system', 'fileDetail', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:40:00', 1, '2023-07-15 20:40:00', NULL); +INSERT INTO `gen_table` VALUES (1680199147407806465, 'sys_file_info', '文件记录表', NULL, NULL, 'SysFileInfo', 'crud', 'com.xmzs.system', 'system', 'fileInfo', '文件记录', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-15 20:53:56', 1, '2023-07-15 20:53:56', NULL); +INSERT INTO `gen_table` VALUES (1680481752850145282, 'sd_model_param', '模型参数信息表', NULL, NULL, 'SdModelParam', 'crud', 'com.xmzs.system', 'system', 'modelParam', '模型参数信息', 'Lion Li', '0', '/', NULL, 103, 1, '2023-07-16 15:18:34', 1, '2023-07-16 15:18:34', NULL); +INSERT INTO `gen_table` VALUES (1740573614897897473, 'payment_orders', '支付订单表', NULL, NULL, 'PaymentOrders', 'crud', 'com.xmzs.system', 'system', 'orders', '支付订单', 'Lion Li', '0', '/', NULL, 103, 1, '2023-12-27 23:04:45', 1, '2023-12-27 23:04:45', NULL); +INSERT INTO `gen_table` VALUES (1775895242171076610, 'sys_model', '系统模型', NULL, NULL, 'SysModel', 'crud', 'com.xmzs.system', 'system', 'model', '系统模型', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-04 22:27:08', 1, '2024-04-04 22:27:08', NULL); +INSERT INTO `gen_table` VALUES (1785390411861803009, 'wx_rob_config', '微信机器人管理', NULL, NULL, 'WxRobConfig', 'crud', 'com.xmzs.system', 'system', 'robConfig', 'robot', 'Lion Li', '0', '/', '{\"treeCode\":null,\"treeName\":null,\"treeParentCode\":null,\"parentMenuId\":null}', 103, 1, '2024-05-01 01:10:04', 1, '2024-05-03 21:00:51', NULL); +INSERT INTO `gen_table` VALUES (1785390413745045505, 'wx_rob_keyword', '', NULL, NULL, 'WxRobKeyword', 'crud', 'com.xmzs.system', 'system', 'robKeyword', '', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-30 23:51:44', 1, '2024-04-30 23:51:44', NULL); +INSERT INTO `gen_table` VALUES (1785390414860730369, 'wx_rob_relation', '', NULL, NULL, 'WxRobRelation', 'crud', 'com.xmzs.system', 'system', 'robRelation', '', 'Lion Li', '0', '/', NULL, 103, 1, '2024-04-30 23:51:44', 1, '2024-04-30 23:51:44', NULL); +INSERT INTO `gen_table` VALUES (1786379560181882881, 'chat_voucher', '用户兑换记录', NULL, NULL, 'ChatVoucher', 'crud', 'com.xmzs.system', 'system', 'voucher', '用户兑换记录', 'Lion Li', '0', '/', NULL, 103, 1, '2024-05-03 20:57:18', 1, '2024-05-03 20:57:18', NULL); +INSERT INTO `gen_table` VALUES (1789155611035381761, 'sys_notice_state', '用户阅读状态表', NULL, NULL, 'SysNoticeState', 'crud', 'com.xmzs.system', 'system', 'noticeState', '用户阅读状态', 'Lion Li', '0', '/', NULL, 103, 1, '2024-05-11 12:48:14', 1, '2024-05-11 12:48:14', NULL); + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint(20) NOT NULL COMMENT '编号', + `table_id` bigint(20) NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int(11) NULL DEFAULT NULL COMMENT '排序', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- +INSERT INTO `gen_table_column` VALUES (1661288223078666241, 1661288222902505474, 'notice_id', '公告ID', 'bigint(20)', 'Long', 'noticeId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223108026369, 1661288222902505474, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223108026370, 1661288222902505474, 'notice_title', '公告标题', 'varchar(50)', 'String', 'noticeTitle', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223108026371, 1661288222902505474, 'notice_type', '公告类型(1通知 2公告)', 'char(1)', 'String', 'noticeType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223108026372, 1661288222902505474, 'notice_content', '公告内容', 'longblob', 'String', 'noticeContent', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'editor', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609282, 1661288222902505474, 'status', '公告状态(0正常 1关闭)', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609283, 1661288222902505474, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609284, 1661288222902505474, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609285, 1661288222902505474, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609286, 1661288222902505474, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609287, 1661288222902505474, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223120609288, 1661288222902505474, 'remark', '备注', 'varchar(255)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223363878913, 1661288223338713089, 'oper_id', '日志主键', 'bigint(20)', 'Long', 'operId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223363878914, 1661288223338713089, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223363878915, 1661288223338713089, 'title', '模块标题', 'varchar(50)', 'String', 'title', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223363878916, 1661288223338713089, 'business_type', '业务类型(0其它 1新增 2修改 3删除)', 'int(2)', 'Integer', 'businessType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627649, 1661288223338713089, 'method', '方法名称', 'varchar(100)', 'String', 'method', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627650, 1661288223338713089, 'request_method', '请求方式', 'varchar(10)', 'String', 'requestMethod', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627651, 1661288223338713089, 'operator_type', '操作类别(0其它 1后台用户 2手机端用户)', 'int(1)', 'Integer', 'operatorType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627652, 1661288223338713089, 'oper_name', '操作人员', 'varchar(50)', 'String', 'operName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627653, 1661288223338713089, 'dept_name', '部门名称', 'varchar(50)', 'String', 'deptName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627654, 1661288223338713089, 'oper_url', '请求URL', 'varchar(255)', 'String', 'operUrl', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627655, 1661288223338713089, 'oper_ip', '主机地址', 'varchar(128)', 'String', 'operIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627656, 1661288223338713089, 'oper_location', '操作地点', 'varchar(255)', 'String', 'operLocation', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627657, 1661288223338713089, 'oper_param', '请求参数', 'varchar(2000)', 'String', 'operParam', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 13, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627658, 1661288223338713089, 'json_result', '返回参数', 'varchar(2000)', 'String', 'jsonResult', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 14, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627659, 1661288223338713089, 'status', '操作状态(0正常 1异常)', 'int(1)', 'Integer', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 15, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627660, 1661288223338713089, 'error_msg', '错误消息', 'varchar(2000)', 'String', 'errorMsg', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 16, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627661, 1661288223338713089, 'oper_time', '操作时间', 'datetime', 'Date', 'operTime', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'datetime', '', 17, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223401627662, 1661288223338713089, 'cost_time', '消耗时间', 'bigint(20)', 'Long', 'costTime', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 18, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290946, 1661288223477125122, 'oss_id', '对象存储主键', 'bigint(20)', 'Long', 'ossId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290947, 1661288223477125122, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290948, 1661288223477125122, 'file_name', '文件名', 'varchar(255)', 'String', 'fileName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290949, 1661288223477125122, 'original_name', '原名', 'varchar(255)', 'String', 'originalName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290950, 1661288223477125122, 'file_suffix', '文件后缀名', 'varchar(10)', 'String', 'fileSuffix', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290951, 1661288223477125122, 'url', 'URL地址', 'varchar(500)', 'String', 'url', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290952, 1661288223477125122, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290953, 1661288223477125122, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290954, 1661288223477125122, 'create_by', '上传人', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290955, 1661288223477125122, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290956, 1661288223477125122, 'update_by', '更新人', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223502290957, 1661288223477125122, 'service', '服务商', 'varchar(20)', 'String', 'service', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342850, 1661288223586177025, 'oss_config_id', '主建', 'bigint(20)', 'Long', 'ossConfigId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342851, 1661288223586177025, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342852, 1661288223586177025, 'config_key', '配置key', 'varchar(20)', 'String', 'configKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342853, 1661288223586177025, 'access_key', 'accessKey', 'varchar(255)', 'String', 'accessKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342854, 1661288223586177025, 'secret_key', '秘钥', 'varchar(255)', 'String', 'secretKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342855, 1661288223586177025, 'bucket_name', '桶名称', 'varchar(255)', 'String', 'bucketName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342856, 1661288223586177025, 'prefix', '前缀', 'varchar(255)', 'String', 'prefix', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342857, 1661288223586177025, 'endpoint', '访问站点', 'varchar(255)', 'String', 'endpoint', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342858, 1661288223586177025, 'domain', '自定义域名', 'varchar(255)', 'String', 'domain', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342859, 1661288223586177025, 'is_https', '是否https(Y=是,N=否)', 'char(1)', 'String', 'isHttps', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223611342860, 1661288223586177025, 'region', '域', 'varchar(255)', 'String', 'region', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285889, 1661288223586177025, 'access_policy', '桶权限类型(0=private 1=public 2=custom)', 'char(1)', 'String', 'accessPolicy', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285890, 1661288223586177025, 'status', '是否默认(0=是,1=否)', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 13, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285891, 1661288223586177025, 'ext1', '扩展字段', 'varchar(255)', 'String', 'ext1', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 14, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285892, 1661288223586177025, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 15, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285893, 1661288223586177025, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 16, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285894, 1661288223586177025, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 17, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285895, 1661288223586177025, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 18, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285896, 1661288223586177025, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 19, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223653285897, 1661288223586177025, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 20, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754881, 1661288223728783361, 'post_id', '岗位ID', 'bigint(20)', 'Long', 'postId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754882, 1661288223728783361, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754883, 1661288223728783361, 'post_code', '岗位编码', 'varchar(64)', 'String', 'postCode', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754884, 1661288223728783361, 'post_name', '岗位名称', 'varchar(50)', 'String', 'postName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754885, 1661288223728783361, 'post_sort', '显示顺序', 'int(4)', 'Integer', 'postSort', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754886, 1661288223728783361, 'status', '状态(0正常 1停用)', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754887, 1661288223728783361, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754888, 1661288223728783361, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754889, 1661288223728783361, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754890, 1661288223728783361, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754891, 1661288223728783361, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223749754892, 1661288223728783361, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223874, 1661288223821058050, 'role_id', '角色ID', 'bigint(20)', 'Long', 'roleId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223875, 1661288223821058050, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223876, 1661288223821058050, 'role_name', '角色名称', 'varchar(30)', 'String', 'roleName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223877, 1661288223821058050, 'role_key', '角色权限字符串', 'varchar(100)', 'String', 'roleKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223878, 1661288223821058050, 'role_sort', '显示顺序', 'int(4)', 'Integer', 'roleSort', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223879, 1661288223821058050, 'data_scope', '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', 'char(1)', 'String', 'dataScope', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223880, 1661288223821058050, 'menu_check_strictly', '菜单树选择项是否关联显示', 'tinyint(1)', 'Integer', 'menuCheckStrictly', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223881, 1661288223821058050, 'dept_check_strictly', '部门树选择项是否关联显示', 'tinyint(1)', 'Integer', 'deptCheckStrictly', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223882, 1661288223821058050, 'status', '角色状态(0正常 1停用)', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 9, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223883, 1661288223821058050, 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223884, 1661288223821058050, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 11, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223885, 1661288223821058050, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 12, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223886, 1661288223821058050, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 13, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223887, 1661288223821058050, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 14, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223888, 1661288223821058050, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 15, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223846223889, 1661288223821058050, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 16, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223951081474, 1661288223925915650, 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223951081475, 1661288223925915650, 'post_id', '岗位ID', 'bigint(20)', 'Long', 'postId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:12', 1, '2023-05-24 16:29:12'); +INSERT INTO `gen_table_column` VALUES (1661288223993024514, 1661288223967858689, 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); +INSERT INTO `gen_table_column` VALUES (1661288223993024515, 1661288223967858689, 'role_id', '角色ID', 'bigint(20)', 'Long', 'roleId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:13', 1, '2023-05-24 16:29:13'); +INSERT INTO `gen_table_column` VALUES (1661288385121406978, 1661288385096241154, 'config_id', '参数主键', 'bigint(20)', 'Long', 'configId', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406979, 1661288385096241154, 'tenant_id', '租户编号', 'varchar(20)', 'String', 'tenantId', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 2, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406980, 1661288385096241154, 'config_name', '参数名称', 'varchar(100)', 'String', 'configName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406981, 1661288385096241154, 'config_key', '参数键名', 'varchar(100)', 'String', 'configKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406982, 1661288385096241154, 'config_value', '参数键值', 'varchar(500)', 'String', 'configValue', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 5, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406983, 1661288385096241154, 'config_type', '系统内置(Y是 N否)', 'char(1)', 'String', 'configType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 6, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385121406984, 1661288385096241154, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385142378498, 1661288385096241154, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385142378499, 1661288385096241154, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385142378500, 1661288385096241154, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385142378501, 1661288385096241154, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1661288385142378502, 1661288385096241154, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2023-05-24 16:29:51', 1, '2023-05-24 16:29:51'); +INSERT INTO `gen_table_column` VALUES (1680196323806289921, 1680196323521077249, 'id', '文件id', 'bigint(20) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323806289922, 1680196323521077249, 'url', '文件访问地址', 'varchar(512)', 'String', 'url', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 2, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323835650050, 1680196323445579778, 'id', '文件id', 'bigint(20) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323835650051, 1680196323445579778, 'url', '文件访问地址', 'varchar(512)', 'String', 'url', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 2, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323835650052, 1680196323445579778, 'size', '文件大小,单位字节', 'bigint(20)', 'Long', 'size', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398785, 1680196323445579778, 'filename', '文件名称', 'varchar(256)', 'String', 'filename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398786, 1680196323445579778, 'original_filename', '原始文件名', 'varchar(256)', 'String', 'originalFilename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 5, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398787, 1680196323445579778, 'base_path', '基础存储路径', 'varchar(256)', 'String', 'basePath', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398788, 1680196323445579778, 'path', '存储路径', 'varchar(256)', 'String', 'path', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398789, 1680196323445579778, 'ext', '文件扩展名', 'varchar(32)', 'String', 'ext', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398790, 1680196323445579778, 'object_id', '文件所属对象id', 'varchar(32)', 'String', 'objectId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323873398791, 1680196323445579778, 'file_type', '文件类型', 'varchar(32)', 'String', 'fileType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 10, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323932119041, 1680196323445579778, 'attr', '附加属性', 'text', 'String', 'attr', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 11, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323932119042, 1680196323445579778, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507649, 1680196323445579778, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507650, 1680196323445579778, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 14, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507651, 1680196323445579778, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 15, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507652, 1680196323445579778, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 16, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507653, 1680196323445579778, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 17, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323940507654, 1680196323445579778, 'del_flag', '删除标志(0代表存在 1代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 18, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479170, 1680196323521077249, 'size', '文件大小,单位字节', 'bigint(20)', 'Long', 'size', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479171, 1680196323521077249, 'filename', '文件名称', 'varchar(256)', 'String', 'filename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479172, 1680196323521077249, 'original_filename', '原始文件名', 'varchar(256)', 'String', 'originalFilename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 5, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479173, 1680196323521077249, 'base_path', '基础存储路径', 'varchar(256)', 'String', 'basePath', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479174, 1680196323521077249, 'path', '存储路径', 'varchar(256)', 'String', 'path', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479175, 1680196323521077249, 'ext', '文件扩展名', 'varchar(32)', 'String', 'ext', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479176, 1680196323521077249, 'object_id', '文件所属对象id', 'varchar(32)', 'String', 'objectId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479177, 1680196323521077249, 'file_type', '文件类型', 'varchar(32)', 'String', 'fileType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 10, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323961479178, 1680196323521077249, 'attr', '附加属性', 'text', 'String', 'attr', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 11, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323999227905, 1680196323445579778, 'update_ip', '更新IP', 'varchar(128)', 'String', 'updateIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 19, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196323999227906, 1680196323445579778, 'tenant_id', '租户Id', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'input', '', 20, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199425, 1680196323521077249, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199426, 1680196323521077249, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199427, 1680196323521077249, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 14, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199428, 1680196323521077249, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 15, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199429, 1680196323521077249, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 16, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199430, 1680196323521077249, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 17, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199431, 1680196323521077249, 'del_flag', '删除标志(0代表存在 1代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 18, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199432, 1680196323521077249, 'update_ip', '更新IP', 'varchar(128)', 'String', 'updateIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 19, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680196324020199433, 1680196323521077249, 'tenant_id', '租户Id', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'input', '', 20, 103, 1, '2023-07-15 20:43:15', 1, '2023-07-15 20:43:15'); +INSERT INTO `gen_table_column` VALUES (1680199147667853313, 1680199147407806465, 'id', '文件id', 'bigint(20) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147667853314, 1680199147407806465, 'url', '文件访问地址', 'varchar(512)', 'String', 'url', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 2, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147667853315, 1680199147407806465, 'size', '文件大小,单位字节', 'bigint(20)', 'Long', 'size', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147667853316, 1680199147407806465, 'filename', '文件名称', 'varchar(256)', 'String', 'filename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147667853317, 1680199147407806465, 'original_filename', '原始文件名', 'varchar(256)', 'String', 'originalFilename', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 5, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147667853318, 1680199147407806465, 'base_path', '基础存储路径', 'varchar(256)', 'String', 'basePath', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962178, 1680199147407806465, 'path', '存储路径', 'varchar(256)', 'String', 'path', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962179, 1680199147407806465, 'ext', '文件扩展名', 'varchar(32)', 'String', 'ext', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962180, 1680199147407806465, 'object_id', '文件所属对象id', 'varchar(32)', 'String', 'objectId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962181, 1680199147407806465, 'file_type', '文件类型', 'varchar(32)', 'String', 'fileType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 10, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962182, 1680199147407806465, 'attr', '附加属性', 'text', 'String', 'attr', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 11, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962183, 1680199147407806465, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962184, 1680199147407806465, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962185, 1680199147407806465, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 14, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962186, 1680199147407806465, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 15, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962187, 1680199147407806465, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 16, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962188, 1680199147407806465, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 17, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962189, 1680199147407806465, 'del_flag', '删除标志(0代表存在 1代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 18, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962190, 1680199147407806465, 'update_ip', '更新IP', 'varchar(128)', 'String', 'updateIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 19, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680199147734962191, 1680199147407806465, 'tenant_id', '租户Id', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'input', '', 20, 103, 1, '2023-07-15 20:54:28', 1, '2023-07-15 20:54:28'); +INSERT INTO `gen_table_column` VALUES (1680481753240215553, 1680481752850145282, 'id', 'id', 'bigint(20) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215554, 1680481752850145282, 'prompt', '描述词', 'text', 'String', 'prompt', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 2, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215555, 1680481752850145282, 'negative_prompt', '负面词', 'text', 'String', 'negativePrompt', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 3, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215556, 1680481752850145282, 'model_name', '模型名称', 'varchar(256)', 'String', 'modelName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215557, 1680481752850145282, 'steps', '迭代步数', 'int(10)', 'Integer', 'steps', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215558, 1680481752850145282, 'seed', '种子', 'varchar(256)', 'String', 'seed', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215559, 1680481752850145282, 'width', '图片宽度', 'varchar(256)', 'String', 'width', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215560, 1680481752850145282, 'height', '图片高度', 'varchar(32)', 'String', 'height', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215561, 1680481752850145282, 'sampler_name', '采样方法', 'varchar(32)', 'String', 'samplerName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 9, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215562, 1680481752850145282, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215563, 1680481752850145282, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215564, 1680481752850145282, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 12, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215565, 1680481752850145282, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 13, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215566, 1680481752850145282, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 14, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215567, 1680481752850145282, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 15, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215568, 1680481752850145282, 'version', '版本', 'int(11)', 'Long', 'version', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 16, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215569, 1680481752850145282, 'del_flag', '删除标志(0代表存在 1代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 17, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215570, 1680481752850145282, 'update_ip', '更新IP', 'varchar(128)', 'String', 'updateIp', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 18, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1680481753240215571, 1680481752850145282, 'tenant_id', '租户Id', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'input', '', 19, 103, 1, '2023-07-16 15:37:26', 1, '2023-07-16 15:37:26'); +INSERT INTO `gen_table_column` VALUES (1740573615225053185, 1740573614897897473, 'id', '主键', 'int(11)', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053186, 1740573614897897473, 'order_no', '订单编号', 'varchar(20)', 'String', 'orderNo', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053187, 1740573614897897473, 'order_name', '订单名称', 'varchar(100)', 'String', 'orderName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053188, 1740573614897897473, 'amount', '金额', 'decimal(10,2)', 'BigDecimal', 'amount', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053189, 1740573614897897473, 'payment_status', '支付状态', 'char(1)', 'String', 'paymentStatus', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 5, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053190, 1740573614897897473, 'payment_method', '支付方式', 'char(1)', 'String', 'paymentMethod', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053191, 1740573614897897473, 'user_id', '用户ID', 'timestamp', 'Date', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'datetime', '', 7, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053192, 1740573614897897473, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053193, 1740573614897897473, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053194, 1740573614897897473, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053195, 1740573614897897473, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1740573615225053196, 1740573614897897473, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2023-12-29 11:21:03', 1, '2023-12-29 11:21:03'); +INSERT INTO `gen_table_column` VALUES (1775895242624061441, 1775895242171076610, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061442, 1775895242171076610, 'model_name', '模型名称', 'varchar(50)', 'String', 'modelName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061443, 1775895242171076610, 'model_no', '模型编号', 'varchar(255)', 'String', 'modelNo', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061444, 1775895242171076610, 'model_describe', '模型描述', 'varchar(255)', 'String', 'modelDescribe', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061445, 1775895242171076610, 'model_price', '模型价格', 'double', 'Long', 'modelPrice', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061446, 1775895242171076610, 'model_type', '计费类型', 'char(1)', 'String', 'modelType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 6, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061447, 1775895242171076610, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 7, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061448, 1775895242171076610, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061449, 1775895242171076610, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061450, 1775895242171076610, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061451, 1775895242171076610, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 11, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1775895242624061452, 1775895242171076610, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 103, 1, '2024-04-04 22:36:35', 1, '2024-04-04 22:36:35'); +INSERT INTO `gen_table_column` VALUES (1785390412381896706, 1785390411861803009, 'id', '主键', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896707, 1785390411861803009, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896708, 1785390411861803009, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412381896709, 1785390411861803009, 'remark', '备注(微信号)', 'varchar(64)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 4, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811265, 1785390411861803009, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 5, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811266, 1785390411861803009, 'update_time', '修改时间', 'datetime', 'Date', 'updateTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 6, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811267, 1785390411861803009, 'to_friend', '指定好友回复开关', 'bit(1)', 'Integer', 'toFriend', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811268, 1785390411861803009, 'to_group', '指定群回复开关', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811269, 1785390411861803009, 'default_friend', '默认好友回复开关', 'bit(1)', 'Integer', 'defaultFriend', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811270, 1785390411861803009, 'default_group', '默认群回复开关', 'bit(1)', 'Integer', 'defaultGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 10, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811271, 1785390411861803009, 'from_out', '对外接口开关', 'bit(1)', 'Integer', 'fromOut', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 11, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390412444811272, 1785390411861803009, 'enable', '机器启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 12, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-03 21:00:52'); +INSERT INTO `gen_table_column` VALUES (1785390414135115778, 1785390413745045505, 'id', '', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115779, 1785390413745045505, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115780, 1785390413745045505, 'key_data', '关键词', 'varchar(64)', 'String', 'keyData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115781, 1785390413745045505, 'value_data', '回复内容', 'varchar(1024)', 'String', 'valueData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'textarea', '', 4, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115782, 1785390413745045505, 'type_data', '回复类型', 'varchar(64)', 'String', 'typeData', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115783, 1785390413745045505, 'nick_name', '目标昵称', 'varchar(64)', 'String', 'nickName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115784, 1785390413745045505, 'to_group', '群1好友0', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115785, 1785390413745045505, 'enable', '启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 8, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390414135115786, 1785390413745045505, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-01 03:27:00', 1, '2024-05-01 03:27:00'); +INSERT INTO `gen_table_column` VALUES (1785390415250800642, 1785390414860730369, 'id', '', 'int(11) unsigned', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800643, 1785390414860730369, 'out_key', '外接唯一码', 'varchar(16)', 'String', 'outKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800644, 1785390414860730369, 'unique_key', '机器唯一码', 'varchar(16)', 'String', 'uniqueKey', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800645, 1785390414860730369, 'nick_name', '目标昵称', 'varchar(64)', 'String', 'nickName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800646, 1785390414860730369, 'to_group', '群1好友0', 'bit(1)', 'Integer', 'toGroup', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800647, 1785390414860730369, 'enable', '启用1禁用0', 'bit(1)', 'Integer', 'enable', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800648, 1785390414860730369, 'white_list', 'IP白名单', 'varchar(255)', 'String', 'whiteList', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1785390415250800649, 1785390414860730369, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', '1', NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 8, 103, 1, '2024-05-01 03:27:01', 1, '2024-05-01 03:27:01'); +INSERT INTO `gen_table_column` VALUES (1786379560827805698, 1786379560181882881, 'id', '主键', 'bigint(20)', 'Long', 'id', '1', '1', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805699, 1786379560181882881, 'user_id', '用户id', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805700, 1786379560181882881, 'code', '兑换码', 'varchar(255)', 'String', 'code', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805701, 1786379560181882881, 'amount', '兑换金额', 'double(10,2)', 'BigDecimal', 'amount', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805702, 1786379560181882881, 'status', '兑换状态', 'char(1)', 'String', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 5, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805703, 1786379560181882881, 'balance_before', '兑换前余额', 'double(10,2)', 'BigDecimal', 'balanceBefore', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 6, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805704, 1786379560181882881, 'balance_after', '兑换后余额', 'double(10,2)', 'BigDecimal', 'balanceAfter', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805705, 1786379560181882881, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805706, 1786379560181882881, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805707, 1786379560181882881, 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 10, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805708, 1786379560181882881, 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 11, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560827805709, 1786379560181882881, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 12, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1786379560890720257, 1786379560181882881, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 103, 1, '2024-05-03 20:57:31', 1, '2024-05-03 20:57:31'); +INSERT INTO `gen_table_column` VALUES (1789155611425452034, 1789155611035381761, 'id', 'ID', 'bigint(20)', 'Long', 'id', '1', '0', '1', NULL, '1', '1', NULL, 'EQ', 'input', '', 1, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452035, 1789155611035381761, 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452036, 1789155611035381761, 'notice_id', '公告ID', 'bigint(20)', 'Long', 'noticeId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452037, 1789155611035381761, 'read_status', '阅读状态(0未读 1已读)', 'char(1)', 'String', 'readStatus', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 4, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452038, 1789155611035381761, 'create_dept', '创建部门', 'bigint(20)', 'Long', 'createDept', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 5, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452039, 1789155611035381761, 'create_by', '创建者', 'bigint(20)', 'Long', 'createBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 6, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452040, 1789155611035381761, 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 7, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452041, 1789155611035381761, 'update_by', '更新者', 'bigint(20)', 'Long', 'updateBy', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'input', '', 8, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452042, 1789155611035381761, 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, NULL, NULL, NULL, NULL, 'EQ', 'datetime', '', 9, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); +INSERT INTO `gen_table_column` VALUES (1789155611425452043, 1789155611035381761, 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'textarea', '', 10, 103, 1, '2024-05-11 12:48:33', 1, '2024-05-11 12:48:33'); + +-- ---------------------------- +-- Table structure for knowledge_attach +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_attach`; +CREATE TABLE `knowledge_attach` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `doc_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档ID', + `doc_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档名称', + `doc_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档类型', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文档内容', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_kname`(`kid`, `doc_name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 103 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识库附件' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_attach +-- ---------------------------- + +-- ---------------------------- +-- Table structure for knowledge_fragment +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_fragment`; +CREATE TABLE `knowledge_fragment` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `doc_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文档ID', + `fid` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识片段ID', + `idx` int(11) NOT NULL COMMENT '片段索引下标', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文档内容', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识片段' ROW_FORMAT = DYNAMIC; + +-- ---------------------------- +-- Records of knowledge_fragment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for knowledge_info +-- ---------------------------- +DROP TABLE IF EXISTS `knowledge_info`; +CREATE TABLE `knowledge_info` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `kid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库ID', + `uid` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户ID', + `kname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '知识库名称', + `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `create_time` datetime NULL DEFAULT NULL, + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_kid`(`kid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '知识库' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of knowledge_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` bigint(20) NOT NULL COMMENT '参数主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES (1, '000000', '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` VALUES (2, '000000', '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '初始化密码 123456'); +INSERT INTO `sys_config` VALUES (3, '000000', '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` VALUES (5, '000000', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, '是否开启注册用户功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (11, '000000', 'OSS预览列表资源开关', 'sys.oss.previewListResource', 'true', 'Y', 103, 1, '2023-05-14 15:19:42', NULL, NULL, 'true:开启, false:关闭'); + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint(20) NOT NULL COMMENT '部门id', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, '000000', 0, '0', '熊猫科技', 0, 'ageerle', '15888888888', 'ageerle@163.com', '0', '0', 103, 1, '2023-05-14 15:19:39', 1, '2023-12-29 11:18:24'); +INSERT INTO `sys_dept` VALUES (101, '000000', 100, '0,100', '深圳总公司', 1, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (102, '000000', 100, '0,100', '长沙分公司', 2, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (103, '000000', 101, '0,100,101', '研发部门', 1, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (104, '000000', 101, '0,100,101', '市场部门', 2, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (105, '000000', 101, '0,100,101', '测试部门', 3, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (106, '000000', 101, '0,100,101', '财务部门', 4, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (107, '000000', 101, '0,100,101', '运维部门', 5, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (108, '000000', 102, '0,100,102', '市场部门', 1, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (109, '000000', 102, '0,100,102', '财务部门', 2, '疯狂的狮子Li', '15888888888', 'xxx@qq.com', '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_dept` VALUES (1729685491964084226, '911866', 0, '0', '5126', 0, 'admin', NULL, NULL, '0', '2', 103, 1, '2023-11-29 10:15:32', 1, '2023-11-29 10:15:32'); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `dict_code` bigint(20) NOT NULL COMMENT '字典编码', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dict_sort` int(4) NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, '000000', 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '性别男'); +INSERT INTO `sys_dict_data` VALUES (2, '000000', 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '性别女'); +INSERT INTO `sys_dict_data` VALUES (3, '000000', 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '性别未知'); +INSERT INTO `sys_dict_data` VALUES (4, '000000', 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '显示菜单'); +INSERT INTO `sys_dict_data` VALUES (5, '000000', 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '隐藏菜单'); +INSERT INTO `sys_dict_data` VALUES (6, '000000', 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (7, '000000', 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (12, '000000', 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '系统默认是'); +INSERT INTO `sys_dict_data` VALUES (13, '000000', 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '系统默认否'); +INSERT INTO `sys_dict_data` VALUES (14, '000000', 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '通知'); +INSERT INTO `sys_dict_data` VALUES (15, '000000', 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '公告'); +INSERT INTO `sys_dict_data` VALUES (16, '000000', 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (17, '000000', 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '关闭状态'); +INSERT INTO `sys_dict_data` VALUES (18, '000000', 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '新增操作'); +INSERT INTO `sys_dict_data` VALUES (19, '000000', 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '修改操作'); +INSERT INTO `sys_dict_data` VALUES (20, '000000', 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '删除操作'); +INSERT INTO `sys_dict_data` VALUES (21, '000000', 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '授权操作'); +INSERT INTO `sys_dict_data` VALUES (22, '000000', 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '导出操作'); +INSERT INTO `sys_dict_data` VALUES (23, '000000', 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '导入操作'); +INSERT INTO `sys_dict_data` VALUES (24, '000000', 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '强退操作'); +INSERT INTO `sys_dict_data` VALUES (25, '000000', 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '生成操作'); +INSERT INTO `sys_dict_data` VALUES (26, '000000', 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '清空操作'); +INSERT INTO `sys_dict_data` VALUES (27, '000000', 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (28, '000000', 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (29, '000000', 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '其他操作'); +INSERT INTO `sys_dict_data` VALUES (1775756996568993793, '000000', 1, '免费用户', '0', 'sys_user_grade', '', 'info', 'N', '0', 103, 1, '2024-04-04 13:27:15', 1, '2024-04-04 13:30:09', ''); +INSERT INTO `sys_dict_data` VALUES (1775757116970684418, '000000', 2, '高级会员', '1', 'sys_user_grade', '', 'success', 'N', '0', 103, 1, '2024-04-04 13:27:43', 1, '2024-04-04 13:30:15', ''); +INSERT INTO `sys_dict_data` VALUES (1776109770934677506, '000000', 0, 'token计费', '1', 'sys_model_billing', '', 'primary', 'N', '0', 103, 1, '2024-04-05 12:49:03', 1, '2024-04-21 00:05:41', ''); +INSERT INTO `sys_dict_data` VALUES (1776109853377916929, '000000', 0, '次数计费', '2', 'sys_model_billing', '', 'success', 'N', '0', 103, 1, '2024-04-05 12:49:22', 1, '2024-04-05 12:49:22', ''); +INSERT INTO `sys_dict_data` VALUES (1780264338471858177, '000000', 0, '未支付', '1', 'pay_state', '', 'info', 'N', '0', 103, 1, '2024-04-16 23:57:49', 1, '2024-04-16 23:58:29', ''); +INSERT INTO `sys_dict_data` VALUES (1780264431589601282, '000000', 2, '已支付', '2', 'pay_state', '', 'success', 'N', '0', 103, 1, '2024-04-16 23:58:11', 1, '2024-04-16 23:58:21', ''); + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `dict_id` bigint(20) NOT NULL COMMENT '字典主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `tenant_id`(`tenant_id`, `dict_type`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +INSERT INTO `sys_dict_type` VALUES (1, '000000', '用户性别', 'sys_user_sex', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (2, '000000', '菜单状态', 'sys_show_hide', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (3, '000000', '系统开关', 'sys_normal_disable', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (6, '000000', '系统是否', 'sys_yes_no', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (7, '000000', '通知类型', 'sys_notice_type', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (8, '000000', '通知状态', 'sys_notice_status', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (9, '000000', '操作类型', 'sys_oper_type', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (10, '000000', '系统状态', 'sys_common_status', '0', 103, 1, '2023-05-14 15:19:41', NULL, NULL, '登录状态列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083714, '911866', '用户性别', 'sys_user_sex', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083715, '911866', '菜单状态', 'sys_show_hide', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083716, '911866', '系统开关', 'sys_normal_disable', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083717, '911866', '系统是否', 'sys_yes_no', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083718, '911866', '通知类型', 'sys_notice_type', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083719, '911866', '通知状态', 'sys_notice_status', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083720, '911866', '操作类型', 'sys_oper_type', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (1729685494468083721, '911866', '系统状态', 'sys_common_status', '0', 103, 1, '2023-05-14 15:19:41', 1, '2023-05-14 15:19:41', '登录状态列表'); +INSERT INTO `sys_dict_type` VALUES (1775756736895438849, '000000', '用户等级', 'sys_user_grade', '0', 103, 1, '2024-04-04 13:26:13', 1, '2024-04-04 13:26:13', ''); +INSERT INTO `sys_dict_type` VALUES (1776109665045278721, '000000', '模型计费方式', 'sys_model_billing', '0', 103, 1, '2024-04-05 12:48:37', 1, '2024-04-08 11:22:18', '模型计费方式'); +INSERT INTO `sys_dict_type` VALUES (1780263881368219649, '000000', '支付状态', 'pay_state', '0', 103, 1, '2024-04-16 23:56:00', 1, '2024-04-16 23:56:00', '支付状态'); + +-- ---------------------------- +-- Table structure for sys_file_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_file_info`; +CREATE TABLE `sys_file_info` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文件id', + `url` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件访问地址', + `size` bigint(20) NULL DEFAULT NULL COMMENT '文件大小,单位字节', + `filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称', + `original_filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '原始文件名', + `base_path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '基础存储路径', + `path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '存储路径', + `ext` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件扩展名', + `user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件所属用户', + `file_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', + `attr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '附加属性', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `version` int(11) NULL DEFAULT NULL COMMENT '版本', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新IP', + `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户Id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_file_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_logininfor +-- ---------------------------- +DROP TABLE IF EXISTS `sys_logininfor`; +CREATE TABLE `sys_logininfor` ( + `info_id` bigint(20) NOT NULL COMMENT '访问ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE, + INDEX `idx_sys_logininfor_s`(`status`) USING BTREE, + INDEX `idx_sys_logininfor_lt`(`login_time`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- +INSERT INTO `sys_logininfor` VALUES (1872667867861315585, '00000', 'pandarobot@163.com', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '1', '密码输入错误2次', '2024-12-27 23:36:27'); +INSERT INTO `sys_logininfor` VALUES (1872668258141302786, '00000', 'pandarobot@163.com', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '1', '密码输入错误3次', '2024-12-27 23:38:00'); +INSERT INTO `sys_logininfor` VALUES (1872668848007254018, '00000', 'pandarobot@163.com', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2024-12-27 23:40:21'); + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `is_frame` int(11) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int(11) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '显示状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 2, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'eos-icons:system-group', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-06 21:08:06', '系统管理目录'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1775500307898949634, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'ph:user-fill', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-07 21:29:29', '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'ri:user-3-fill', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-07 21:04:59', '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'typcn:th-menu-outline', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-07 21:06:06', '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '1', '1', 'system:dept:list', 'mdi:company', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-07 21:07:38', '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '1', '1', 'system:post:list', 'post', 103, 1, '2023-05-14 15:19:39', 1, '2024-04-04 22:36:15', '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'fluent-mdl2:dictionary', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:14:33', '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '系统参数', 1, 10, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'tdesign:system-code', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:11:07', '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 14, 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'icon-park-solid:volume-notice', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:11:42', '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', '', 1, 0, 'M', '0', '0', '', 'icon-park-solid:log', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:10:41', '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (113, '缓存监控', 1, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'octicon:cache-24', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:09:44', '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'icon-park-solid:log', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:13:20', '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'icon-park-solid:log', 103, 1, '2023-05-14 15:19:40', 1, '2024-10-07 21:13:33', '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1001, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '日志导出', 500, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '账户解锁', 501, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 103, 1, '2023-05-14 15:19:40', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1775500307898949634, '运营管理', 0, 0, 'operate', NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'icon-park-outline:appointment', 103, 1, '2024-04-03 20:27:15', 1, '2024-10-06 21:10:18', ''); +INSERT INTO `sys_menu` VALUES (1775895273104068610, '系统模型', 1775500307898949634, 2, 'model', 'system/model/index', NULL, 1, 0, 'C', '0', '0', 'system:model:list', 'ph:list-fill', 103, 1, '2024-04-05 12:00:38', 1, '2024-10-07 21:36:00', '系统模型菜单'); +INSERT INTO `sys_menu` VALUES (1775895273104068611, '系统模型查询', 1775895273104068610, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:query', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1775895273104068612, '系统模型新增', 1775895273104068610, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:add', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1775895273104068613, '系统模型修改', 1775895273104068610, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:edit', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1775895273104068614, '系统模型删除', 1775895273104068610, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:remove', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1775895273104068615, '系统模型导出', 1775895273104068610, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:export', '#', 103, 1, '2024-04-05 12:00:38', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507266, '聊天消息', 1775500307898949634, 5, 'chatMessage', 'system/message/index', NULL, 1, 0, 'C', '0', '0', 'system:message:list', 'bx:chat', 103, 1, '2024-04-16 22:24:48', 1, '2024-10-07 21:38:49', '聊天消息菜单'); +INSERT INTO `sys_menu` VALUES (1780240077690507267, '聊天消息查询', 1780240077690507266, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:query', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507268, '聊天消息新增', 1780240077690507266, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:add', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507269, '聊天消息修改', 1780240077690507266, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:edit', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507270, '聊天消息删除', 1780240077690507266, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:remove', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780240077690507271, '聊天消息导出', 1780240077690507266, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:export', '#', 103, 1, '2024-04-16 22:24:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018433, '支付订单', 1775500307898949634, 6, 'order', 'system/order/index', NULL, 1, 0, 'C', '0', '0', 'system:order:list', 'material-symbols:order-approve', 103, 1, '2024-04-16 23:32:48', 1, '2024-11-02 22:21:15', '支付订单菜单'); +INSERT INTO `sys_menu` VALUES (1780255628576018434, '支付订单查询', 1780255628576018433, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:query', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018435, '支付订单新增', 1780255628576018433, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:add', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018436, '支付订单修改', 1780255628576018433, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:edit', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018437, '支付订单删除', 1780255628576018433, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:remove', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1780255628576018438, '支付订单导出', 1780255628576018433, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:orders:export', '#', 103, 1, '2024-04-16 23:32:48', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156481, '兑换管理', 1775500307898949634, 8, 'exchange', 'system/exchange/index', NULL, 1, 0, 'C', '0', '0', 'system:exchange:list', 'mingcute:exchange-cny-fill', 103, 1, '2024-05-03 20:59:54', 1, '2024-11-02 22:22:41', '用户兑换记录菜单'); +INSERT INTO `sys_menu` VALUES (1786379590171156482, '用户兑换记录查询', 1786379590171156481, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:query', '#', 103, 1, '2024-05-03 20:59:54', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156483, '用户兑换记录新增', 1786379590171156481, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:add', '#', 103, 1, '2024-05-03 20:59:54', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156484, '用户兑换记录修改', 1786379590171156481, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:edit', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156485, '用户兑换记录删除', 1786379590171156481, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:remove', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1786379590171156486, '用户兑换记录导出', 1786379590171156481, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:voucher:export', '#', 103, 1, '2024-05-03 20:59:55', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1787078000285122561, '套餐管理', 1775500307898949634, 3, 'package', 'system/package/index', NULL, 1, 0, 'C', '0', '0', 'system:package:list', 'lets-icons:order', 103, 1, '2024-05-05 19:13:53', 1, '2024-11-02 22:20:36', '套餐管理菜单'); +INSERT INTO `sys_menu` VALUES (1810594719028834305, '应用管理', 1775500307898949634, 4, 'gpts', 'system/gpts/index', NULL, 1, 0, 'C', '1', '0', 'system:gpts:list', 'tdesign:app', 103, 1, '2024-07-09 16:40:18', 1, '2024-10-07 21:39:40', 'gpts管理菜单'); +INSERT INTO `sys_menu` VALUES (1810594719028834306, 'gpts管理查询', 1810594719028834305, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:gpts:query', '#', 103, 1, '2024-07-09 16:40:19', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1810594719028834307, 'gpts管理新增', 1810594719028834305, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:gpts:add', '#', 103, 1, '2024-07-09 16:40:19', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1810594719028834308, 'gpts管理修改', 1810594719028834305, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:gpts:edit', '#', 103, 1, '2024-07-09 16:40:19', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1810594719028834309, 'gpts管理删除', 1810594719028834305, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:gpts:remove', '#', 103, 1, '2024-07-09 16:40:19', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1810594719028834310, 'gpts管理导出', 1810594719028834305, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:gpts:export', '#', 103, 1, '2024-07-09 16:40:19', NULL, NULL, ''); +INSERT INTO `sys_menu` VALUES (1843281231381852162, '文件管理', 1775500307898949634, 20, 'file', 'system/oss/index', NULL, 1, 0, 'C', '0', '0', NULL, 'material-symbols-light:folder', 103, 1, '2024-10-07 21:24:27', 1, '2024-12-27 23:03:04', ''); +INSERT INTO `sys_menu` VALUES (1860690448695549953, '配置管理', 1775500307898949634, 9, 'configurationManage', 'system/configurationManage/index', NULL, 1, 0, 'C', '1', '0', NULL, 'mdi:archive-cog-outline', 103, 1, '2024-11-24 22:22:28', 1, '2024-12-27 23:01:57', ''); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` bigint(20) NOT NULL COMMENT '公告ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1789324923280932865, '000000', '公告', '2', 0x3C703E3C7374726F6E67207374796C653D22636F6C6F723A20726762283235352C203135332C2030293B223EE69CACE7BD91E7AB99E4B88EE4BBBBE4BD95E585B6E4BB96E585ACE58FB8E68896E59586E6A087E6B2A1E69C89E4BBBBE4BD95E585B3E88194E68896E59088E4BD9CE585B3E7B3BB3C2F7374726F6E673E3C2F703E3C703E3C7370616E207374796C653D22636F6C6F723A20726762283233302C20302C2030293B223E4149E4B99FE4BC9AE78AAFE99499E38082E8AFB7E58BBFE5B086E585B6E794A8E4BA8EE9878DE8A681E79BAEE79A843C2F7370616E3E3C2F703E3C703E3C7370616E207374796C653D22636F6C6F723A20726762283235352C203135332C2030293B223EE68891E4BBACE79BAEE5898DE6ADA3E59CA8E4BFAEE5A48DE68891E4BBACE7BD91E7AB99E4B88AE79A84E99499E8AFAFE5B9B6E694B9E8BF9BE7BB86E88A82E38082E5A682E69E9CE682A8E69C89E4BBBBE4BD95E79691E997AEEFBC8CE8AFB7E9809AE8BF87E4BBA5E4B88BE696B9E5BC8FE88194E7B3BBE68891E4BBACEFBC9A61676565726C65403136332E636F6D3C2F7370616E3E3C2F703E3C703E3C62723E3C2F703E, '0', 103, 1, '2024-05-12 00:01:20', 1, '2024-10-28 23:25:21', ''); + +-- ---------------------------- +-- Table structure for sys_notice_state +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice_state`; +CREATE TABLE `sys_notice_state` ( + `id` bigint(20) NOT NULL COMMENT 'ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `notice_id` bigint(20) NOT NULL COMMENT '公告ID', + `read_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '阅读状态(0未读 1已读)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户阅读状态表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice_state +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_oper_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oper_log`; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint(20) NOT NULL COMMENT '日志主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int(2) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int(1) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int(1) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime NULL DEFAULT NULL COMMENT '操作时间', + `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间', + PRIMARY KEY (`oper_id`) USING BTREE, + INDEX `idx_sys_oper_log_bt`(`business_type`) USING BTREE, + INDEX `idx_sys_oper_log_s`(`status`) USING BTREE, + INDEX `idx_sys_oper_log_ot`(`oper_time`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- +INSERT INTO `sys_oper_log` VALUES (1872671357086044161, '00000', '系统模型', 1, 'org.ruoyi.system.controller.system.SysModelController.add()', 'POST', 1, 'admin', '', '/system/model', '0:0:0:0:0:0:0:1', '内网IP', '{\"createDept\":null,\"createBy\":null,\"createTime\":null,\"updateBy\":null,\"updateTime\":null,\"id\":\"1872671356234600449\",\"modelName\":\"suno-v31\",\"modelDescribe\":\"suno-v31\",\"modelPrice\":0.2,\"modelType\":\"1\",\"modelShow\":\"1\",\"systemPrompt\":null,\"apiHost\":\"10\",\"apiKey\":\"10\",\"remark\":\"10\"}', '{\"code\":200,\"msg\":\"操作成功\",\"data\":null}', 0, '', '2024-12-27 23:50:19', 198); + +-- ---------------------------- +-- Table structure for sys_oss +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oss`; +CREATE TABLE `sys_oss` ( + `oss_id` bigint(20) NOT NULL COMMENT '对象存储主键', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名', + `original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原名', + `file_suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件后缀名', + `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'URL地址', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '上传人', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + `service` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'minio' COMMENT '服务商', + PRIMARY KEY (`oss_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'OSS对象存储表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oss +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_oss_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oss_config`; +CREATE TABLE `sys_oss_config` ( + `oss_config_id` bigint(20) NOT NULL COMMENT '主建', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `config_key` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置key', + `access_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'accessKey', + `secret_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '秘钥', + `bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '桶名称', + `prefix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '前缀', + `endpoint` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '访问站点', + `domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '自定义域名', + `is_https` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否https(Y=是,N=否)', + `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '域', + `access_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '桶权限类型(0=private 1=public 2=custom)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否默认(0=是,1=否)', + `ext1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '扩展字段', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`oss_config_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '对象存储配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oss_config +-- ---------------------------- +INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-07-13 23:28:18', NULL); +INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'ruoyi', 'ruoyi123', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', NULL); +INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'ruoyi', 'ruoyi123', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-07-13 23:35:23', NULL); +INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'ruoyi', 'ruoyi123', 'ruoyi', 'panda', 'cos.ap-guangzhou.myqcloud.com', '', 'N', 'ap-guangzhou', '1', '0', '', 103, 1, '2023-05-14 15:19:42', 1, '2024-11-04 00:13:35', ''); +INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2023-05-14 15:19:42', 1, '2023-05-14 15:19:42', NULL); + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint(20) NOT NULL COMMENT '岗位ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int(4) NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES (1, '000000', 'ceo', '董事长', 1, '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL, ''); +INSERT INTO `sys_post` VALUES (2, '000000', 'se', '项目经理', 2, '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL, ''); +INSERT INTO `sys_post` VALUES (3, '000000', 'hr', '人力资源', 3, '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL, ''); +INSERT INTO `sys_post` VALUES (4, '000000', 'user', '普通员工', 4, '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int(4) NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '000000', '超级管理员', 'superadmin', 1, '1', 1, 1, '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '000000', '普通角色', 'common', 2, '2', 1, 1, '0', '0', 103, 1, '2023-05-14 15:19:39', 1, '2024-10-07 20:59:32', '普通角色'); +INSERT INTO `sys_role` VALUES (3, '000000', '本部门及以下', 'test1', 3, '4', 1, 1, '0', '0', 103, 1, '2023-05-14 15:20:00', 1, '2023-06-04 10:20:43', NULL); +INSERT INTO `sys_role` VALUES (4, '000000', '仅本人', 'test2', 4, '5', 1, 1, '0', '0', 103, 1, '2023-05-14 15:20:00', 1, '2023-06-04 10:21:01', NULL); +INSERT INTO `sys_role` VALUES (1661661183933177857, '000000', '小程序管理员', 'xcxadmin', 1, '1', 1, 1, '0', '0', 103, 1, '2023-05-25 17:11:13', 1, '2023-05-25 17:11:13', ''); +INSERT INTO `sys_role` VALUES (1729685491108446210, '911866', '管理员', 'admin', 1, '1', 1, 1, '0', '0', 103, 1, '2023-11-29 10:15:32', 1, '2023-11-29 10:15:32', NULL); + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; +CREATE TABLE `sys_role_dept` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- +INSERT INTO `sys_role_dept` VALUES (2, 100); +INSERT INTO `sys_role_dept` VALUES (2, 101); +INSERT INTO `sys_role_dept` VALUES (2, 105); +INSERT INTO `sys_role_dept` VALUES (1729685491108446210, 1729685491964084226); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES (3, 1); +INSERT INTO `sys_role_menu` VALUES (3, 100); +INSERT INTO `sys_role_menu` VALUES (3, 101); +INSERT INTO `sys_role_menu` VALUES (3, 102); +INSERT INTO `sys_role_menu` VALUES (3, 103); +INSERT INTO `sys_role_menu` VALUES (3, 104); +INSERT INTO `sys_role_menu` VALUES (3, 105); +INSERT INTO `sys_role_menu` VALUES (3, 106); +INSERT INTO `sys_role_menu` VALUES (3, 107); +INSERT INTO `sys_role_menu` VALUES (3, 108); +INSERT INTO `sys_role_menu` VALUES (3, 500); +INSERT INTO `sys_role_menu` VALUES (3, 501); +INSERT INTO `sys_role_menu` VALUES (3, 1001); +INSERT INTO `sys_role_menu` VALUES (3, 1002); +INSERT INTO `sys_role_menu` VALUES (3, 1003); +INSERT INTO `sys_role_menu` VALUES (3, 1004); +INSERT INTO `sys_role_menu` VALUES (3, 1005); +INSERT INTO `sys_role_menu` VALUES (3, 1006); +INSERT INTO `sys_role_menu` VALUES (3, 1007); +INSERT INTO `sys_role_menu` VALUES (3, 1008); +INSERT INTO `sys_role_menu` VALUES (3, 1009); +INSERT INTO `sys_role_menu` VALUES (3, 1010); +INSERT INTO `sys_role_menu` VALUES (3, 1011); +INSERT INTO `sys_role_menu` VALUES (3, 1012); +INSERT INTO `sys_role_menu` VALUES (3, 1013); +INSERT INTO `sys_role_menu` VALUES (3, 1014); +INSERT INTO `sys_role_menu` VALUES (3, 1015); +INSERT INTO `sys_role_menu` VALUES (3, 1016); +INSERT INTO `sys_role_menu` VALUES (3, 1017); +INSERT INTO `sys_role_menu` VALUES (3, 1018); +INSERT INTO `sys_role_menu` VALUES (3, 1019); +INSERT INTO `sys_role_menu` VALUES (3, 1020); +INSERT INTO `sys_role_menu` VALUES (3, 1021); +INSERT INTO `sys_role_menu` VALUES (3, 1022); +INSERT INTO `sys_role_menu` VALUES (3, 1023); +INSERT INTO `sys_role_menu` VALUES (3, 1024); +INSERT INTO `sys_role_menu` VALUES (3, 1025); +INSERT INTO `sys_role_menu` VALUES (3, 1026); +INSERT INTO `sys_role_menu` VALUES (3, 1027); +INSERT INTO `sys_role_menu` VALUES (3, 1028); +INSERT INTO `sys_role_menu` VALUES (3, 1029); +INSERT INTO `sys_role_menu` VALUES (3, 1030); +INSERT INTO `sys_role_menu` VALUES (3, 1031); +INSERT INTO `sys_role_menu` VALUES (3, 1032); +INSERT INTO `sys_role_menu` VALUES (3, 1033); +INSERT INTO `sys_role_menu` VALUES (3, 1034); +INSERT INTO `sys_role_menu` VALUES (3, 1035); +INSERT INTO `sys_role_menu` VALUES (3, 1036); +INSERT INTO `sys_role_menu` VALUES (3, 1037); +INSERT INTO `sys_role_menu` VALUES (3, 1038); +INSERT INTO `sys_role_menu` VALUES (3, 1039); +INSERT INTO `sys_role_menu` VALUES (3, 1040); +INSERT INTO `sys_role_menu` VALUES (3, 1041); +INSERT INTO `sys_role_menu` VALUES (3, 1042); +INSERT INTO `sys_role_menu` VALUES (3, 1043); +INSERT INTO `sys_role_menu` VALUES (3, 1044); +INSERT INTO `sys_role_menu` VALUES (3, 1045); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 100); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 107); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1001); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1002); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1003); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1004); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1005); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1006); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1007); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1036); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1037); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1038); +INSERT INTO `sys_role_menu` VALUES (1661661183933177857, 1039); +INSERT INTO `sys_role_menu` VALUES (1729685491108446210, 1689201668374556674); +INSERT INTO `sys_role_menu` VALUES (1729685491108446210, 1689205943360188417); +INSERT INTO `sys_role_menu` VALUES (1729685491108446210, 1689243465037561858); +INSERT INTO `sys_role_menu` VALUES (1729685491108446210, 1689243466220355585); + +-- ---------------------------- +-- Table structure for sys_tenant +-- ---------------------------- +DROP TABLE IF EXISTS `sys_tenant`; +CREATE TABLE `sys_tenant` ( + `id` bigint(20) NOT NULL COMMENT 'id', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '租户编号', + `contact_user_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系人', + `contact_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `company_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业名称', + `license_number` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '统一社会信用代码', + `address` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', + `intro` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '企业简介', + `domain` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '域名', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `package_id` bigint(20) NULL DEFAULT NULL COMMENT '租户套餐编号', + `expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间', + `account_count` int(11) NULL DEFAULT -1 COMMENT '用户数量(-1不限制)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '租户状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_tenant +-- ---------------------------- +INSERT INTO `sys_tenant` VALUES (1, '000000', '管理组', '15888888888', 'XXX有限公司', NULL, NULL, '多租户通用后台管理管理系统', NULL, NULL, NULL, NULL, -1, '0', '0', 103, 1, '2023-05-14 15:19:39', NULL, NULL); +INSERT INTO `sys_tenant` VALUES (1729685490647072769, '911866', '测试', '11111111111', '5126', '', '', '', '', '', 1729685389795033090, NULL, 1, '0', '2', 103, 1, '2023-11-29 10:15:32', 1, '2023-11-29 10:15:32'); + +-- ---------------------------- +-- Table structure for sys_tenant_package +-- ---------------------------- +DROP TABLE IF EXISTS `sys_tenant_package`; +CREATE TABLE `sys_tenant_package` ( + `package_id` bigint(20) NOT NULL COMMENT '租户套餐id', + `package_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '套餐名称', + `menu_ids` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联菜单id', + `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`package_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '租户套餐表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_tenant_package +-- ---------------------------- +INSERT INTO `sys_tenant_package` VALUES (1729685389795033090, '测试', '1689205943360188417, 1689243466220355585, 1689201668374556674, 1689243465037561858', '', 1, '0', '2', 103, 1, '2023-11-29 10:15:08', 1, '2023-11-29 10:15:08'); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信用户标识', + `user_grade` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户等级', + `user_balance` double(20, 2) NULL DEFAULT 0.00 COMMENT '账户余额', + `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', + `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'sys_user' COMMENT '用户类型(sys_user系统用户)', + `user_plan` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'Free' COMMENT '用户套餐', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像地址', + `wx_avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信头像地址', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `domain_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册域名', + `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, NULL, '1', 100.00, '00000', 103, 'admin', '熊猫助手', 'sys_user', 'Free', 'ageerle@163.com', '15888888888', '0', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/10/07/09bd580f55954b50a3093231945123e0.jpg', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '0:0:0:0:0:0:0:1', '2024-12-26 20:27:52', NULL, 103, 1, '2023-05-14 15:19:39', 1, '2024-12-26 20:27:52', '管理员'); +INSERT INTO `sys_user` VALUES (1714176194496339970, NULL, '1', 88.88, '00000', NULL, 'pandarobot@163.com', '问答助手', 'sys_user', 'Free', '', '', '0', 'http://panda-1253683406.cos.ap-guangzhou.myqcloud.com/panda/2024/04/28/346796f5c32744c1987bf28d5820325b.jpg', NULL, '$2a$10$rxKsOfft6w7yywmpngroo.2/9y8Rucc9uj1rdc5wPg9dlwe9mITIi', '0', '0', '127.0.0.1', '2024-12-27 23:40:21', NULL, 103, 1713440206715650049, '2023-10-17 15:07:07', 1714176194496339970, '2024-12-27 23:40:21', NULL); + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_post`; +CREATE TABLE `sys_user_post` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `post_id` bigint(20) NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +INSERT INTO `sys_user_post` VALUES (1, 1); +INSERT INTO `sys_user_post` VALUES (2, 2); +INSERT INTO `sys_user_post` VALUES (1661660085084250114, 2); +INSERT INTO `sys_user_post` VALUES (1661660804847788034, 1); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (2, 2); +INSERT INTO `sys_user_role` VALUES (3, 3); +INSERT INTO `sys_user_role` VALUES (4, 4); +INSERT INTO `sys_user_role` VALUES (1661646824293031937, 1661661183933177857); +INSERT INTO `sys_user_role` VALUES (1661660085084250114, 1661661183933177857); +INSERT INTO `sys_user_role` VALUES (1661660804847788034, 2); +INSERT INTO `sys_user_role` VALUES (1713427806956404738, 1); +INSERT INTO `sys_user_role` VALUES (1713439839684689921, 1); +INSERT INTO `sys_user_role` VALUES (1713440206715650049, 1); +INSERT INTO `sys_user_role` VALUES (1714176194496339970, 1); +INSERT INTO `sys_user_role` VALUES (1714267685998907393, 1); +INSERT INTO `sys_user_role` VALUES (1714269581270667265, 1); +INSERT INTO `sys_user_role` VALUES (1714270420659949569, 1); +INSERT INTO `sys_user_role` VALUES (1714455864827723777, 1); +INSERT INTO `sys_user_role` VALUES (1714536425072115714, 1); +INSERT INTO `sys_user_role` VALUES (1714819715117105153, 1); +INSERT INTO `sys_user_role` VALUES (1714820415783976961, 1); +INSERT INTO `sys_user_role` VALUES (1714820611611836417, 1); +INSERT INTO `sys_user_role` VALUES (1714820755698761729, 1); +INSERT INTO `sys_user_role` VALUES (1714823588305190914, 1); +INSERT INTO `sys_user_role` VALUES (1714829502936530945, 1); +INSERT INTO `sys_user_role` VALUES (1714898663033290754, 1); +INSERT INTO `sys_user_role` VALUES (1714942733206175746, 1); +INSERT INTO `sys_user_role` VALUES (1714943378361434113, 1); +INSERT INTO `sys_user_role` VALUES (1714943388671033346, 1); +INSERT INTO `sys_user_role` VALUES (1714945928464711682, 1); +INSERT INTO `sys_user_role` VALUES (1714946100850606082, 1); +INSERT INTO `sys_user_role` VALUES (1714952355237347329, 1); +INSERT INTO `sys_user_role` VALUES (1714954192279584770, 1); +INSERT INTO `sys_user_role` VALUES (1714960721598758913, 1); +INSERT INTO `sys_user_role` VALUES (1714961357132283906, 1); +INSERT INTO `sys_user_role` VALUES (1714963426656403458, 1); +INSERT INTO `sys_user_role` VALUES (1714980339130318850, 1); +INSERT INTO `sys_user_role` VALUES (1714985002550444034, 1); +INSERT INTO `sys_user_role` VALUES (1714996959085084674, 1); +INSERT INTO `sys_user_role` VALUES (1715000784541990913, 1); +INSERT INTO `sys_user_role` VALUES (1715160830886297602, 1); +INSERT INTO `sys_user_role` VALUES (1715174792021426177, 1); +INSERT INTO `sys_user_role` VALUES (1715176760861278209, 1); +INSERT INTO `sys_user_role` VALUES (1715187418688405506, 1); +INSERT INTO `sys_user_role` VALUES (1715263570077564930, 1); +INSERT INTO `sys_user_role` VALUES (1715273299113820162, 1); +INSERT INTO `sys_user_role` VALUES (1715289765028577281, 1); +INSERT INTO `sys_user_role` VALUES (1715642509052624897, 1); +INSERT INTO `sys_user_role` VALUES (1715645217792868353, 1); +INSERT INTO `sys_user_role` VALUES (1715655140035543041, 1); +INSERT INTO `sys_user_role` VALUES (1715688813166346242, 1); +INSERT INTO `sys_user_role` VALUES (1715695623109623810, 1); +INSERT INTO `sys_user_role` VALUES (1716076523383177217, 1); +INSERT INTO `sys_user_role` VALUES (1716077329079615490, 1); +INSERT INTO `sys_user_role` VALUES (1716316658037178370, 1); +INSERT INTO `sys_user_role` VALUES (1716375479287824386, 1); +INSERT INTO `sys_user_role` VALUES (1716376929359380482, 1); +INSERT INTO `sys_user_role` VALUES (1716449431389487106, 1); +INSERT INTO `sys_user_role` VALUES (1716626232627707906, 1); +INSERT INTO `sys_user_role` VALUES (1716668774639484929, 1); +INSERT INTO `sys_user_role` VALUES (1716723582348050434, 1); +INSERT INTO `sys_user_role` VALUES (1717010625036828674, 1); +INSERT INTO `sys_user_role` VALUES (1717112818712723458, 1); +INSERT INTO `sys_user_role` VALUES (1717171039955599361, 1); +INSERT INTO `sys_user_role` VALUES (1717382776042569730, 1); +INSERT INTO `sys_user_role` VALUES (1717383874597896194, 1); +INSERT INTO `sys_user_role` VALUES (1717463477270102018, 1); +INSERT INTO `sys_user_role` VALUES (1717550755342467074, 1); +INSERT INTO `sys_user_role` VALUES (1718643906618605569, 1); +INSERT INTO `sys_user_role` VALUES (1719357065528623105, 1); +INSERT INTO `sys_user_role` VALUES (1719629669720145921, 1); +INSERT INTO `sys_user_role` VALUES (1719631746265530370, 1); +INSERT INTO `sys_user_role` VALUES (1719969371128086529, 1); +INSERT INTO `sys_user_role` VALUES (1719994192431955970, 1); +INSERT INTO `sys_user_role` VALUES (1720001597920264194, 1); +INSERT INTO `sys_user_role` VALUES (1720054174099718145, 1); +INSERT INTO `sys_user_role` VALUES (1720373256426635265, 1); +INSERT INTO `sys_user_role` VALUES (1720615324298264578, 1); +INSERT INTO `sys_user_role` VALUES (1720966085100191746, 1); +INSERT INTO `sys_user_role` VALUES (1721433118342397954, 1); +INSERT INTO `sys_user_role` VALUES (1721798759096270850, 1); +INSERT INTO `sys_user_role` VALUES (1721869407395332097, 1); +INSERT INTO `sys_user_role` VALUES (1721869952080232450, 1); +INSERT INTO `sys_user_role` VALUES (1722083875718737921, 1); +INSERT INTO `sys_user_role` VALUES (1722126825769185282, 1); +INSERT INTO `sys_user_role` VALUES (1722453238653169665, 1); +INSERT INTO `sys_user_role` VALUES (1722501722198552577, 1); +INSERT INTO `sys_user_role` VALUES (1722546398997819394, 1); +INSERT INTO `sys_user_role` VALUES (1722635856464097281, 1); +INSERT INTO `sys_user_role` VALUES (1722652602847768578, 1); +INSERT INTO `sys_user_role` VALUES (1722787874222682114, 1); +INSERT INTO `sys_user_role` VALUES (1722799180870889473, 1); +INSERT INTO `sys_user_role` VALUES (1722872660475817986, 1); +INSERT INTO `sys_user_role` VALUES (1722874592401600514, 1); +INSERT INTO `sys_user_role` VALUES (1722883137289367554, 1); +INSERT INTO `sys_user_role` VALUES (1722918534182645762, 1); +INSERT INTO `sys_user_role` VALUES (1723173295586848769, 1); +INSERT INTO `sys_user_role` VALUES (1723222687891107841, 1); +INSERT INTO `sys_user_role` VALUES (1723224404040921089, 1); +INSERT INTO `sys_user_role` VALUES (1723225015520112641, 1); +INSERT INTO `sys_user_role` VALUES (1723278284531478529, 1); +INSERT INTO `sys_user_role` VALUES (1723330835209564161, 1); +INSERT INTO `sys_user_role` VALUES (1723708198137147393, 1); +INSERT INTO `sys_user_role` VALUES (1723754683843260417, 1); +INSERT INTO `sys_user_role` VALUES (1723878185250369537, 1); +INSERT INTO `sys_user_role` VALUES (1723940614634254337, 1); +INSERT INTO `sys_user_role` VALUES (1723975861757325314, 1); +INSERT INTO `sys_user_role` VALUES (1724306907803725826, 1); +INSERT INTO `sys_user_role` VALUES (1724308252862492673, 1); +INSERT INTO `sys_user_role` VALUES (1724382895124295681, 1); +INSERT INTO `sys_user_role` VALUES (1724727778758406145, 1); +INSERT INTO `sys_user_role` VALUES (1724815478295425026, 1); +INSERT INTO `sys_user_role` VALUES (1725026071145107458, 1); +INSERT INTO `sys_user_role` VALUES (1725026978817658881, 1); +INSERT INTO `sys_user_role` VALUES (1725043562961457154, 1); +INSERT INTO `sys_user_role` VALUES (1725058936893362178, 1); +INSERT INTO `sys_user_role` VALUES (1725363117009162242, 1); +INSERT INTO `sys_user_role` VALUES (1725538633251049474, 1); +INSERT INTO `sys_user_role` VALUES (1725564937467875329, 1); +INSERT INTO `sys_user_role` VALUES (1725891713243021314, 1); +INSERT INTO `sys_user_role` VALUES (1725905000621932546, 1); +INSERT INTO `sys_user_role` VALUES (1726440708294049793, 1); +INSERT INTO `sys_user_role` VALUES (1726443526979584002, 1); +INSERT INTO `sys_user_role` VALUES (1726445663797116929, 1); +INSERT INTO `sys_user_role` VALUES (1726452867329687553, 1); +INSERT INTO `sys_user_role` VALUES (1726472827451998209, 1); +INSERT INTO `sys_user_role` VALUES (1726479651370696705, 1); +INSERT INTO `sys_user_role` VALUES (1726487492674195458, 1); +INSERT INTO `sys_user_role` VALUES (1726496513055784961, 1); +INSERT INTO `sys_user_role` VALUES (1726498781398302722, 1); +INSERT INTO `sys_user_role` VALUES (1726506873632587778, 1); +INSERT INTO `sys_user_role` VALUES (1726529248394739714, 1); +INSERT INTO `sys_user_role` VALUES (1726578079102664705, 1); +INSERT INTO `sys_user_role` VALUES (1726582181383634946, 1); +INSERT INTO `sys_user_role` VALUES (1726583555672506369, 1); +INSERT INTO `sys_user_role` VALUES (1726596448690372609, 1); +INSERT INTO `sys_user_role` VALUES (1726599361261207553, 1); +INSERT INTO `sys_user_role` VALUES (1726604511749079041, 1); +INSERT INTO `sys_user_role` VALUES (1726606973822304258, 1); +INSERT INTO `sys_user_role` VALUES (1726609379524083713, 1); +INSERT INTO `sys_user_role` VALUES (1726616151265640450, 1); +INSERT INTO `sys_user_role` VALUES (1726775811478126594, 1); +INSERT INTO `sys_user_role` VALUES (1726795490141667329, 1); +INSERT INTO `sys_user_role` VALUES (1726798403169681410, 1); +INSERT INTO `sys_user_role` VALUES (1726830794655399937, 1); +INSERT INTO `sys_user_role` VALUES (1726862038013313026, 1); +INSERT INTO `sys_user_role` VALUES (1726919220696186882, 1); +INSERT INTO `sys_user_role` VALUES (1727140184050630658, 1); +INSERT INTO `sys_user_role` VALUES (1727506163368722433, 1); +INSERT INTO `sys_user_role` VALUES (1727518983086931969, 1); +INSERT INTO `sys_user_role` VALUES (1727580969606840321, 1); +INSERT INTO `sys_user_role` VALUES (1727590505323429890, 1); +INSERT INTO `sys_user_role` VALUES (1727918393172164609, 1); +INSERT INTO `sys_user_role` VALUES (1728249002000121857, 1); +INSERT INTO `sys_user_role` VALUES (1728680561446486017, 1); +INSERT INTO `sys_user_role` VALUES (1728964404182577153, 1); +INSERT INTO `sys_user_role` VALUES (1729020459675611137, 1); +INSERT INTO `sys_user_role` VALUES (1729051002043691009, 1); +INSERT INTO `sys_user_role` VALUES (1729423744832172033, 1); +INSERT INTO `sys_user_role` VALUES (1729429590291050497, 1); +INSERT INTO `sys_user_role` VALUES (1729685493222375426, 1729685491108446210); +INSERT INTO `sys_user_role` VALUES (1730050324466036738, 1); +INSERT INTO `sys_user_role` VALUES (1730102403335254018, 1); +INSERT INTO `sys_user_role` VALUES (1730129923250122754, 1); +INSERT INTO `sys_user_role` VALUES (1730155108925763586, 1); +INSERT INTO `sys_user_role` VALUES (1730273428207366145, 1); +INSERT INTO `sys_user_role` VALUES (1730498722784669697, 1); +INSERT INTO `sys_user_role` VALUES (1730815105229713410, 1); +INSERT INTO `sys_user_role` VALUES (1730858886951923714, 1); +INSERT INTO `sys_user_role` VALUES (1731357405659824130, 1); +INSERT INTO `sys_user_role` VALUES (1731475532557090818, 1); +INSERT INTO `sys_user_role` VALUES (1731480953627901953, 1); +INSERT INTO `sys_user_role` VALUES (1731502381106495490, 1); +INSERT INTO `sys_user_role` VALUES (1731524458442162177, 1); +INSERT INTO `sys_user_role` VALUES (1731524630094053377, 1); +INSERT INTO `sys_user_role` VALUES (1731524650293821441, 1); +INSERT INTO `sys_user_role` VALUES (1731529253710233601, 1); +INSERT INTO `sys_user_role` VALUES (1731559936046432258, 1); +INSERT INTO `sys_user_role` VALUES (1731564032228884482, 1); +INSERT INTO `sys_user_role` VALUES (1731565926737281026, 1); +INSERT INTO `sys_user_role` VALUES (1731566918589513729, 1); +INSERT INTO `sys_user_role` VALUES (1731567740094283778, 1); +INSERT INTO `sys_user_role` VALUES (1731575439263563777, 1); +INSERT INTO `sys_user_role` VALUES (1731583864055824385, 1); +INSERT INTO `sys_user_role` VALUES (1731588155382464513, 1); +INSERT INTO `sys_user_role` VALUES (1731589827840212993, 1); +INSERT INTO `sys_user_role` VALUES (1731635461435719682, 1); +INSERT INTO `sys_user_role` VALUES (1731668049902731266, 1); +INSERT INTO `sys_user_role` VALUES (1731922694168412162, 1); +INSERT INTO `sys_user_role` VALUES (1731944975456305153, 1); +INSERT INTO `sys_user_role` VALUES (1731949019394506753, 1); +INSERT INTO `sys_user_role` VALUES (1731951425054343170, 1); +INSERT INTO `sys_user_role` VALUES (1732000242621513729, 1); +INSERT INTO `sys_user_role` VALUES (1732027163380056066, 1); +INSERT INTO `sys_user_role` VALUES (1732289382269353985, 1); +INSERT INTO `sys_user_role` VALUES (1732289439282528258, 1); +INSERT INTO `sys_user_role` VALUES (1732289699585228801, 1); +INSERT INTO `sys_user_role` VALUES (1732290827173527553, 1); +INSERT INTO `sys_user_role` VALUES (1732291549344595969, 1); +INSERT INTO `sys_user_role` VALUES (1732293265184030721, 1); +INSERT INTO `sys_user_role` VALUES (1732329664117506049, 1); +INSERT INTO `sys_user_role` VALUES (1732334104450990081, 1); +INSERT INTO `sys_user_role` VALUES (1732578671045672962, 1); +INSERT INTO `sys_user_role` VALUES (1732584047426174978, 1); +INSERT INTO `sys_user_role` VALUES (1732608690321129474, 1); +INSERT INTO `sys_user_role` VALUES (1732678147815014401, 1); +INSERT INTO `sys_user_role` VALUES (1732731410102910977, 1); +INSERT INTO `sys_user_role` VALUES (1733005266763939841, 1); +INSERT INTO `sys_user_role` VALUES (1733016149837774850, 1); +INSERT INTO `sys_user_role` VALUES (1733053523871432705, 1); +INSERT INTO `sys_user_role` VALUES (1733061400367497218, 1); +INSERT INTO `sys_user_role` VALUES (1733167090469732353, 1); +INSERT INTO `sys_user_role` VALUES (1733298702729641986, 1); +INSERT INTO `sys_user_role` VALUES (1733488544511983617, 1); +INSERT INTO `sys_user_role` VALUES (1733720554119659521, 1); +INSERT INTO `sys_user_role` VALUES (1733846657777827842, 1); +INSERT INTO `sys_user_role` VALUES (1733859832720031745, 1); +INSERT INTO `sys_user_role` VALUES (1734137817339559938, 1); +INSERT INTO `sys_user_role` VALUES (1734227535762849793, 1); +INSERT INTO `sys_user_role` VALUES (1734492373726560257, 1); +INSERT INTO `sys_user_role` VALUES (1734508040978726914, 1); +INSERT INTO `sys_user_role` VALUES (1734513545461661697, 1); +INSERT INTO `sys_user_role` VALUES (1734581580998451202, 1); +INSERT INTO `sys_user_role` VALUES (1734751884580298754, 1); +INSERT INTO `sys_user_role` VALUES (1734781716483612674, 1); +INSERT INTO `sys_user_role` VALUES (1734833221987278849, 1); +INSERT INTO `sys_user_role` VALUES (1734834063154946050, 1); +INSERT INTO `sys_user_role` VALUES (1734880697666576386, 1); +INSERT INTO `sys_user_role` VALUES (1734891995888427009, 1); +INSERT INTO `sys_user_role` VALUES (1735132534701367297, 1); +INSERT INTO `sys_user_role` VALUES (1735242647239991298, 1); +INSERT INTO `sys_user_role` VALUES (1735486862444273666, 1); +INSERT INTO `sys_user_role` VALUES (1735487912727355394, 1); +INSERT INTO `sys_user_role` VALUES (1735542352767426561, 1); +INSERT INTO `sys_user_role` VALUES (1735551915889598466, 1); +INSERT INTO `sys_user_role` VALUES (1735616653411557377, 1); +INSERT INTO `sys_user_role` VALUES (1735835864146714626, 1); +INSERT INTO `sys_user_role` VALUES (1735953007769100289, 1); +INSERT INTO `sys_user_role` VALUES (1735960189784891393, 1); +INSERT INTO `sys_user_role` VALUES (1736265950381547522, 1); +INSERT INTO `sys_user_role` VALUES (1736577606684844034, 1); +INSERT INTO `sys_user_role` VALUES (1736638822375563266, 1); +INSERT INTO `sys_user_role` VALUES (1736779069306511361, 1); +INSERT INTO `sys_user_role` VALUES (1737028378602053634, 1); +INSERT INTO `sys_user_role` VALUES (1737271234797314050, 1); +INSERT INTO `sys_user_role` VALUES (1737315322405920770, 1); +INSERT INTO `sys_user_role` VALUES (1737445221154234370, 1); +INSERT INTO `sys_user_role` VALUES (1737452907568635906, 1); +INSERT INTO `sys_user_role` VALUES (1737453186955419649, 1); +INSERT INTO `sys_user_role` VALUES (1737717777685880833, 1); +INSERT INTO `sys_user_role` VALUES (1737768515594166274, 1); +INSERT INTO `sys_user_role` VALUES (1738108912170246145, 1); +INSERT INTO `sys_user_role` VALUES (1738118086488825858, 1); +INSERT INTO `sys_user_role` VALUES (1738520430804279297, 1); +INSERT INTO `sys_user_role` VALUES (1738802060248817666, 1); +INSERT INTO `sys_user_role` VALUES (1738812447119712257, 1); +INSERT INTO `sys_user_role` VALUES (1738941480197234689, 1); +INSERT INTO `sys_user_role` VALUES (1738963430776840194, 1); +INSERT INTO `sys_user_role` VALUES (1739121784341995522, 1); +INSERT INTO `sys_user_role` VALUES (1739166931951886338, 1); +INSERT INTO `sys_user_role` VALUES (1739272055240073217, 1); +INSERT INTO `sys_user_role` VALUES (1739451838930427905, 1); +INSERT INTO `sys_user_role` VALUES (1739452037375533057, 1); +INSERT INTO `sys_user_role` VALUES (1739452376946384898, 1); +INSERT INTO `sys_user_role` VALUES (1739484503888961537, 1); +INSERT INTO `sys_user_role` VALUES (1739485282335006722, 1); +INSERT INTO `sys_user_role` VALUES (1739577551431999490, 1); +INSERT INTO `sys_user_role` VALUES (1739825609910591489, 1); +INSERT INTO `sys_user_role` VALUES (1739916453439152130, 1); +INSERT INTO `sys_user_role` VALUES (1740188388454629378, 1); +INSERT INTO `sys_user_role` VALUES (1741339991320580097, 1); +INSERT INTO `sys_user_role` VALUES (1741803737633542145, 1); +INSERT INTO `sys_user_role` VALUES (1741823858229923841, 1); +INSERT INTO `sys_user_role` VALUES (1741845883943227393, 1); +INSERT INTO `sys_user_role` VALUES (1742179775941201921, 1); +INSERT INTO `sys_user_role` VALUES (1742437553771458562, 1); +INSERT INTO `sys_user_role` VALUES (1742451201315254273, 1); +INSERT INTO `sys_user_role` VALUES (1742469913120419841, 1); +INSERT INTO `sys_user_role` VALUES (1742798283280568321, 1); +INSERT INTO `sys_user_role` VALUES (1742798987701342210, 1); +INSERT INTO `sys_user_role` VALUES (1742799476950126594, 1); +INSERT INTO `sys_user_role` VALUES (1742799839619010562, 1); +INSERT INTO `sys_user_role` VALUES (1742801019527057410, 1); +INSERT INTO `sys_user_role` VALUES (1742804073915699202, 1); +INSERT INTO `sys_user_role` VALUES (1742821280687149058, 1); +INSERT INTO `sys_user_role` VALUES (1742821467476283394, 1); +INSERT INTO `sys_user_role` VALUES (1742822775600009217, 1); +INSERT INTO `sys_user_role` VALUES (1742823890928357377, 1); +INSERT INTO `sys_user_role` VALUES (1742838225297821697, 1); +INSERT INTO `sys_user_role` VALUES (1742902317295423490, 1); +INSERT INTO `sys_user_role` VALUES (1742910854243373058, 1); +INSERT INTO `sys_user_role` VALUES (1742961994725150721, 1); +INSERT INTO `sys_user_role` VALUES (1742969861079388161, 1); +INSERT INTO `sys_user_role` VALUES (1743068363130228737, 1); +INSERT INTO `sys_user_role` VALUES (1743075924621479938, 1); +INSERT INTO `sys_user_role` VALUES (1743079200725225474, 1); +INSERT INTO `sys_user_role` VALUES (1743085878682144769, 1); +INSERT INTO `sys_user_role` VALUES (1743110774967586818, 1); +INSERT INTO `sys_user_role` VALUES (1743162481042870274, 1); +INSERT INTO `sys_user_role` VALUES (1743166491284033537, 1); +INSERT INTO `sys_user_role` VALUES (1743251016219447297, 1); +INSERT INTO `sys_user_role` VALUES (1743469820367142914, 1); +INSERT INTO `sys_user_role` VALUES (1743514389280522242, 1); +INSERT INTO `sys_user_role` VALUES (1743519646916083714, 1); +INSERT INTO `sys_user_role` VALUES (1743670356026654722, 1); +INSERT INTO `sys_user_role` VALUES (1743892570516815874, 1); +INSERT INTO `sys_user_role` VALUES (1743952049409146882, 1); +INSERT INTO `sys_user_role` VALUES (1744268693259993089, 1); +INSERT INTO `sys_user_role` VALUES (1744351384550567938, 1); +INSERT INTO `sys_user_role` VALUES (1744561041202278402, 1); +INSERT INTO `sys_user_role` VALUES (1744574752277196801, 1); +INSERT INTO `sys_user_role` VALUES (1744619123995373569, 1); +INSERT INTO `sys_user_role` VALUES (1744627110742913025, 1); +INSERT INTO `sys_user_role` VALUES (1744634408357916673, 1); +INSERT INTO `sys_user_role` VALUES (1744645281965207554, 1); +INSERT INTO `sys_user_role` VALUES (1744724410316156930, 1); +INSERT INTO `sys_user_role` VALUES (1744892307919400962, 1); +INSERT INTO `sys_user_role` VALUES (1744903174606090241, 1); +INSERT INTO `sys_user_role` VALUES (1744904968014983169, 1); +INSERT INTO `sys_user_role` VALUES (1744905787204497410, 1); +INSERT INTO `sys_user_role` VALUES (1744911513595473921, 1); +INSERT INTO `sys_user_role` VALUES (1744912178359103490, 1); +INSERT INTO `sys_user_role` VALUES (1744912486720139266, 1); +INSERT INTO `sys_user_role` VALUES (1744915552240463874, 1); +INSERT INTO `sys_user_role` VALUES (1744923917133869058, 1); +INSERT INTO `sys_user_role` VALUES (1744971513579761666, 1); +INSERT INTO `sys_user_role` VALUES (1744984070818426882, 1); +INSERT INTO `sys_user_role` VALUES (1744984147393835010, 1); +INSERT INTO `sys_user_role` VALUES (1744992401243041793, 1); +INSERT INTO `sys_user_role` VALUES (1745011131444424706, 1); +INSERT INTO `sys_user_role` VALUES (1745061549180514306, 1); +INSERT INTO `sys_user_role` VALUES (1745346479991091201, 1); +INSERT INTO `sys_user_role` VALUES (1745346822607007745, 1); +INSERT INTO `sys_user_role` VALUES (1745368346374217730, 1); +INSERT INTO `sys_user_role` VALUES (1745424741765259266, 1); +INSERT INTO `sys_user_role` VALUES (1745426757090582530, 1); +INSERT INTO `sys_user_role` VALUES (1745620173124575234, 1); +INSERT INTO `sys_user_role` VALUES (1745623876426571777, 1); +INSERT INTO `sys_user_role` VALUES (1745654577691664386, 1); +INSERT INTO `sys_user_role` VALUES (1745663259879972865, 1); +INSERT INTO `sys_user_role` VALUES (1745686038692012034, 1); +INSERT INTO `sys_user_role` VALUES (1745738268480675842, 1); +INSERT INTO `sys_user_role` VALUES (1745790952546017281, 1); +INSERT INTO `sys_user_role` VALUES (1746397384551211009, 1); +INSERT INTO `sys_user_role` VALUES (1746400980533551105, 1); +INSERT INTO `sys_user_role` VALUES (1746522414111039489, 1); +INSERT INTO `sys_user_role` VALUES (1746873386528223234, 1); +INSERT INTO `sys_user_role` VALUES (1747067318369333249, 1); +INSERT INTO `sys_user_role` VALUES (1747071365822361602, 1); +INSERT INTO `sys_user_role` VALUES (1747153912031948801, 1); +INSERT INTO `sys_user_role` VALUES (1747197655195922434, 1); +INSERT INTO `sys_user_role` VALUES (1747519480203390977, 1); +INSERT INTO `sys_user_role` VALUES (1747521265550831618, 1); +INSERT INTO `sys_user_role` VALUES (1747523421662162945, 1); +INSERT INTO `sys_user_role` VALUES (1747797864993075201, 1); +INSERT INTO `sys_user_role` VALUES (1747800427213697025, 1); +INSERT INTO `sys_user_role` VALUES (1747910191046275073, 1); +INSERT INTO `sys_user_role` VALUES (1747923453217419265, 1); +INSERT INTO `sys_user_role` VALUES (1748187110132232193, 1); +INSERT INTO `sys_user_role` VALUES (1748260926648823809, 1); +INSERT INTO `sys_user_role` VALUES (1748276826697445377, 1); +INSERT INTO `sys_user_role` VALUES (1748312313952808962, 1); +INSERT INTO `sys_user_role` VALUES (1748635584837529601, 1); +INSERT INTO `sys_user_role` VALUES (1748642479459610625, 1); +INSERT INTO `sys_user_role` VALUES (1748663294624346114, 1); +INSERT INTO `sys_user_role` VALUES (1748703876608503810, 1); +INSERT INTO `sys_user_role` VALUES (1748704145589219329, 1); +INSERT INTO `sys_user_role` VALUES (1748708285178523649, 1); +INSERT INTO `sys_user_role` VALUES (1748728575929430017, 1); +INSERT INTO `sys_user_role` VALUES (1748761666442047490, 1); +INSERT INTO `sys_user_role` VALUES (1748925826178035713, 1); +INSERT INTO `sys_user_role` VALUES (1749259130492235778, 1); +INSERT INTO `sys_user_role` VALUES (1749280237328871426, 1); +INSERT INTO `sys_user_role` VALUES (1749289400549322754, 1); +INSERT INTO `sys_user_role` VALUES (1749327661225291778, 1); +INSERT INTO `sys_user_role` VALUES (1749365593797636097, 1); +INSERT INTO `sys_user_role` VALUES (1749407786692325378, 1); +INSERT INTO `sys_user_role` VALUES (1749519043344805890, 1); +INSERT INTO `sys_user_role` VALUES (1749683041063219202, 1); +INSERT INTO `sys_user_role` VALUES (1749683546774646786, 1); +INSERT INTO `sys_user_role` VALUES (1749691765567860737, 1); +INSERT INTO `sys_user_role` VALUES (1749705571236917249, 1); +INSERT INTO `sys_user_role` VALUES (1749740828837359618, 1); +INSERT INTO `sys_user_role` VALUES (1749741179162406914, 1); +INSERT INTO `sys_user_role` VALUES (1749741340039131137, 1); +INSERT INTO `sys_user_role` VALUES (1749747618241130497, 1); +INSERT INTO `sys_user_role` VALUES (1749747701439344641, 1); +INSERT INTO `sys_user_role` VALUES (1749786825391157250, 1); +INSERT INTO `sys_user_role` VALUES (1749789665819963394, 1); +INSERT INTO `sys_user_role` VALUES (1749797707705823234, 1); +INSERT INTO `sys_user_role` VALUES (1749974903762210818, 1); +INSERT INTO `sys_user_role` VALUES (1749982777750081537, 1); +INSERT INTO `sys_user_role` VALUES (1749990634667134978, 1); +INSERT INTO `sys_user_role` VALUES (1749991325137653761, 1); +INSERT INTO `sys_user_role` VALUES (1749992779328016386, 1); +INSERT INTO `sys_user_role` VALUES (1749993573204905985, 1); +INSERT INTO `sys_user_role` VALUES (1749994406877351937, 1); +INSERT INTO `sys_user_role` VALUES (1749995279187726337, 1); +INSERT INTO `sys_user_role` VALUES (1749995486029828097, 1); +INSERT INTO `sys_user_role` VALUES (1749995707686211586, 1); +INSERT INTO `sys_user_role` VALUES (1750000406883749890, 1); +INSERT INTO `sys_user_role` VALUES (1750000942706085889, 1); +INSERT INTO `sys_user_role` VALUES (1750005079111913473, 1); +INSERT INTO `sys_user_role` VALUES (1750428606466117633, 1); +INSERT INTO `sys_user_role` VALUES (1750553534423126017, 1); +INSERT INTO `sys_user_role` VALUES (1750690119441469441, 1); +INSERT INTO `sys_user_role` VALUES (1750723725312413698, 1); +INSERT INTO `sys_user_role` VALUES (1750724537434525697, 1); +INSERT INTO `sys_user_role` VALUES (1750743381616119810, 1); +INSERT INTO `sys_user_role` VALUES (1750822931356192769, 1); +INSERT INTO `sys_user_role` VALUES (1750823004563574785, 1); +INSERT INTO `sys_user_role` VALUES (1751548639330177026, 1); +INSERT INTO `sys_user_role` VALUES (1751796140318658561, 1); +INSERT INTO `sys_user_role` VALUES (1751889049818763265, 1); +INSERT INTO `sys_user_role` VALUES (1751896081141600258, 1); +INSERT INTO `sys_user_role` VALUES (1751949653564723201, 1); +INSERT INTO `sys_user_role` VALUES (1751955373517443073, 1); +INSERT INTO `sys_user_role` VALUES (1751980511470292993, 1); +INSERT INTO `sys_user_role` VALUES (1752128867307884546, 1); +INSERT INTO `sys_user_role` VALUES (1752128948195037185, 1); +INSERT INTO `sys_user_role` VALUES (1752138835683708930, 1); +INSERT INTO `sys_user_role` VALUES (1752148500127682561, 1); +INSERT INTO `sys_user_role` VALUES (1752276638077816834, 1); +INSERT INTO `sys_user_role` VALUES (1752299834210521089, 1); +INSERT INTO `sys_user_role` VALUES (1752306117726703618, 1); +INSERT INTO `sys_user_role` VALUES (1752504006021222402, 1); +INSERT INTO `sys_user_role` VALUES (1752602885546840066, 1); +INSERT INTO `sys_user_role` VALUES (1752724639351050242, 1); +INSERT INTO `sys_user_role` VALUES (1753215436756357122, 1); +INSERT INTO `sys_user_role` VALUES (1753402656570216449, 1); +INSERT INTO `sys_user_role` VALUES (1753486557368029185, 1); +INSERT INTO `sys_user_role` VALUES (1753797902466551809, 1); +INSERT INTO `sys_user_role` VALUES (1753967757819908098, 1); +INSERT INTO `sys_user_role` VALUES (1754016754462887938, 1); +INSERT INTO `sys_user_role` VALUES (1754029247868440577, 1); +INSERT INTO `sys_user_role` VALUES (1754413960445562882, 1); +INSERT INTO `sys_user_role` VALUES (1754424078633537538, 1); +INSERT INTO `sys_user_role` VALUES (1754764137119354881, 1); +INSERT INTO `sys_user_role` VALUES (1755042084761899009, 1); +INSERT INTO `sys_user_role` VALUES (1755047141691625473, 1); +INSERT INTO `sys_user_role` VALUES (1756274975479173121, 1); +INSERT INTO `sys_user_role` VALUES (1756308183021260801, 1); +INSERT INTO `sys_user_role` VALUES (1757325877958938626, 1); +INSERT INTO `sys_user_role` VALUES (1758445439802675202, 1); +INSERT INTO `sys_user_role` VALUES (1759032628991234049, 1); +INSERT INTO `sys_user_role` VALUES (1759050804781125634, 1); +INSERT INTO `sys_user_role` VALUES (1759089524834045954, 1); +INSERT INTO `sys_user_role` VALUES (1759092949802029057, 1); +INSERT INTO `sys_user_role` VALUES (1759100324189573121, 1); +INSERT INTO `sys_user_role` VALUES (1759103449889771521, 1); +INSERT INTO `sys_user_role` VALUES (1759147026191749121, 1); +INSERT INTO `sys_user_role` VALUES (1759413482020147202, 1); +INSERT INTO `sys_user_role` VALUES (1759427862430486529, 1); +INSERT INTO `sys_user_role` VALUES (1759428010174844929, 1); +INSERT INTO `sys_user_role` VALUES (1759496088514465794, 1); +INSERT INTO `sys_user_role` VALUES (1759764705965510657, 1); +INSERT INTO `sys_user_role` VALUES (1759777481207320578, 1); +INSERT INTO `sys_user_role` VALUES (1759806155667279873, 1); +INSERT INTO `sys_user_role` VALUES (1759812015655227394, 1); +INSERT INTO `sys_user_role` VALUES (1759815447778693121, 1); +INSERT INTO `sys_user_role` VALUES (1759832486966726658, 1); +INSERT INTO `sys_user_role` VALUES (1759858071113830402, 1); +INSERT INTO `sys_user_role` VALUES (1759863475847827458, 1); +INSERT INTO `sys_user_role` VALUES (1759868018195173378, 1); +INSERT INTO `sys_user_role` VALUES (1759869729374736385, 1); +INSERT INTO `sys_user_role` VALUES (1760186079276175362, 1); +INSERT INTO `sys_user_role` VALUES (1760319626808922114, 1); +INSERT INTO `sys_user_role` VALUES (1760347236137963522, 1); +INSERT INTO `sys_user_role` VALUES (1760358546837868546, 1); +INSERT INTO `sys_user_role` VALUES (1760377107434180609, 1); +INSERT INTO `sys_user_role` VALUES (1760472305161998338, 1); +INSERT INTO `sys_user_role` VALUES (1760472829932343298, 1); +INSERT INTO `sys_user_role` VALUES (1760477732188721153, 1); +INSERT INTO `sys_user_role` VALUES (1760502088176504833, 1); +INSERT INTO `sys_user_role` VALUES (1760508166310203394, 1); +INSERT INTO `sys_user_role` VALUES (1760511294409543681, 1); +INSERT INTO `sys_user_role` VALUES (1760562604135682049, 1); +INSERT INTO `sys_user_role` VALUES (1760841877480280066, 1); +INSERT INTO `sys_user_role` VALUES (1760896840365510658, 1); +INSERT INTO `sys_user_role` VALUES (1760903600501428226, 1); +INSERT INTO `sys_user_role` VALUES (1761404022634844162, 1); +INSERT INTO `sys_user_role` VALUES (1761954868732891138, 1); +INSERT INTO `sys_user_role` VALUES (1761955584197267458, 1); +INSERT INTO `sys_user_role` VALUES (1762003524345401345, 1); +INSERT INTO `sys_user_role` VALUES (1762004833618366465, 1); +INSERT INTO `sys_user_role` VALUES (1762010183880937474, 1); +INSERT INTO `sys_user_role` VALUES (1762298283890839554, 1); +INSERT INTO `sys_user_role` VALUES (1762363188014747649, 1); +INSERT INTO `sys_user_role` VALUES (1762389902388367361, 1); +INSERT INTO `sys_user_role` VALUES (1762401081961746434, 1); +INSERT INTO `sys_user_role` VALUES (1762481911417540610, 1); +INSERT INTO `sys_user_role` VALUES (1762482221645041665, 1); +INSERT INTO `sys_user_role` VALUES (1762482243174404097, 1); +INSERT INTO `sys_user_role` VALUES (1762483838461153282, 1); +INSERT INTO `sys_user_role` VALUES (1762487212380262401, 1); +INSERT INTO `sys_user_role` VALUES (1762498553535008770, 1); +INSERT INTO `sys_user_role` VALUES (1762636163465138177, 1); +INSERT INTO `sys_user_role` VALUES (1762655625413185537, 1); +INSERT INTO `sys_user_role` VALUES (1762656108559257601, 1); +INSERT INTO `sys_user_role` VALUES (1762673833499217922, 1); +INSERT INTO `sys_user_role` VALUES (1762677825344163842, 1); +INSERT INTO `sys_user_role` VALUES (1762677876015550465, 1); +INSERT INTO `sys_user_role` VALUES (1762678082262061057, 1); +INSERT INTO `sys_user_role` VALUES (1762678138012749825, 1); +INSERT INTO `sys_user_role` VALUES (1762678144652333057, 1); +INSERT INTO `sys_user_role` VALUES (1762678174192816129, 1); +INSERT INTO `sys_user_role` VALUES (1762678472563019777, 1); +INSERT INTO `sys_user_role` VALUES (1762678534596775938, 1); +INSERT INTO `sys_user_role` VALUES (1762678534894571521, 1); +INSERT INTO `sys_user_role` VALUES (1762678581635895298, 1); +INSERT INTO `sys_user_role` VALUES (1762678844920745985, 1); +INSERT INTO `sys_user_role` VALUES (1762679194973163522, 1); +INSERT INTO `sys_user_role` VALUES (1762679425299173378, 1); +INSERT INTO `sys_user_role` VALUES (1762679810776682498, 1); +INSERT INTO `sys_user_role` VALUES (1762679862656028674, 1); +INSERT INTO `sys_user_role` VALUES (1762679937360777217, 1); +INSERT INTO `sys_user_role` VALUES (1762680184698884098, 1); +INSERT INTO `sys_user_role` VALUES (1762680290076577794, 1); +INSERT INTO `sys_user_role` VALUES (1762680350055124993, 1); +INSERT INTO `sys_user_role` VALUES (1762681014038614017, 1); +INSERT INTO `sys_user_role` VALUES (1762681042207559681, 1); +INSERT INTO `sys_user_role` VALUES (1762681082732924929, 1); +INSERT INTO `sys_user_role` VALUES (1762681088869191682, 1); +INSERT INTO `sys_user_role` VALUES (1762681283195490306, 1); +INSERT INTO `sys_user_role` VALUES (1762681876752420865, 1); +INSERT INTO `sys_user_role` VALUES (1762681980129431553, 1); +INSERT INTO `sys_user_role` VALUES (1762682038488977410, 1); +INSERT INTO `sys_user_role` VALUES (1762682208211488769, 1); +INSERT INTO `sys_user_role` VALUES (1762683406603833346, 1); +INSERT INTO `sys_user_role` VALUES (1762683500048732162, 1); +INSERT INTO `sys_user_role` VALUES (1762683740843724801, 1); +INSERT INTO `sys_user_role` VALUES (1762683806404890625, 1); +INSERT INTO `sys_user_role` VALUES (1762684131715108865, 1); +INSERT INTO `sys_user_role` VALUES (1762684408442703874, 1); +INSERT INTO `sys_user_role` VALUES (1762684686994821121, 1); +INSERT INTO `sys_user_role` VALUES (1762686405808017409, 1); +INSERT INTO `sys_user_role` VALUES (1762687370061729794, 1); +INSERT INTO `sys_user_role` VALUES (1762687537527705602, 1); +INSERT INTO `sys_user_role` VALUES (1762687814947360769, 1); +INSERT INTO `sys_user_role` VALUES (1762688734347186177, 1); +INSERT INTO `sys_user_role` VALUES (1762690035701305346, 1); +INSERT INTO `sys_user_role` VALUES (1762690104575971330, 1); +INSERT INTO `sys_user_role` VALUES (1762691273243283457, 1); +INSERT INTO `sys_user_role` VALUES (1762691277462753282, 1); +INSERT INTO `sys_user_role` VALUES (1762692468406013954, 1); +INSERT INTO `sys_user_role` VALUES (1762693304498573314, 1); +INSERT INTO `sys_user_role` VALUES (1762693710704332801, 1); +INSERT INTO `sys_user_role` VALUES (1762694382220791809, 1); +INSERT INTO `sys_user_role` VALUES (1762696242545610754, 1); +INSERT INTO `sys_user_role` VALUES (1762696275626086402, 1); +INSERT INTO `sys_user_role` VALUES (1762696945854894082, 1); +INSERT INTO `sys_user_role` VALUES (1762698940057702402, 1); +INSERT INTO `sys_user_role` VALUES (1762699511732948994, 1); +INSERT INTO `sys_user_role` VALUES (1762701338956320769, 1); +INSERT INTO `sys_user_role` VALUES (1762701352860438530, 1); +INSERT INTO `sys_user_role` VALUES (1762703221934575617, 1); +INSERT INTO `sys_user_role` VALUES (1762705239214444546, 1); +INSERT INTO `sys_user_role` VALUES (1762705858788642817, 1); +INSERT INTO `sys_user_role` VALUES (1762706220585111553, 1); +INSERT INTO `sys_user_role` VALUES (1762707979655237633, 1); +INSERT INTO `sys_user_role` VALUES (1762709372369686529, 1); +INSERT INTO `sys_user_role` VALUES (1762717698755186689, 1); +INSERT INTO `sys_user_role` VALUES (1762719280540471297, 1); +INSERT INTO `sys_user_role` VALUES (1762719395619590146, 1); +INSERT INTO `sys_user_role` VALUES (1762721161459322881, 1); +INSERT INTO `sys_user_role` VALUES (1762721300685049857, 1); +INSERT INTO `sys_user_role` VALUES (1762724284441612290, 1); +INSERT INTO `sys_user_role` VALUES (1762728759105474561, 1); +INSERT INTO `sys_user_role` VALUES (1762732886506131458, 1); +INSERT INTO `sys_user_role` VALUES (1762744418904354818, 1); +INSERT INTO `sys_user_role` VALUES (1762749711537188865, 1); +INSERT INTO `sys_user_role` VALUES (1762749741056700418, 1); +INSERT INTO `sys_user_role` VALUES (1762750396991320065, 1); +INSERT INTO `sys_user_role` VALUES (1762752966828797954, 1); +INSERT INTO `sys_user_role` VALUES (1762753464445218817, 1); +INSERT INTO `sys_user_role` VALUES (1762753558548623362, 1); +INSERT INTO `sys_user_role` VALUES (1762755306625478657, 1); +INSERT INTO `sys_user_role` VALUES (1762756726481268737, 1); +INSERT INTO `sys_user_role` VALUES (1762756744172843010, 1); +INSERT INTO `sys_user_role` VALUES (1762760948073410562, 1); +INSERT INTO `sys_user_role` VALUES (1762768424588062721, 1); +INSERT INTO `sys_user_role` VALUES (1762770353779159041, 1); +INSERT INTO `sys_user_role` VALUES (1762770690174922754, 1); +INSERT INTO `sys_user_role` VALUES (1762773352299671554, 1); +INSERT INTO `sys_user_role` VALUES (1762809323107954689, 1); +INSERT INTO `sys_user_role` VALUES (1762839585439133698, 1); +INSERT INTO `sys_user_role` VALUES (1762854389474177026, 1); +INSERT INTO `sys_user_role` VALUES (1762962461110611969, 1); +INSERT INTO `sys_user_role` VALUES (1763011242199920642, 1); +INSERT INTO `sys_user_role` VALUES (1763014994155843586, 1); +INSERT INTO `sys_user_role` VALUES (1763017291741048833, 1); +INSERT INTO `sys_user_role` VALUES (1763021759299760129, 1); +INSERT INTO `sys_user_role` VALUES (1763033286434140162, 1); +INSERT INTO `sys_user_role` VALUES (1763034914528735233, 1); +INSERT INTO `sys_user_role` VALUES (1763039329885138945, 1); +INSERT INTO `sys_user_role` VALUES (1763046791925248001, 1); +INSERT INTO `sys_user_role` VALUES (1763059898533851137, 1); +INSERT INTO `sys_user_role` VALUES (1763074956366229505, 1); +INSERT INTO `sys_user_role` VALUES (1763083906738335746, 1); +INSERT INTO `sys_user_role` VALUES (1763087371808059394, 1); +INSERT INTO `sys_user_role` VALUES (1763110723763351554, 1); +INSERT INTO `sys_user_role` VALUES (1763119583433633794, 1); +INSERT INTO `sys_user_role` VALUES (1763121912195100674, 1); +INSERT INTO `sys_user_role` VALUES (1763150617374142466, 1); +INSERT INTO `sys_user_role` VALUES (1763219512067928065, 1); +INSERT INTO `sys_user_role` VALUES (1763232955600777217, 1); +INSERT INTO `sys_user_role` VALUES (1763234635201425410, 1); +INSERT INTO `sys_user_role` VALUES (1763246126281568257, 1); +INSERT INTO `sys_user_role` VALUES (1763323873230106626, 1); +INSERT INTO `sys_user_role` VALUES (1763384782623387650, 1); +INSERT INTO `sys_user_role` VALUES (1763386804647014401, 1); +INSERT INTO `sys_user_role` VALUES (1763396269777661953, 1); +INSERT INTO `sys_user_role` VALUES (1763405607485353985, 1); +INSERT INTO `sys_user_role` VALUES (1763432831823425537, 1); +INSERT INTO `sys_user_role` VALUES (1763453676952268802, 1); +INSERT INTO `sys_user_role` VALUES (1763456811204653057, 1); +INSERT INTO `sys_user_role` VALUES (1763461579713064962, 1); +INSERT INTO `sys_user_role` VALUES (1763491204732379137, 1); +INSERT INTO `sys_user_role` VALUES (1763497378051612674, 1); +INSERT INTO `sys_user_role` VALUES (1763559058706096130, 1); +INSERT INTO `sys_user_role` VALUES (1763577018824876033, 1); +INSERT INTO `sys_user_role` VALUES (1763633124087521281, 1); +INSERT INTO `sys_user_role` VALUES (1763886812869775362, 1); +INSERT INTO `sys_user_role` VALUES (1763913997563285506, 1); +INSERT INTO `sys_user_role` VALUES (1764173595432013826, 1); +INSERT INTO `sys_user_role` VALUES (1764261292183998465, 1); +INSERT INTO `sys_user_role` VALUES (1764287995094585346, 1); +INSERT INTO `sys_user_role` VALUES (1764461290695774209, 1); +INSERT INTO `sys_user_role` VALUES (1764474718197993473, 1); +INSERT INTO `sys_user_role` VALUES (1764482496870305794, 1); +INSERT INTO `sys_user_role` VALUES (1764495637402439682, 1); +INSERT INTO `sys_user_role` VALUES (1764498159743619073, 1); +INSERT INTO `sys_user_role` VALUES (1764498751559913473, 1); +INSERT INTO `sys_user_role` VALUES (1764514945641828354, 1); +INSERT INTO `sys_user_role` VALUES (1764519088087453698, 1); +INSERT INTO `sys_user_role` VALUES (1764520899728986114, 1); +INSERT INTO `sys_user_role` VALUES (1764525084016988161, 1); +INSERT INTO `sys_user_role` VALUES (1764539443405475842, 1); +INSERT INTO `sys_user_role` VALUES (1764564174649249794, 1); +INSERT INTO `sys_user_role` VALUES (1764583176607977474, 1); +INSERT INTO `sys_user_role` VALUES (1764607755468505089, 1); +INSERT INTO `sys_user_role` VALUES (1764634462757920770, 1); +INSERT INTO `sys_user_role` VALUES (1764827973771915265, 1); +INSERT INTO `sys_user_role` VALUES (1764831906313596929, 1); +INSERT INTO `sys_user_role` VALUES (1764857801929715713, 1); +INSERT INTO `sys_user_role` VALUES (1764882243925913602, 1); +INSERT INTO `sys_user_role` VALUES (1764897874259816449, 1); +INSERT INTO `sys_user_role` VALUES (1764945289142677505, 1); +INSERT INTO `sys_user_role` VALUES (1764973230396354562, 1); +INSERT INTO `sys_user_role` VALUES (1765026702110044161, 1); +INSERT INTO `sys_user_role` VALUES (1765029529888829441, 1); +INSERT INTO `sys_user_role` VALUES (1765032464647532546, 1); +INSERT INTO `sys_user_role` VALUES (1765189908342321154, 1); +INSERT INTO `sys_user_role` VALUES (1765214567611838465, 1); +INSERT INTO `sys_user_role` VALUES (1765219002413035521, 1); +INSERT INTO `sys_user_role` VALUES (1765220951434801153, 1); +INSERT INTO `sys_user_role` VALUES (1765248990147325954, 1); +INSERT INTO `sys_user_role` VALUES (1765249652247572481, 1); +INSERT INTO `sys_user_role` VALUES (1765256689840893953, 1); +INSERT INTO `sys_user_role` VALUES (1765258070287003649, 1); +INSERT INTO `sys_user_role` VALUES (1765276219292069890, 1); +INSERT INTO `sys_user_role` VALUES (1765276256986279938, 1); +INSERT INTO `sys_user_role` VALUES (1765288006737539074, 1); +INSERT INTO `sys_user_role` VALUES (1765312970979094529, 1); +INSERT INTO `sys_user_role` VALUES (1765626857976840193, 1); +INSERT INTO `sys_user_role` VALUES (1765662415604236289, 1); +INSERT INTO `sys_user_role` VALUES (1765673187432546306, 1); +INSERT INTO `sys_user_role` VALUES (1765733893087510530, 1); +INSERT INTO `sys_user_role` VALUES (1765927148689326081, 1); +INSERT INTO `sys_user_role` VALUES (1765946481549279233, 1); +INSERT INTO `sys_user_role` VALUES (1765987575418880002, 1); +INSERT INTO `sys_user_role` VALUES (1765991619675848705, 1); +INSERT INTO `sys_user_role` VALUES (1765997037533822977, 1); +INSERT INTO `sys_user_role` VALUES (1766008273063411714, 1); +INSERT INTO `sys_user_role` VALUES (1766011496348286978, 1); +INSERT INTO `sys_user_role` VALUES (1766017335771561986, 1); +INSERT INTO `sys_user_role` VALUES (1766020112446947329, 1); +INSERT INTO `sys_user_role` VALUES (1766085955713269762, 1); +INSERT INTO `sys_user_role` VALUES (1766102635604639746, 1); +INSERT INTO `sys_user_role` VALUES (1766323008493355009, 1); +INSERT INTO `sys_user_role` VALUES (1766387294112612353, 1); +INSERT INTO `sys_user_role` VALUES (1766842982618136577, 1); +INSERT INTO `sys_user_role` VALUES (1767018925722730497, 1); +INSERT INTO `sys_user_role` VALUES (1767098572703563778, 1); +INSERT INTO `sys_user_role` VALUES (1767193870939488258, 1); +INSERT INTO `sys_user_role` VALUES (1767371461667356673, 1); +INSERT INTO `sys_user_role` VALUES (1767472876167397377, 1); +INSERT INTO `sys_user_role` VALUES (1767484503956684801, 1); +INSERT INTO `sys_user_role` VALUES (1767494435045146626, 1); +INSERT INTO `sys_user_role` VALUES (1767502928200368129, 1); +INSERT INTO `sys_user_role` VALUES (1767790695329333250, 1); +INSERT INTO `sys_user_role` VALUES (1767797421759823874, 1); +INSERT INTO `sys_user_role` VALUES (1767867514107756545, 1); +INSERT INTO `sys_user_role` VALUES (1768123513418842114, 1); +INSERT INTO `sys_user_role` VALUES (1768125846164897794, 1); +INSERT INTO `sys_user_role` VALUES (1768137512021688322, 1); +INSERT INTO `sys_user_role` VALUES (1768172797870768129, 1); +INSERT INTO `sys_user_role` VALUES (1768257272084463617, 1); +INSERT INTO `sys_user_role` VALUES (1768452168263172097, 1); +INSERT INTO `sys_user_role` VALUES (1768487959811096578, 1); +INSERT INTO `sys_user_role` VALUES (1768522172358754306, 1); +INSERT INTO `sys_user_role` VALUES (1768523379651411969, 1); +INSERT INTO `sys_user_role` VALUES (1768528826072596482, 1); +INSERT INTO `sys_user_role` VALUES (1768554562896560130, 1); +INSERT INTO `sys_user_role` VALUES (1768560191165988866, 1); +INSERT INTO `sys_user_role` VALUES (1768560307197214722, 1); +INSERT INTO `sys_user_role` VALUES (1768561334289989633, 1); +INSERT INTO `sys_user_role` VALUES (1768565063735083009, 1); +INSERT INTO `sys_user_role` VALUES (1768570261782167553, 1); +INSERT INTO `sys_user_role` VALUES (1768598711431626753, 1); +INSERT INTO `sys_user_role` VALUES (1768635967806668802, 1); +INSERT INTO `sys_user_role` VALUES (1768887604487946241, 1); +INSERT INTO `sys_user_role` VALUES (1768911351987077122, 1); +INSERT INTO `sys_user_role` VALUES (1769186172289449986, 1); +INSERT INTO `sys_user_role` VALUES (1769408371134857218, 1); +INSERT INTO `sys_user_role` VALUES (1769520576635371521, 1); +INSERT INTO `sys_user_role` VALUES (1769561862704758786, 1); +INSERT INTO `sys_user_role` VALUES (1769569234722521089, 1); +INSERT INTO `sys_user_role` VALUES (1769607528399273986, 1); +INSERT INTO `sys_user_role` VALUES (1769617177890553857, 1); +INSERT INTO `sys_user_role` VALUES (1769663440459694082, 1); +INSERT INTO `sys_user_role` VALUES (1769908456541233154, 1); +INSERT INTO `sys_user_role` VALUES (1769957357877043201, 1); +INSERT INTO `sys_user_role` VALUES (1770021611783168002, 1); +INSERT INTO `sys_user_role` VALUES (1770063295095087106, 1); +INSERT INTO `sys_user_role` VALUES (1770063700436819970, 1); +INSERT INTO `sys_user_role` VALUES (1770281104395837442, 1); +INSERT INTO `sys_user_role` VALUES (1770288338521661441, 1); +INSERT INTO `sys_user_role` VALUES (1770322814056333313, 1); +INSERT INTO `sys_user_role` VALUES (1770338641849679874, 1); +INSERT INTO `sys_user_role` VALUES (1770351581952802817, 1); +INSERT INTO `sys_user_role` VALUES (1770357305466486786, 1); +INSERT INTO `sys_user_role` VALUES (1770364755406028802, 1); +INSERT INTO `sys_user_role` VALUES (1770381062524436482, 1); +INSERT INTO `sys_user_role` VALUES (1770470677998534657, 1); +INSERT INTO `sys_user_role` VALUES (1770642413331218434, 1); +INSERT INTO `sys_user_role` VALUES (1770648858382630914, 1); +INSERT INTO `sys_user_role` VALUES (1770715116272680962, 1); +INSERT INTO `sys_user_role` VALUES (1770720646688997377, 1); +INSERT INTO `sys_user_role` VALUES (1770726609303175170, 1); +INSERT INTO `sys_user_role` VALUES (1770757521378181121, 1); +INSERT INTO `sys_user_role` VALUES (1770759021907214338, 1); +INSERT INTO `sys_user_role` VALUES (1771002145573240833, 1); +INSERT INTO `sys_user_role` VALUES (1771019340902629377, 1); +INSERT INTO `sys_user_role` VALUES (1771085212270788610, 1); +INSERT INTO `sys_user_role` VALUES (1771091102206066689, 1); +INSERT INTO `sys_user_role` VALUES (1771105696307806210, 1); +INSERT INTO `sys_user_role` VALUES (1771529088861274114, 1); +INSERT INTO `sys_user_role` VALUES (1772148936234565634, 1); +INSERT INTO `sys_user_role` VALUES (1772170742823714818, 1); +INSERT INTO `sys_user_role` VALUES (1772173596070313986, 1); +INSERT INTO `sys_user_role` VALUES (1772181791232819201, 1); +INSERT INTO `sys_user_role` VALUES (1772807697592832001, 1); +INSERT INTO `sys_user_role` VALUES (1772821509767254018, 1); +INSERT INTO `sys_user_role` VALUES (1772947270113251330, 1); +INSERT INTO `sys_user_role` VALUES (1773149840576434178, 1); +INSERT INTO `sys_user_role` VALUES (1773180693536919554, 1); +INSERT INTO `sys_user_role` VALUES (1773192472325345282, 1); +INSERT INTO `sys_user_role` VALUES (1773200350612377601, 1); +INSERT INTO `sys_user_role` VALUES (1773307685607395329, 1); +INSERT INTO `sys_user_role` VALUES (1773529379840282625, 1); +INSERT INTO `sys_user_role` VALUES (1773543535003914241, 1); +INSERT INTO `sys_user_role` VALUES (1773615949826052097, 1); +INSERT INTO `sys_user_role` VALUES (1773714968015278082, 1); +INSERT INTO `sys_user_role` VALUES (1773741523022123010, 1); +INSERT INTO `sys_user_role` VALUES (1773774290929848321, 1); +INSERT INTO `sys_user_role` VALUES (1773969452180258818, 1); +INSERT INTO `sys_user_role` VALUES (1774094144111198210, 1); +INSERT INTO `sys_user_role` VALUES (1774326191970926594, 1); +INSERT INTO `sys_user_role` VALUES (1774595110106685441, 1); +INSERT INTO `sys_user_role` VALUES (1774603290157113346, 1); +INSERT INTO `sys_user_role` VALUES (1774671916088287233, 1); +INSERT INTO `sys_user_role` VALUES (1774712059876728833, 1); +INSERT INTO `sys_user_role` VALUES (1775005868787359746, 1); +INSERT INTO `sys_user_role` VALUES (1775039514470637569, 1); +INSERT INTO `sys_user_role` VALUES (1775046202846208002, 1); +INSERT INTO `sys_user_role` VALUES (1775055115012399106, 1); +INSERT INTO `sys_user_role` VALUES (1775058985780371458, 1); +INSERT INTO `sys_user_role` VALUES (1775066829695082497, 1); +INSERT INTO `sys_user_role` VALUES (1775078808497283074, 1); +INSERT INTO `sys_user_role` VALUES (1775109977754427393, 1); +INSERT INTO `sys_user_role` VALUES (1775109977771204609, 1); +INSERT INTO `sys_user_role` VALUES (1775192704981786626, 1); +INSERT INTO `sys_user_role` VALUES (1775421589681987586, 1); +INSERT INTO `sys_user_role` VALUES (1776124571507613697, 1); +INSERT INTO `sys_user_role` VALUES (1776550027549597698, 1); +INSERT INTO `sys_user_role` VALUES (1776815081159254018, 1); +INSERT INTO `sys_user_role` VALUES (1776827459129171969, 1); +INSERT INTO `sys_user_role` VALUES (1776861348769947650, 1); +INSERT INTO `sys_user_role` VALUES (1776864185373548546, 1); +INSERT INTO `sys_user_role` VALUES (1776871215274516482, 1); +INSERT INTO `sys_user_role` VALUES (1776872376396275714, 1); +INSERT INTO `sys_user_role` VALUES (1776889562355589122, 1); +INSERT INTO `sys_user_role` VALUES (1777118704363757570, 1); +INSERT INTO `sys_user_role` VALUES (1777126438664527874, 1); +INSERT INTO `sys_user_role` VALUES (1777157190659727362, 1); +INSERT INTO `sys_user_role` VALUES (1777217669537062914, 1); +INSERT INTO `sys_user_role` VALUES (1777220647320936449, 1); +INSERT INTO `sys_user_role` VALUES (1777252116550508545, 1); +INSERT INTO `sys_user_role` VALUES (1777260896986193921, 1); +INSERT INTO `sys_user_role` VALUES (1777296499484254210, 1); +INSERT INTO `sys_user_role` VALUES (1777301747972038657, 1); +INSERT INTO `sys_user_role` VALUES (1777363539016409089, 1); +INSERT INTO `sys_user_role` VALUES (1777483372982820866, 1); +INSERT INTO `sys_user_role` VALUES (1777537906459402242, 1); +INSERT INTO `sys_user_role` VALUES (1777610641428570114, 1); +INSERT INTO `sys_user_role` VALUES (1777613556604067842, 1); +INSERT INTO `sys_user_role` VALUES (1777718773123244034, 1); +INSERT INTO `sys_user_role` VALUES (1777743939492503554, 1); +INSERT INTO `sys_user_role` VALUES (1777887539056467969, 1); +INSERT INTO `sys_user_role` VALUES (1777887799262699521, 1); +INSERT INTO `sys_user_role` VALUES (1777890253115088897, 1); +INSERT INTO `sys_user_role` VALUES (1777909423068274689, 1); +INSERT INTO `sys_user_role` VALUES (1777930481544585218, 1); +INSERT INTO `sys_user_role` VALUES (1777954050559303681, 1); +INSERT INTO `sys_user_role` VALUES (1778078614597525506, 1); +INSERT INTO `sys_user_role` VALUES (1778307871026307073, 1); +INSERT INTO `sys_user_role` VALUES (1778341191034462209, 1); +INSERT INTO `sys_user_role` VALUES (1778352526686281729, 1); +INSERT INTO `sys_user_role` VALUES (1778591039688138754, 1); +INSERT INTO `sys_user_role` VALUES (1778625241280274433, 1); +INSERT INTO `sys_user_role` VALUES (1778645603636338689, 1); +INSERT INTO `sys_user_role` VALUES (1779329016437530626, 1); +INSERT INTO `sys_user_role` VALUES (1779509451201306625, 1); +INSERT INTO `sys_user_role` VALUES (1781359789389049858, 1); +INSERT INTO `sys_user_role` VALUES (1781463900025450497, 1); +INSERT INTO `sys_user_role` VALUES (1781519961809940482, 1); +INSERT INTO `sys_user_role` VALUES (1781570458679963650, 1); +INSERT INTO `sys_user_role` VALUES (1781679536911609858, 1); +INSERT INTO `sys_user_role` VALUES (1781680345497923586, 1); +INSERT INTO `sys_user_role` VALUES (1781938051479711745, 1); +INSERT INTO `sys_user_role` VALUES (1781979644345659393, 1); +INSERT INTO `sys_user_role` VALUES (1781982608724537345, 1); +INSERT INTO `sys_user_role` VALUES (1782339521316294658, 1); +INSERT INTO `sys_user_role` VALUES (1782584811885596674, 1); +INSERT INTO `sys_user_role` VALUES (1782597966938411009, 1); +INSERT INTO `sys_user_role` VALUES (1782598345608564738, 1); +INSERT INTO `sys_user_role` VALUES (1782599696132509698, 1); +INSERT INTO `sys_user_role` VALUES (1782655923667505153, 1); +INSERT INTO `sys_user_role` VALUES (1782658558470557698, 1); +INSERT INTO `sys_user_role` VALUES (1782697212870037505, 1); +INSERT INTO `sys_user_role` VALUES (1782711689380270082, 1); +INSERT INTO `sys_user_role` VALUES (1782733890905083906, 1); +INSERT INTO `sys_user_role` VALUES (1782734018948796418, 1); +INSERT INTO `sys_user_role` VALUES (1782741134992379906, 1); +INSERT INTO `sys_user_role` VALUES (1782926062560382978, 1); +INSERT INTO `sys_user_role` VALUES (1782941277477834753, 1); +INSERT INTO `sys_user_role` VALUES (1782982532157050881, 1); +INSERT INTO `sys_user_role` VALUES (1783068876598317057, 1); +INSERT INTO `sys_user_role` VALUES (1783086777506107393, 1); +INSERT INTO `sys_user_role` VALUES (1783144268357079041, 1); +INSERT INTO `sys_user_role` VALUES (1783297415947915265, 1); +INSERT INTO `sys_user_role` VALUES (1783310569679523841, 1); +INSERT INTO `sys_user_role` VALUES (1783326930816372738, 1); +INSERT INTO `sys_user_role` VALUES (1783358421143293953, 1); +INSERT INTO `sys_user_role` VALUES (1783421941125910530, 1); +INSERT INTO `sys_user_role` VALUES (1783439451980206081, 1); +INSERT INTO `sys_user_role` VALUES (1783471940098494466, 1); +INSERT INTO `sys_user_role` VALUES (1783777388311777281, 1); +INSERT INTO `sys_user_role` VALUES (1783796572785643521, 1); +INSERT INTO `sys_user_role` VALUES (1783877442208960514, 1); +INSERT INTO `sys_user_role` VALUES (1784199358216048642, 1); +INSERT INTO `sys_user_role` VALUES (1784389326918029313, 1); +INSERT INTO `sys_user_role` VALUES (1784400528377286657, 1); +INSERT INTO `sys_user_role` VALUES (1784435756558880770, 1); +INSERT INTO `sys_user_role` VALUES (1784457537797656577, 1); +INSERT INTO `sys_user_role` VALUES (1784521057603538945, 1); +INSERT INTO `sys_user_role` VALUES (1784522252246724609, 1); +INSERT INTO `sys_user_role` VALUES (1784548227567202306, 1); +INSERT INTO `sys_user_role` VALUES (1784569508068995073, 1); +INSERT INTO `sys_user_role` VALUES (1784777389905162242, 1); +INSERT INTO `sys_user_role` VALUES (1784783910114308097, 1); +INSERT INTO `sys_user_role` VALUES (1784821184902344705, 1); +INSERT INTO `sys_user_role` VALUES (1784838825360633858, 1); +INSERT INTO `sys_user_role` VALUES (1784870260805087233, 1); +INSERT INTO `sys_user_role` VALUES (1784910451020279810, 1); +INSERT INTO `sys_user_role` VALUES (1785130539233193985, 1); +INSERT INTO `sys_user_role` VALUES (1785240710601125890, 1); +INSERT INTO `sys_user_role` VALUES (1785360485289439233, 1); +INSERT INTO `sys_user_role` VALUES (1785588726424023041, 1); +INSERT INTO `sys_user_role` VALUES (1785975035152019458, 1); +INSERT INTO `sys_user_role` VALUES (1786448824117735425, 1); +INSERT INTO `sys_user_role` VALUES (1787036511853850625, 1); +INSERT INTO `sys_user_role` VALUES (1787040098730356738, 1); +INSERT INTO `sys_user_role` VALUES (1787442869522636802, 1); +INSERT INTO `sys_user_role` VALUES (1787802087576530946, 1); +INSERT INTO `sys_user_role` VALUES (1787878100067119105, 1); +INSERT INTO `sys_user_role` VALUES (1788016335816716290, 1); +INSERT INTO `sys_user_role` VALUES (1788135951385718786, 1); +INSERT INTO `sys_user_role` VALUES (1788136924611047425, 1); +INSERT INTO `sys_user_role` VALUES (1788564791958401026, 1); +INSERT INTO `sys_user_role` VALUES (1788861563763126273, 1); +INSERT INTO `sys_user_role` VALUES (1789104577664217090, 1); +INSERT INTO `sys_user_role` VALUES (1789215891946434561, 1); +INSERT INTO `sys_user_role` VALUES (1789891068120231937, 1); +INSERT INTO `sys_user_role` VALUES (1789916787885961218, 1); +INSERT INTO `sys_user_role` VALUES (1790285085844664322, 1); +INSERT INTO `sys_user_role` VALUES (1790395963663413250, 1); +INSERT INTO `sys_user_role` VALUES (1790626495441698817, 1); +INSERT INTO `sys_user_role` VALUES (1790733204311015425, 1); +INSERT INTO `sys_user_role` VALUES (1790747738857832449, 1); +INSERT INTO `sys_user_role` VALUES (1790893072141549570, 1); +INSERT INTO `sys_user_role` VALUES (1790953693902045186, 1); +INSERT INTO `sys_user_role` VALUES (1790986267617689601, 1); +INSERT INTO `sys_user_role` VALUES (1791058271444172801, 1); +INSERT INTO `sys_user_role` VALUES (1791123542645178370, 1); +INSERT INTO `sys_user_role` VALUES (1791170948304764929, 1); +INSERT INTO `sys_user_role` VALUES (1791173160204533762, 1); +INSERT INTO `sys_user_role` VALUES (1791181681805524994, 1); +INSERT INTO `sys_user_role` VALUES (1791184448041287681, 1); +INSERT INTO `sys_user_role` VALUES (1791281872491544578, 1); +INSERT INTO `sys_user_role` VALUES (1791281970680201217, 1); +INSERT INTO `sys_user_role` VALUES (1791283037744693249, 1); +INSERT INTO `sys_user_role` VALUES (1791285337913589762, 1); +INSERT INTO `sys_user_role` VALUES (1791289816255856641, 1); +INSERT INTO `sys_user_role` VALUES (1791296357612683266, 1); +INSERT INTO `sys_user_role` VALUES (1791299213191315457, 1); +INSERT INTO `sys_user_role` VALUES (1791308308178829314, 1); +INSERT INTO `sys_user_role` VALUES (1791318977032781826, 1); +INSERT INTO `sys_user_role` VALUES (1791371260403687425, 1); +INSERT INTO `sys_user_role` VALUES (1791387421707116546, 1); +INSERT INTO `sys_user_role` VALUES (1791447204858470402, 1); +INSERT INTO `sys_user_role` VALUES (1791729117863124993, 1); +INSERT INTO `sys_user_role` VALUES (1793165965818912770, 1); +INSERT INTO `sys_user_role` VALUES (1793568337082740737, 1); +INSERT INTO `sys_user_role` VALUES (1794560044937154561, 1); +INSERT INTO `sys_user_role` VALUES (1794749939555143681, 1); +INSERT INTO `sys_user_role` VALUES (1795107096276410369, 1); +INSERT INTO `sys_user_role` VALUES (1795403915137032194, 1); +INSERT INTO `sys_user_role` VALUES (1795789913440296962, 1); +INSERT INTO `sys_user_role` VALUES (1796141206390349825, 1); +INSERT INTO `sys_user_role` VALUES (1796355287995031553, 1); +INSERT INTO `sys_user_role` VALUES (1796407753490997250, 1); +INSERT INTO `sys_user_role` VALUES (1796463188688412674, 1); +INSERT INTO `sys_user_role` VALUES (1796906411999272961, 1); +INSERT INTO `sys_user_role` VALUES (1797537246867791874, 1); +INSERT INTO `sys_user_role` VALUES (1797817711835127809, 1); +INSERT INTO `sys_user_role` VALUES (1797909973524979713, 1); +INSERT INTO `sys_user_role` VALUES (1798175479586791425, 1); +INSERT INTO `sys_user_role` VALUES (1798235243616313345, 1); +INSERT INTO `sys_user_role` VALUES (1798520237534388226, 1); +INSERT INTO `sys_user_role` VALUES (1798712494199840770, 1); +INSERT INTO `sys_user_role` VALUES (1799280384053518338, 1); +INSERT INTO `sys_user_role` VALUES (1799744018567307266, 1); +INSERT INTO `sys_user_role` VALUES (1800533174780338178, 1); +INSERT INTO `sys_user_role` VALUES (1800536812638609409, 1); +INSERT INTO `sys_user_role` VALUES (1800674959565430786, 1); +INSERT INTO `sys_user_role` VALUES (1801079442480996354, 1); +INSERT INTO `sys_user_role` VALUES (1801092008536088577, 1); +INSERT INTO `sys_user_role` VALUES (1801164484339212289, 1); +INSERT INTO `sys_user_role` VALUES (1801390702451924994, 1); +INSERT INTO `sys_user_role` VALUES (1801448239394103297, 1); +INSERT INTO `sys_user_role` VALUES (1801450423980564482, 1); +INSERT INTO `sys_user_role` VALUES (1801600035647299585, 1); +INSERT INTO `sys_user_role` VALUES (1801917626890756098, 1); +INSERT INTO `sys_user_role` VALUES (1802151483346952194, 1); +INSERT INTO `sys_user_role` VALUES (1802185387541962754, 1); +INSERT INTO `sys_user_role` VALUES (1802352201437716481, 1); +INSERT INTO `sys_user_role` VALUES (1802595299652706305, 1); +INSERT INTO `sys_user_role` VALUES (1802615605641519105, 1); +INSERT INTO `sys_user_role` VALUES (1802884960002416641, 1); +INSERT INTO `sys_user_role` VALUES (1803244799710896130, 1); +INSERT INTO `sys_user_role` VALUES (1803310345022251010, 1); +INSERT INTO `sys_user_role` VALUES (1803350775793360898, 1); +INSERT INTO `sys_user_role` VALUES (1803952381528145922, 1); +INSERT INTO `sys_user_role` VALUES (1804409446046400513, 1); +INSERT INTO `sys_user_role` VALUES (1804412156426616834, 1); +INSERT INTO `sys_user_role` VALUES (1805074712967282689, 1); +INSERT INTO `sys_user_role` VALUES (1806151742303535105, 1); +INSERT INTO `sys_user_role` VALUES (1806589360086482945, 1); +INSERT INTO `sys_user_role` VALUES (1806743654970458113, 1); +INSERT INTO `sys_user_role` VALUES (1807019618258419713, 1); +INSERT INTO `sys_user_role` VALUES (1807670449198628866, 1); +INSERT INTO `sys_user_role` VALUES (1808432476074573826, 1); +INSERT INTO `sys_user_role` VALUES (1809093167261450242, 1); +INSERT INTO `sys_user_role` VALUES (1809123002226606082, 1); +INSERT INTO `sys_user_role` VALUES (1811926844047654913, 1); +INSERT INTO `sys_user_role` VALUES (1813103212164841473, 1); +INSERT INTO `sys_user_role` VALUES (1815634871045087233, 1); +INSERT INTO `sys_user_role` VALUES (1816485229208297473, 1); +INSERT INTO `sys_user_role` VALUES (1821084376519434241, 1); +INSERT INTO `sys_user_role` VALUES (1821169552259833858, 1); +INSERT INTO `sys_user_role` VALUES (1821804728467873793, 1); +INSERT INTO `sys_user_role` VALUES (1822834793930637314, 1); +INSERT INTO `sys_user_role` VALUES (1822959243497914370, 1); +INSERT INTO `sys_user_role` VALUES (1826249520908156930, 1); +INSERT INTO `sys_user_role` VALUES (1829035060720123905, 1); +INSERT INTO `sys_user_role` VALUES (1831211798115991553, 1); +INSERT INTO `sys_user_role` VALUES (1831273555001950210, 1); +INSERT INTO `sys_user_role` VALUES (1834083211252416513, 1); +INSERT INTO `sys_user_role` VALUES (1838475187125043201, 1); +INSERT INTO `sys_user_role` VALUES (1846455089220632577, 1); +INSERT INTO `sys_user_role` VALUES (1847910185208987649, 1); +INSERT INTO `sys_user_role` VALUES (1871910972567822337, 1); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/script/sql/update/update20241227.sql b/script/sql/update/update20241227.sql new file mode 100644 index 00000000..e69de29b