zsl 3 years ago
parent
commit
35d8381566
100 changed files with 10888 additions and 0 deletions
  1. 54 0
      ruoyi-admin/src/main/java/com/ruoyi/web/Mytest.java
  2. 93 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java
  3. 1 0
      ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties
  4. 37 0
      ruoyi-admin/src/main/resources/i18n/messages.properties
  5. 19 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
  6. 40 0
      ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java
  7. 89 0
      ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
  8. 77 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java
  9. 69 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java
  10. 266 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java
  11. 11 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java
  12. 246 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
  13. 36 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java
  14. 20 0
      ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java
  15. 16 0
      ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java
  16. 52 0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
  17. 75 0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
  18. 74 0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java
  19. 111 0
      ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
  20. 120 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
  21. 55 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java
  22. 56 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
  23. 264 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
  24. 291 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java
  25. 67 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java
  26. 91 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
  27. 48 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java
  28. 69 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
  29. 146 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
  30. 32 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java
  31. 63 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java
  32. 72 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java
  33. 45 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java
  34. 44 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
  35. 34 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java
  36. 53 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java
  37. 134 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
  38. 66 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java
  39. 115 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java
  40. 225 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
  41. 60 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
  42. 68 0
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java
  43. 521 0
      ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
  44. 44 0
      ruoyi-generator/src/main/resources/vm/js/api.js.vm
  45. 502 0
      ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
  46. 598 0
      ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  47. 486 0
      ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
  48. 596 0
      ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
  49. 1 0
      ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt
  50. 114 0
      ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceController.java
  51. 149 0
      ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceHistoryController.java
  52. 117 0
      ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceRunningController.java
  53. 207 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDevice.java
  54. 980 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDeviceHistory.java
  55. 53 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDeviceRunning.java
  56. 85 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/BloodPressure.java
  57. 35 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/Cure.java
  58. 94 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/CurrentValue.java
  59. 18 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceBody.java
  60. 52 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceHeader.java
  61. 18 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceRunningDto.java
  62. 67 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DialysisParam.java
  63. 47 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/SignDto.java
  64. 58 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/UfParam.java
  65. 106 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
  66. 148 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java
  67. 72 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceHistoryMapper.java
  68. 71 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceMapper.java
  69. 61 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceRunningMapper.java
  70. 78 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceHistoryService.java
  71. 61 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceRunningService.java
  72. 61 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceService.java
  73. 190 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceHistoryServiceImpl.java
  74. 93 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceRunningServiceImpl.java
  75. 96 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceServiceImpl.java
  76. 401 0
      ruoyi-system/src/main/resources/mapper/system/BusDeviceHistoryMapper.xml
  77. 132 0
      ruoyi-system/src/main/resources/mapper/system/BusDeviceMapper.xml
  78. 58 0
      ruoyi-system/src/main/resources/mapper/system/BusDeviceRunningMapper.xml
  79. 22 0
      ruoyi-ui/.editorconfig
  80. 11 0
      ruoyi-ui/.env.development
  81. 8 0
      ruoyi-ui/.env.production
  82. 10 0
      ruoyi-ui/.env.staging
  83. 10 0
      ruoyi-ui/.eslintignore
  84. 199 0
      ruoyi-ui/.eslintrc.js
  85. 23 0
      ruoyi-ui/.gitignore
  86. 30 0
      ruoyi-ui/README.md
  87. 13 0
      ruoyi-ui/babel.config.js
  88. 12 0
      ruoyi-ui/bin/build.bat
  89. 12 0
      ruoyi-ui/bin/package.bat
  90. 12 0
      ruoyi-ui/bin/run-web.bat
  91. 35 0
      ruoyi-ui/build/index.js
  92. 90 0
      ruoyi-ui/package.json
  93. BIN
      ruoyi-ui/public/favicon.ico
  94. 21 0
      ruoyi-ui/public/html/ie.html
  95. 208 0
      ruoyi-ui/public/index.html
  96. 2 0
      ruoyi-ui/public/robots.txt
  97. 19 0
      ruoyi-ui/src/App.vue
  98. 59 0
      ruoyi-ui/src/api/login.js
  99. 9 0
      ruoyi-ui/src/api/menu.js
  100. 9 0
      ruoyi-ui/src/api/monitor/cache.js

+ 54 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/Mytest.java

@@ -0,0 +1,54 @@
+package com.ruoyi.web;
+
+import com.ruoyi.RuoYiApplication;
+import com.ruoyi.system.controller.BusDeviceHistoryController;
+import com.ruoyi.system.domain.BusDeviceHistory;
+import com.ruoyi.system.domain.BusPatient;
+import com.ruoyi.system.mapper.BusDeviceHistoryMapper;
+import com.ruoyi.system.mapper.BusPatientMapper;
+import com.ruoyi.system.service.IBusDeviceHistoryService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName Mytest.java
+ * @Description TODO
+ * @createTime 2022/6/1614:15
+ */
+
+@SpringBootTest(classes = RuoYiApplication.class)
+@RunWith(SpringRunner.class)
+public class Mytest {
+    @Autowired
+    private BusPatientMapper patientMapper;
+    @Autowired
+    private BusDeviceHistoryMapper deviceHistoryMapper;
+    @Autowired
+    private BusDeviceHistoryController deviceHistoryController;
+    @Autowired
+    private IBusDeviceHistoryService busDeviceHistoryService;
+
+    @Test
+    public void test0001(){
+
+        Long[] l1 = new Long[2];
+        l1[0]= 5L;
+        l1[1]=6L;
+
+        System.out.println(deviceHistoryMapper.updateStatusByIds(l1,1));
+    }
+    @Test
+    public void test0002(){
+
+        Long[] l1 = new Long[5];
+        l1[0]= 5L;
+        l1[1]=6L;
+
+        System.out.println(busDeviceHistoryService.cure(l1,l1));
+    }
+}

+ 93 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java

@@ -0,0 +1,93 @@
+package com.ruoyi.web.controller.common;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletResponse;
+import com.ruoyi.common.config.RuoYiConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.code.kaptcha.Producer;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.sign.Base64;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import com.ruoyi.system.service.ISysConfigService;
+
+/**
+ * 验证码操作处理
+ * 
+ * @author ruoyi
+ */
+@RestController
+public class CaptchaController
+{
+    @Resource(name = "captchaProducer")
+    private Producer captchaProducer;
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    @Autowired
+    private ISysConfigService configService;
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public AjaxResult getCode(HttpServletResponse response) throws IOException
+    {
+        AjaxResult ajax = AjaxResult.success();
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        ajax.put("captchaOnOff", captchaOnOff);
+        if (!captchaOnOff)
+        {
+            return ajax;
+        }
+
+        // 保存验证码信息
+        String uuid = IdUtils.simpleUUID();
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+
+        String capStr = null, code = null;
+        BufferedImage image = null;
+
+        // 生成验证码
+        String captchaType = RuoYiConfig.getCaptchaType();
+        if ("math".equals(captchaType))
+        {
+            String capText = captchaProducerMath.createText();
+            capStr = capText.substring(0, capText.lastIndexOf("@"));
+            code = capText.substring(capText.lastIndexOf("@") + 1);
+            image = captchaProducerMath.createImage(capStr);
+        }
+        else if ("char".equals(captchaType))
+        {
+            capStr = code = captchaProducer.createText();
+            image = captchaProducer.createImage(capStr);
+        }
+
+        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try
+        {
+            ImageIO.write(image, "jpg", os);
+        }
+        catch (IOException e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+
+        ajax.put("uuid", uuid);
+        ajax.put("img", Base64.encode(os.toByteArray()));
+        return ajax;
+    }
+}

+ 1 - 0
ruoyi-admin/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson.*.jar

+ 37 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -0,0 +1,37 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 19 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java

@@ -0,0 +1,19 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 匿名访问不鉴权注解
+ * 
+ * @author ruoyi
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous
+{
+}

+ 40 - 0
ruoyi-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java

@@ -0,0 +1,40 @@
+package com.ruoyi.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.enums.LimitType;
+
+/**
+ * 限流注解
+ * 
+ * @author ruoyi
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter
+{
+    /**
+     * 限流key
+     */
+    public String key() default Constants.RATE_LIMIT_KEY;
+
+    /**
+     * 限流时间,单位秒
+     */
+    public int time() default 60;
+
+    /**
+     * 限流次数
+     */
+    public int count() default 100;
+
+    /**
+     * 限流类型
+     */
+    public LimitType limitType() default LimitType.DEFAULT;
+}

+ 89 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java

@@ -0,0 +1,89 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 返回状态码
+ * 
+ * @author ruoyi
+ */
+public class HttpStatus
+{
+    /**
+     * 操作成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    public static final int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    public static final int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    public static final int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    public static final int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    public static final int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    public static final int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    public static final int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    public static final int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    public static final int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    public static final int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    public static final int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    public static final int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    public static final int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    public static final int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    public static final int NOT_IMPLEMENTED = 501;
+}

+ 77 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java

@@ -0,0 +1,77 @@
+package com.ruoyi.common.core.domain;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.stream.Collectors;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.core.domain.entity.SysMenu;
+
+/**
+ * Treeselect树结构实体类
+ * 
+ * @author ruoyi
+ */
+public class TreeSelect implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private Long id;
+
+    /** 节点名称 */
+    private String label;
+
+    /** 子节点 */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private List<TreeSelect> children;
+
+    public TreeSelect()
+    {
+
+    }
+
+    public TreeSelect(SysDept dept)
+    {
+        this.id = dept.getDeptId();
+        this.label = dept.getDeptName();
+        this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    public TreeSelect(SysMenu menu)
+    {
+        this.id = menu.getMenuId();
+        this.label = menu.getMenuName();
+        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public void setLabel(String label)
+    {
+        this.label = label;
+    }
+
+    public List<TreeSelect> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<TreeSelect> children)
+    {
+        this.children = children;
+    }
+}

+ 69 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java

@@ -0,0 +1,69 @@
+package com.ruoyi.common.core.domain.model;
+
+/**
+ * 用户登录对象
+ * 
+ * @author ruoyi
+ */
+public class LoginBody
+{
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    private String password;
+
+    /**
+     * 验证码
+     */
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    private String uuid;
+
+    public String getUsername()
+    {
+        return username;
+    }
+
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+
+    public String getPassword()
+    {
+        return password;
+    }
+
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public void setCode(String code)
+    {
+        this.code = code;
+    }
+
+    public String getUuid()
+    {
+        return uuid;
+    }
+
+    public void setUuid(String uuid)
+    {
+        this.uuid = uuid;
+    }
+}

+ 266 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java

@@ -0,0 +1,266 @@
+package com.ruoyi.common.core.domain.model;
+
+import java.util.Collection;
+import java.util.Set;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.ruoyi.common.core.domain.entity.SysUser;
+
+/**
+ * 登录用户身份权限
+ * 
+ * @author ruoyi
+ */
+public class LoginUser implements UserDetails
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * 用户信息
+     */
+    private SysUser user;
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public Long getDeptId()
+    {
+        return deptId;
+    }
+
+    public void setDeptId(Long deptId)
+    {
+        this.deptId = deptId;
+    }
+
+    public String getToken()
+    {
+        return token;
+    }
+
+    public void setToken(String token)
+    {
+        this.token = token;
+    }
+
+    public LoginUser()
+    {
+    }
+
+    public LoginUser(SysUser user, Set<String> permissions)
+    {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
+    {
+        this.userId = userId;
+        this.deptId = deptId;
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public String getPassword()
+    {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername()
+    {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonLocked()
+    {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isCredentialsNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     * 
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isEnabled()
+    {
+        return true;
+    }
+
+    public Long getLoginTime()
+    {
+        return loginTime;
+    }
+
+    public void setLoginTime(Long loginTime)
+    {
+        this.loginTime = loginTime;
+    }
+
+    public String getIpaddr()
+    {
+        return ipaddr;
+    }
+
+    public void setIpaddr(String ipaddr)
+    {
+        this.ipaddr = ipaddr;
+    }
+
+    public String getLoginLocation()
+    {
+        return loginLocation;
+    }
+
+    public void setLoginLocation(String loginLocation)
+    {
+        this.loginLocation = loginLocation;
+    }
+
+    public String getBrowser()
+    {
+        return browser;
+    }
+
+    public void setBrowser(String browser)
+    {
+        this.browser = browser;
+    }
+
+    public String getOs()
+    {
+        return os;
+    }
+
+    public void setOs(String os)
+    {
+        this.os = os;
+    }
+
+    public Long getExpireTime()
+    {
+        return expireTime;
+    }
+
+    public void setExpireTime(Long expireTime)
+    {
+        this.expireTime = expireTime;
+    }
+
+    public Set<String> getPermissions()
+    {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions)
+    {
+        this.permissions = permissions;
+    }
+
+    public SysUser getUser()
+    {
+        return user;
+    }
+
+    public void setUser(SysUser user)
+    {
+        this.user = user;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities()
+    {
+        return null;
+    }
+}

+ 11 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java

@@ -0,0 +1,11 @@
+package com.ruoyi.common.core.domain.model;
+
+/**
+ * 用户注册对象
+ * 
+ * @author ruoyi
+ */
+public class RegisterBody extends LoginBody
+{
+
+}

+ 246 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java

@@ -0,0 +1,246 @@
+package com.ruoyi.common.core.redis;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring redis 工具类
+ *
+ * @author ruoyi
+ **/
+@SuppressWarnings(value = { "unchecked", "rawtypes" })
+@Component
+public class RedisCache
+{
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value)
+    {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     * @param timeout 时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout)
+    {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @param unit 时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit)
+    {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key)
+    {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public long deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection);
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key 缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList)
+    {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key)
+    {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key 缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
+    {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext())
+        {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key)
+    {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
+    {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key)
+    {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
+    {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 删除Hash中的数据
+     * 
+     * @param key
+     * @param hKey
+     */
+    public void delCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations hashOperations = redisTemplate.opsForHash();
+        hashOperations.delete(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
+    {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+}

+ 36 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java

@@ -0,0 +1,36 @@
+package com.ruoyi.common.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.lang.Nullable;
+
+/**
+ * 请求方式
+ *
+ * @author ruoyi
+ */
+public enum HttpMethod
+{
+    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+
+    private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
+
+    static
+    {
+        for (HttpMethod httpMethod : values())
+        {
+            mappings.put(httpMethod.name(), httpMethod);
+        }
+    }
+
+    @Nullable
+    public static HttpMethod resolve(@Nullable String method)
+    {
+        return (method != null ? mappings.get(method) : null);
+    }
+
+    public boolean matches(String method)
+    {
+        return (this == resolve(method));
+    }
+}

+ 20 - 0
ruoyi-common/src/main/java/com/ruoyi/common/enums/LimitType.java

@@ -0,0 +1,20 @@
+package com.ruoyi.common.enums;
+
+/**
+ * 限流类型
+ *
+ * @author ruoyi
+ */
+
+public enum LimitType
+{
+    /**
+     * 默认策略全局限流
+     */
+    DEFAULT,
+
+    /**
+     * 根据请求者IP进行限流
+     */
+    IP
+}

+ 16 - 0
ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java

@@ -0,0 +1,16 @@
+package com.ruoyi.common.exception.user;
+
+/**
+ * 验证码失效异常类
+ * 
+ * @author ruoyi
+ */
+public class CaptchaExpireException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaExpireException()
+    {
+        super("user.jcaptcha.expire", null);
+    }
+}

+ 52 - 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java

@@ -0,0 +1,52 @@
+package com.ruoyi.common.filter;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 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()
+    {
+
+    }
+}

+ 75 - 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java

@@ -0,0 +1,75 @@
+package com.ruoyi.common.filter;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import com.ruoyi.common.utils.http.HttpHelper;
+
+/**
+ * 构建可重复读取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("UTF-8");
+        response.setCharacterEncoding("UTF-8");
+
+        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
+    }
+
+    @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)
+            {
+
+            }
+        };
+    }
+}

+ 74 - 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/XssFilter.java

@@ -0,0 +1,74 @@
+package com.ruoyi.common.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 防止XSS攻击的过滤器
+ * 
+ * @author ruoyi
+ */
+public class XssFilter implements Filter
+{
+    /**
+     * 排除链接
+     */
+    public List<String> excludes = new ArrayList<>();
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        String tempExcludes = filterConfig.getInitParameter("excludes");
+        if (StringUtils.isNotEmpty(tempExcludes))
+        {
+            String[] url = tempExcludes.split(",");
+            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 || method.matches("GET") || method.matches("DELETE"))
+        {
+            return true;
+        }
+        return StringUtils.matches(url, excludes);
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 111 - 0
ruoyi-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java

@@ -0,0 +1,111 @@
+package com.ruoyi.common.filter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import org.apache.commons.io.IOUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.html.EscapeUtil;
+
+/**
+ * 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] = EscapeUtil.clean(values[i]).trim();
+            }
+            return escapseValues;
+        }
+        return super.getParameterValues(name);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        // 非json类型,直接返回
+        if (!isJsonRequest())
+        {
+            return super.getInputStream();
+        }
+
+        // 为空,直接返回
+        String json = IOUtils.toString(super.getInputStream(), "utf-8");
+        if (StringUtils.isEmpty(json))
+        {
+            return super.getInputStream();
+        }
+
+        // xss过滤
+        json = EscapeUtil.clean(json).trim();
+        byte[] jsonBytes = json.getBytes("utf-8");
+        final ByteArrayInputStream bis = new ByteArrayInputStream(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请求
+     * 
+     * @param request
+     */
+    public boolean isJsonRequest()
+    {
+        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
+        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
+    }
+}

+ 120 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java

@@ -0,0 +1,120 @@
+package com.ruoyi.common.utils;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.exception.ServiceException;
+
+/**
+ * 安全服务工具类
+ * 
+ * @author ruoyi
+ */
+public class SecurityUtils
+{
+    /**
+     * 用户ID
+     **/
+    public static Long getUserId()
+    {
+        try
+        {
+            return getLoginUser().getUserId();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取部门ID
+     **/
+    public static Long getDeptId()
+    {
+        try
+        {
+            return getLoginUser().getDeptId();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+    
+    /**
+     * 获取用户账户
+     **/
+    public static String getUsername()
+    {
+        try
+        {
+            return getLoginUser().getUsername();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser()
+    {
+        try
+        {
+            return (LoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication()
+    {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 是否为管理员
+     * 
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isAdmin(Long userId)
+    {
+        return userId != null && 1L == userId;
+    }
+}

+ 55 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java

@@ -0,0 +1,55 @@
+package com.ruoyi.common.utils.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.ServletRequest;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通用http工具封装
+ * 
+ * @author ruoyi
+ */
+public class HttpHelper
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
+
+    public static String getBodyString(ServletRequest request)
+    {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader reader = null;
+        try (InputStream inputStream = request.getInputStream())
+        {
+            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+            String line = "";
+            while ((line = reader.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("getBodyString出现问题!");
+        }
+        finally
+        {
+            if (reader != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException e)
+                {
+                    LOGGER.error(ExceptionUtils.getMessage(e));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}

+ 56 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java

@@ -0,0 +1,56 @@
+package com.ruoyi.common.utils.ip;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpUtils;
+
+/**
+ * 获取地址类
+ * 
+ * @author ruoyi
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    // IP地址查询
+    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+
+    // 未知地址
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (RuoYiConfig.isAddressEnabled())
+        {
+            try
+            {
+                String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
+                if (StringUtils.isEmpty(rspStr))
+                {
+                    log.error("获取地理位置异常 {}", ip);
+                    return UNKNOWN;
+                }
+                JSONObject obj = JSON.parseObject(rspStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+            catch (Exception e)
+            {
+                log.error("获取地理位置异常 {}", ip);
+            }
+        }
+        return UNKNOWN;
+    }
+}

+ 264 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java

@@ -0,0 +1,264 @@
+package com.ruoyi.common.utils.ip;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import javax.servlet.http.HttpServletRequest;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 获取IP方法
+ * 
+ * @author ruoyi
+ */
+public class IpUtils
+{
+    /**
+     * 获取客户端IP
+     * 
+     * @param request 请求对象
+     * @return IP地址
+     */
+    public static String getIpAddr(HttpServletRequest request)
+    {
+        if (request == null)
+        {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     * 
+     * @param ip IP地址
+     * @return 结果
+     */
+    public static boolean internalIp(String ip)
+    {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    /**
+     * 检查是否为内部IP地址
+     * 
+     * @param addr byte地址
+     * @return 结果
+     */
+    private static boolean internalIp(byte[] addr)
+    {
+        if (StringUtils.isNull(addr) || addr.length < 2)
+        {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0)
+        {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4)
+                {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1)
+                {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     * 
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text)
+    {
+        if (text.length() == 0)
+        {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try
+        {
+            long l;
+            int i;
+            switch (elements.length)
+            {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L))
+                    {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L))
+                    {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L))
+                    {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L))
+                        {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L))
+                    {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L))
+                        {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e)
+        {
+            return null;
+        }
+        return bytes;
+    }
+
+    /**
+     * 获取IP地址
+     * 
+     * @return 本地IP地址
+     */
+    public static String getHostIp()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "127.0.0.1";
+    }
+
+    /**
+     * 获取主机名
+     * 
+     * @return 本地主机名
+     */
+    public static String getHostName()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "未知";
+    }
+
+    /**
+     * 从多级反向代理中获得第一个非unknown IP地址
+     *
+     * @param ip 获得的IP地址
+     * @return 第一个非unknown IP地址
+     */
+    public static String getMultistageReverseProxyIp(String ip)
+    {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0)
+        {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips)
+            {
+                if (false == isUnknown(subIp))
+                {
+                    ip = subIp;
+                    break;
+                }
+            }
+        }
+        return ip;
+    }
+
+    /**
+     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
+     *
+     * @param checkString 被检测的字符串
+     * @return 是否未知
+     */
+    public static boolean isUnknown(String checkString)
+    {
+        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+}

+ 291 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java

@@ -0,0 +1,291 @@
+package com.ruoyi.common.utils.sign;
+
+/**
+ * Base64工具类
+ * 
+ * @author ruoyi
+ */
+public final class Base64
+{
+    static private final int     BASELENGTH           = 128;
+    static private final int     LOOKUPLENGTH         = 64;
+    static private final int     TWENTYFOURBITGROUP   = 24;
+    static private final int     EIGHTBIT             = 8;
+    static private final int     SIXTEENBIT           = 16;
+    static private final int     FOURBYTE             = 4;
+    static private final int     SIGN                 = -128;
+    static private final char    PAD                  = '=';
+    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
+    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static
+    {
+        for (int i = 0; i < BASELENGTH; ++i)
+        {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+
+        for (int i = '9'; i >= '0'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++)
+        {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect)
+    {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect)
+    {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect)
+    {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * Encodes hex octects into Base64
+     *
+     * @param binaryData Array containing binaryData
+     * @return Encoded Base64 array
+     */
+    public static String encode(byte[] binaryData)
+    {
+        if (binaryData == null)
+        {
+            return null;
+        }
+
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0)
+        {
+            return "";
+        }
+
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+        char encodedData[] = null;
+
+        encodedData = new char[numberQuartet * 4];
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+
+        for (int i = 0; i < numberTriplets; i++)
+        {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT)
+        {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        }
+        else if (fewerThan24bits == SIXTEENBIT)
+        {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param encoded string containing Base64 data
+     * @return Array containind decoded data.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded == null)
+        {
+            return null;
+        }
+
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+
+        if (len % FOURBYTE != 0)
+        {
+            return null;// should be divisible by four
+        }
+
+        int numberQuadruple = (len / FOURBYTE);
+
+        if (numberQuadruple == 0)
+        {
+            return new byte[0];
+        }
+
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+
+        for (; i < numberQuadruple - 1; i++)
+        {
+
+            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
+            {
+                return null;
+            } // if found "no data" just return null
+
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+
+        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
+        {
+            return null;// if found "no data" just return null
+        }
+
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4)))
+        {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4))
+            {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            }
+            else if (!isPad(d3) && isPad(d4))
+            {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64 data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data)
+    {
+        if (data == null)
+        {
+            return 0;
+        }
+
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++)
+        {
+            if (!isWhiteSpace(data[i]))
+            {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}

+ 67 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java

@@ -0,0 +1,67 @@
+package com.ruoyi.common.utils.sign;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Md5加密方法
+ * 
+ * @author ruoyi
+ */
+public class Md5Utils
+{
+    private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
+
+    private static byte[] md5(String s)
+    {
+        MessageDigest algorithm;
+        try
+        {
+            algorithm = MessageDigest.getInstance("MD5");
+            algorithm.reset();
+            algorithm.update(s.getBytes("UTF-8"));
+            byte[] messageDigest = algorithm.digest();
+            return messageDigest;
+        }
+        catch (Exception e)
+        {
+            log.error("MD5 Error...", e);
+        }
+        return null;
+    }
+
+    private static final String toHex(byte hash[])
+    {
+        if (hash == null)
+        {
+            return null;
+        }
+        StringBuffer buf = new StringBuffer(hash.length * 2);
+        int i;
+
+        for (i = 0; i < hash.length; i++)
+        {
+            if ((hash[i] & 0xff) < 0x10)
+            {
+                buf.append("0");
+            }
+            buf.append(Long.toString(hash[i] & 0xff, 16));
+        }
+        return buf.toString();
+    }
+
+    public static String hash(String s)
+    {
+        try
+        {
+            return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
+        }
+        catch (Exception e)
+        {
+            log.error("not supported charset...{}", e);
+            return s;
+        }
+    }
+}

+ 91 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java

@@ -0,0 +1,91 @@
+package com.ruoyi.framework.aspectj;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.annotation.RateLimiter;
+import com.ruoyi.common.enums.LimitType;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+
+/**
+ * 限流处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class RateLimiterAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    private RedisScript<Long> limitScript;
+
+    @Autowired
+    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
+    {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Autowired
+    public void setLimitScript(RedisScript<Long> limitScript)
+    {
+        this.limitScript = limitScript;
+    }
+
+    @Before("@annotation(rateLimiter)")
+    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
+    {
+        String key = rateLimiter.key();
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+
+        String combineKey = getCombineKey(rateLimiter, point);
+        List<Object> keys = Collections.singletonList(combineKey);
+        try
+        {
+            Long number = redisTemplate.execute(limitScript, keys, count, time);
+            if (StringUtils.isNull(number) || number.intValue() > count)
+            {
+                throw new ServiceException("访问过于频繁,请稍候再试");
+            }
+            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
+        }
+        catch (ServiceException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("服务器限流异常,请稍候再试");
+        }
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
+    {
+        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP)
+        {
+            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
+        }
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
+        return stringBuffer.toString();
+    }
+}

+ 48 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,48 @@
+package com.ruoyi.framework.config;
+
+import java.nio.charset.Charset;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+
+/**
+ * Redis使用FastJson序列化
+ * 
+ * @author ruoyi
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    private Class<T> clazz;
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
+    }
+}

+ 69 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java

@@ -0,0 +1,69 @@
+package com.ruoyi.framework.config;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    public DefaultRedisScript<Long> limitScript()
+    {
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(limitScriptText());
+        redisScript.setResultType(Long.class);
+        return redisScript;
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String limitScriptText()
+    {
+        return "local key = KEYS[1]\n" +
+                "local count = tonumber(ARGV[1])\n" +
+                "local time = tonumber(ARGV[2])\n" +
+                "local current = redis.call('get', key);\n" +
+                "if current and tonumber(current) > count then\n" +
+                "    return tonumber(current);\n" +
+                "end\n" +
+                "current = redis.call('incr', key)\n" +
+                "if tonumber(current) == 1 then\n" +
+                "    redis.call('expire', key, time)\n" +
+                "end\n" +
+                "return tonumber(current);";
+    }
+}

+ 146 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

@@ -0,0 +1,146 @@
+package com.ruoyi.framework.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.filter.CorsFilter;
+import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
+import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
+import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
+import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
+
+/**
+ * spring security配置
+ * 
+ * @author ruoyi
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter
+{
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsService userDetailsService;
+    
+    /**
+     * 认证失败处理类
+     */
+    @Autowired
+    private AuthenticationEntryPointImpl unauthorizedHandler;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+    
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    /**
+     * 允许匿名访问的地址
+     */
+    @Autowired
+    private PermitAllUrlProperties permitAllUrl;
+
+    /**
+     * 解决 无法直接注入 AuthenticationManager
+     *
+     * @return
+     * @throws Exception
+     */
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception
+    {
+        return super.authenticationManagerBean();
+    }
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity httpSecurity) throws Exception
+    {
+        // 注解标记允许匿名访问的url
+        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
+        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
+
+        httpSecurity
+                // CSRF禁用,因为不使用session
+                .csrf().disable()
+                // 认证失败处理类
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+                // 基于token,所以不需要session
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+                // 过滤请求
+                .authorizeRequests()
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+                .antMatchers("/login", "/register", "/captchaImage","/system/running/**").anonymous()
+                // 静态资源,可匿名访问
+                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
+                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+                // 除上面外的所有请求全部需要鉴权认证
+                .anyRequest().authenticated()
+                .and()
+                .headers().frameOptions().disable();
+        // 添加Logout filter
+        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+        // 添加JWT filter
+        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+        // 添加CORS filter
+        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
+        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
+    }
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder()
+    {
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 身份认证接口
+     */
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception
+    {
+        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+    }
+}

+ 32 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java

@@ -0,0 +1,32 @@
+package com.ruoyi.framework.config;
+
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.utils.ServletUtils;
+
+/**
+ * 服务相关配置
+ * 
+ * @author ruoyi
+ */
+@Component
+public class ServerConfig
+{
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     * 
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 63 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java

@@ -0,0 +1,63 @@
+package com.ruoyi.framework.config;
+
+import com.ruoyi.common.utils.Threads;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author ruoyi
+ **/
+@Configuration
+public class ThreadPoolConfig
+{
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
+    {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService()
+    {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                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);
+            }
+        };
+    }
+}

+ 72 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java

@@ -0,0 +1,72 @@
+package com.ruoyi.framework.config.properties;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.RegExUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import com.ruoyi.common.annotation.Anonymous;
+
+/**
+ * 设置Anonymous注解允许匿名访问的url
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
+{
+    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+    private ApplicationContext applicationContext;
+
+    private List<String> urls = new ArrayList<>();
+
+    public String ASTERISK = "*";
+
+    @Override
+    public void afterPropertiesSet()
+    {
+        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
+        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+
+        map.keySet().forEach(info -> {
+            HandlerMethod handlerMethod = map.get(info);
+
+            // 获取方法上边的注解 替代path variable 为 *
+            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
+            Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+
+            // 获取类上边的注解, 替代path variable 为 *
+            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
+            Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+        });
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException
+    {
+        this.applicationContext = context;
+    }
+
+    public List<String> getUrls()
+    {
+        return urls;
+    }
+
+    public void setUrls(List<String> urls)
+    {
+        this.urls = urls;
+    }
+}

+ 45 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java

@@ -0,0 +1,45 @@
+package com.ruoyi.framework.datasource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 数据源切换处理
+ * 
+ * @author ruoyi
+ */
+public class DynamicDataSourceContextHolder
+{
+    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
+
+    /**
+     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
+     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
+     */
+    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
+
+    /**
+     * 设置数据源的变量
+     */
+    public static void setDataSourceType(String dsType)
+    {
+        log.info("切换到{}数据源", dsType);
+        CONTEXT_HOLDER.set(dsType);
+    }
+
+    /**
+     * 获得数据源的变量
+     */
+    public static String getDataSourceType()
+    {
+        return CONTEXT_HOLDER.get();
+    }
+
+    /**
+     * 清空数据源变量
+     */
+    public static void clearDataSourceType()
+    {
+        CONTEXT_HOLDER.remove();
+    }
+}

+ 44 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,44 @@
+package com.ruoyi.framework.security.filter;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.TokenService;
+
+/**
+ * token过滤器 验证token有效性
+ * 
+ * @author ruoyi
+ */
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
+        {
+            tokenService.verifyToken(loginUser);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        }
+        chain.doFilter(request, response);
+    }
+}

+ 34 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java

@@ -0,0 +1,34 @@
+package com.ruoyi.framework.security.handle;
+
+import java.io.IOException;
+import java.io.Serializable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 认证失败处理类 返回未授权
+ * 
+ * @author ruoyi
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
+{
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+            throws IOException
+    {
+        int code = HttpStatus.UNAUTHORIZED;
+        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
+    }
+}

+ 53 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -0,0 +1,53 @@
+package com.ruoyi.framework.security.handle;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.framework.web.service.TokenService;
+
+/**
+ * 自定义退出处理类 返回成功
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
+{
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 退出处理
+     * 
+     * @return
+     */
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+            throws IOException, ServletException
+    {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (StringUtils.isNotNull(loginUser))
+        {
+            String userName = loginUser.getUsername();
+            // 删除用户缓存记录
+            tokenService.delLoginUser(loginUser.getToken());
+            // 记录用户退出日志
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
+        }
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
+    }
+}

+ 134 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

@@ -0,0 +1,134 @@
+package com.ruoyi.framework.web.service;
+
+import javax.annotation.Resource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.exception.user.CaptchaException;
+import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 登录校验方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysLoginService
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private RedisCache redisCache;
+    
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 登录验证
+     * 
+     * @param username 用户名
+     * @param password 密码
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public String login(String username, String password, String code, String uuid)
+    {
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        // 验证码开关
+        if (captchaOnOff)
+        {
+            validateCaptcha(username, code, uuid);
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        recordLoginInfo(loginUser.getUserId());
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+
+    /**
+     * 校验验证码
+     * 
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param userId 用户ID
+     */
+    public void recordLoginInfo(Long userId)
+    {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        sysUser.setLoginDate(DateUtils.getNowDate());
+        userService.updateUserProfile(sysUser);
+    }
+}

+ 66 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java

@@ -0,0 +1,66 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysRoleService;
+
+/**
+ * 用户权限处理
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysPermissionService
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysMenuService menuService;
+
+    /**
+     * 获取角色数据权限
+     * 
+     * @param user 用户信息
+     * @return 角色权限信息
+     */
+    public Set<String> getRolePermission(SysUser user)
+    {
+        Set<String> roles = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            roles.add("admin");
+        }
+        else
+        {
+            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
+        }
+        return roles;
+    }
+
+    /**
+     * 获取菜单数据权限
+     * 
+     * @param user 用户信息
+     * @return 菜单权限信息
+     */
+    public Set<String> getMenuPermission(SysUser user)
+    {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            perms.add("*:*:*");
+        }
+        else
+        {
+            perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
+        }
+        return perms;
+    }
+}

+ 115 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java

@@ -0,0 +1,115 @@
+package com.ruoyi.framework.web.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.UserConstants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.RegisterBody;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.exception.user.CaptchaException;
+import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 注册校验方法
+ * 
+ * @author ruoyi
+ */
+@Component
+public class SysRegisterService
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 注册
+     */
+    public String register(RegisterBody registerBody)
+    {
+        String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
+
+        boolean captchaOnOff = configService.selectCaptchaOnOff();
+        // 验证码开关
+        if (captchaOnOff)
+        {
+            validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
+        }
+
+        if (StringUtils.isEmpty(username))
+        {
+            msg = "用户名不能为空";
+        }
+        else if (StringUtils.isEmpty(password))
+        {
+            msg = "用户密码不能为空";
+        }
+        else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
+        {
+            msg = "账户长度必须在2到20个字符之间";
+        }
+        else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
+        {
+            msg = "密码长度必须在5到20个字符之间";
+        }
+        else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
+        {
+            msg = "保存用户'" + username + "'失败,注册账号已存在";
+        }
+        else
+        {
+            SysUser sysUser = new SysUser();
+            sysUser.setUserName(username);
+            sysUser.setNickName(username);
+            sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
+            boolean regFlag = userService.registerUser(sysUser);
+            if (!regFlag)
+            {
+                msg = "注册失败,请联系系统管理人员";
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER,
+                        MessageUtils.message("user.register.success")));
+            }
+        }
+        return msg;
+    }
+
+    /**
+     * 校验验证码
+     * 
+     * @param username 用户名
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public void validateCaptcha(String username, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            throw new CaptchaException();
+        }
+    }
+}

+ 225 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java

@@ -0,0 +1,225 @@
+package com.ruoyi.framework.web.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.AddressUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import eu.bitwalker.useragentutils.UserAgent;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+/**
+ * token验证处理
+ *
+ * @author ruoyi
+ */
+@Component
+public class TokenService
+{
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    // 令牌秘钥
+    @Value("${token.secret}")
+    private String secret;
+
+    // 令牌有效期(默认30分钟)
+    @Value("${token.expireTime}")
+    private int expireTime;
+
+    protected static final long MILLIS_SECOND = 1000;
+
+    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
+
+    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(HttpServletRequest request)
+    {
+        // 获取请求携带的令牌
+        String token = getToken(request);
+        if (StringUtils.isNotEmpty(token))
+        {
+            try
+            {
+                Claims claims = parseToken(token);
+                // 解析对应的权限以及用户信息
+                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+                String userKey = getTokenKey(uuid);
+                LoginUser user = redisCache.getCacheObject(userKey);
+                return user;
+            }
+            catch (Exception e)
+            {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 设置用户身份信息
+     */
+    public void setLoginUser(LoginUser loginUser)
+    {
+        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 删除用户身份信息
+     */
+    public void delLoginUser(String token)
+    {
+        if (StringUtils.isNotEmpty(token))
+        {
+            String userKey = getTokenKey(token);
+            redisCache.deleteObject(userKey);
+        }
+    }
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(LoginUser loginUser)
+    {
+        String token = IdUtils.fastUUID();
+        loginUser.setToken(token);
+        setUserAgent(loginUser);
+        refreshToken(loginUser);
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(Constants.LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser
+     * @return 令牌
+     */
+    public void verifyToken(LoginUser loginUser)
+    {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    public void refreshToken(LoginUser loginUser)
+    {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
+        // 根据uuid将loginUser缓存
+        String userKey = getTokenKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    public void setUserAgent(LoginUser loginUser)
+    {
+        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        loginUser.setIpaddr(ip);
+        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        loginUser.setBrowser(userAgent.getBrowser().getName());
+        loginUser.setOs(userAgent.getOperatingSystem().getName());
+    }
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims)
+    {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims parseToken(String token)
+    {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getUsernameFromToken(String token)
+    {
+        Claims claims = parseToken(token);
+        return claims.getSubject();
+    }
+
+    /**
+     * 获取请求token
+     *
+     * @param request
+     * @return token
+     */
+    private String getToken(HttpServletRequest request)
+    {
+        String token = request.getHeader(header);
+        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
+        {
+            token = token.replace(Constants.TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    private String getTokenKey(String uuid)
+    {
+        return Constants.LOGIN_TOKEN_KEY + uuid;
+    }
+}

+ 60 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java

@@ -0,0 +1,60 @@
+package com.ruoyi.framework.web.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.enums.UserStatus;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 用户验证处理
+ *
+ * @author ruoyi
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService
+{
+    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
+    {
+        SysUser user = userService.selectUserByUserName(username);
+        if (StringUtils.isNull(user))
+        {
+            log.info("登录用户:{} 不存在.", username);
+            throw new ServiceException("登录用户:" + username + " 不存在");
+        }
+        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
+        {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
+        }
+        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
+        {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
+        }
+
+        return createLoginUser(user);
+    }
+
+    public UserDetails createLoginUser(SysUser user)
+    {
+        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
+    }
+}

+ 68 - 0
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java

@@ -0,0 +1,68 @@
+package com.ruoyi.generator.service;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.generator.domain.GenTableColumn;
+import com.ruoyi.generator.mapper.GenTableColumnMapper;
+
+/**
+ * 业务字段 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class GenTableColumnServiceImpl implements IGenTableColumnService 
+{
+	@Autowired
+	private GenTableColumnMapper genTableColumnMapper;
+
+	/**
+     * 查询业务字段列表
+     * 
+     * @param tableId 业务字段编号
+     * @return 业务字段集合
+     */
+	@Override
+	public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId)
+	{
+	    return genTableColumnMapper.selectGenTableColumnListByTableId(tableId);
+	}
+	
+    /**
+     * 新增业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int insertGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.insertGenTableColumn(genTableColumn);
+	}
+	
+	/**
+     * 修改业务字段
+     * 
+     * @param genTableColumn 业务字段信息
+     * @return 结果
+     */
+	@Override
+	public int updateGenTableColumn(GenTableColumn genTableColumn)
+	{
+	    return genTableColumnMapper.updateGenTableColumn(genTableColumn);
+	}
+
+	/**
+     * 删除业务字段对象
+     * 
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+	@Override
+	public int deleteGenTableColumnByIds(String ids)
+	{
+		return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
+	}
+}

+ 521 - 0
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java

@@ -0,0 +1,521 @@
+package com.ruoyi.generator.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.constant.GenConstants;
+import com.ruoyi.common.core.text.CharsetKit;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.generator.domain.GenTable;
+import com.ruoyi.generator.domain.GenTableColumn;
+import com.ruoyi.generator.mapper.GenTableColumnMapper;
+import com.ruoyi.generator.mapper.GenTableMapper;
+import com.ruoyi.generator.util.GenUtils;
+import com.ruoyi.generator.util.VelocityInitializer;
+import com.ruoyi.generator.util.VelocityUtils;
+
+/**
+ * 业务 服务层实现
+ * 
+ * @author ruoyi
+ */
+@Service
+public class GenTableServiceImpl implements IGenTableService
+{
+    private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
+
+    @Autowired
+    private GenTableMapper genTableMapper;
+
+    @Autowired
+    private GenTableColumnMapper genTableColumnMapper;
+
+    /**
+     * 查询业务信息
+     * 
+     * @param id 业务ID
+     * @return 业务信息
+     */
+    @Override
+    public GenTable selectGenTableById(Long id)
+    {
+        GenTable genTable = genTableMapper.selectGenTableById(id);
+        setTableFromOptions(genTable);
+        return genTable;
+    }
+
+    /**
+     * 查询业务列表
+     * 
+     * @param genTable 业务信息
+     * @return 业务集合
+     */
+    @Override
+    public List<GenTable> selectGenTableList(GenTable genTable)
+    {
+        return genTableMapper.selectGenTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param genTable 业务信息
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableList(GenTable genTable)
+    {
+        return genTableMapper.selectDbTableList(genTable);
+    }
+
+    /**
+     * 查询据库列表
+     * 
+     * @param tableNames 表名称组
+     * @return 数据库表集合
+     */
+    @Override
+    public List<GenTable> selectDbTableListByNames(String[] tableNames)
+    {
+        return genTableMapper.selectDbTableListByNames(tableNames);
+    }
+
+    /**
+     * 查询所有表信息
+     * 
+     * @return 表信息集合
+     */
+    @Override
+    public List<GenTable> selectGenTableAll()
+    {
+        return genTableMapper.selectGenTableAll();
+    }
+
+    /**
+     * 修改业务
+     * 
+     * @param genTable 业务信息
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void updateGenTable(GenTable genTable)
+    {
+        String options = JSON.toJSONString(genTable.getParams());
+        genTable.setOptions(options);
+        int row = genTableMapper.updateGenTable(genTable);
+        if (row > 0)
+        {
+            for (GenTableColumn cenTableColumn : genTable.getColumns())
+            {
+                genTableColumnMapper.updateGenTableColumn(cenTableColumn);
+            }
+        }
+    }
+
+    /**
+     * 删除业务对象
+     * 
+     * @param tableIds 需要删除的数据ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public void deleteGenTableByIds(Long[] tableIds)
+    {
+        genTableMapper.deleteGenTableByIds(tableIds);
+        genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
+    }
+
+    /**
+     * 导入表结构
+     * 
+     * @param tableList 导入表列表
+     */
+    @Override
+    @Transactional
+    public void importGenTable(List<GenTable> tableList)
+    {
+        String operName = SecurityUtils.getUsername();
+        try
+        {
+            for (GenTable table : tableList)
+            {
+                String tableName = table.getTableName();
+                GenUtils.initTable(table, operName);
+                int row = genTableMapper.insertGenTable(table);
+                if (row > 0)
+                {
+                    // 保存列信息
+                    List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+                    for (GenTableColumn column : genTableColumns)
+                    {
+                        GenUtils.initColumnField(column, table);
+                        genTableColumnMapper.insertGenTableColumn(column);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 预览代码
+     * 
+     * @param tableId 表编号
+     * @return 预览数据列表
+     */
+    @Override
+    public Map<String, String> previewCode(Long tableId)
+    {
+        Map<String, String> dataMap = new LinkedHashMap<>();
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableById(tableId);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            dataMap.put(template, sw.toString());
+        }
+        return dataMap;
+    }
+
+    /**
+     * 生成代码(下载方式)
+     * 
+     * @param tableName 表名称
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String tableName)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        generatorCode(tableName, zip);
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 生成代码(自定义路径)
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    public void generatorCode(String tableName)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+        for (String template : templates)
+        {
+            if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm"))
+            {
+                // 渲染模板
+                StringWriter sw = new StringWriter();
+                Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+                tpl.merge(context, sw);
+                try
+                {
+                    String path = getGenPath(table, template);
+                    FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
+                }
+                catch (IOException e)
+                {
+                    throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
+                }
+            }
+        }
+    }
+
+    /**
+     * 同步数据库
+     * 
+     * @param tableName 表名称
+     */
+    @Override
+    @Transactional
+    public void synchDb(String tableName)
+    {
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        List<GenTableColumn> tableColumns = table.getColumns();
+        Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));
+
+        List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
+        if (StringUtils.isEmpty(dbTableColumns))
+        {
+            throw new ServiceException("同步数据失败,原表结构不存在");
+        }
+        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
+
+        dbTableColumns.forEach(column -> {
+            GenUtils.initColumnField(column, table);
+            if (tableColumnMap.containsKey(column.getColumnName()))
+            {
+                GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
+                column.setColumnId(prevColumn.getColumnId());
+                if (column.isList())
+                {
+                    // 如果是列表,继续保留查询方式/字典类型选项
+                    column.setDictType(prevColumn.getDictType());
+                    column.setQueryType(prevColumn.getQueryType());
+                }
+                if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
+                        && (column.isInsert() || column.isEdit())
+                        && ((column.isUsableColumn()) || (!column.isSuperColumn())))
+                {
+                    // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
+                    column.setIsRequired(prevColumn.getIsRequired());
+                    column.setHtmlType(prevColumn.getHtmlType());
+                }
+                genTableColumnMapper.updateGenTableColumn(column);
+            }
+            else
+            {
+                genTableColumnMapper.insertGenTableColumn(column);
+            }
+        });
+
+        List<GenTableColumn> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
+        if (StringUtils.isNotEmpty(delColumns))
+        {
+            genTableColumnMapper.deleteGenTableColumns(delColumns);
+        }
+    }
+
+    /**
+     * 批量生成代码(下载方式)
+     * 
+     * @param tableNames 表数组
+     * @return 数据
+     */
+    @Override
+    public byte[] downloadCode(String[] tableNames)
+    {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        for (String tableName : tableNames)
+        {
+            generatorCode(tableName, zip);
+        }
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 查询表信息并生成代码
+     */
+    private void generatorCode(String tableName, ZipOutputStream zip)
+    {
+        // 查询表信息
+        GenTable table = genTableMapper.selectGenTableByName(tableName);
+        // 设置主子表信息
+        setSubTable(table);
+        // 设置主键列信息
+        setPkColumn(table);
+
+        VelocityInitializer.initVelocity();
+
+        VelocityContext context = VelocityUtils.prepareContext(table);
+
+        // 获取模板列表
+        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
+        for (String template : templates)
+        {
+            // 渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
+            tpl.merge(context, sw);
+            try
+            {
+                // 添加到zip
+                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
+                IOUtils.write(sw.toString(), zip, Constants.UTF8);
+                IOUtils.closeQuietly(sw);
+                zip.flush();
+                zip.closeEntry();
+            }
+            catch (IOException e)
+            {
+                log.error("渲染模板失败,表名:" + table.getTableName(), e);
+            }
+        }
+    }
+
+    /**
+     * 修改保存参数校验
+     * 
+     * @param genTable 业务信息
+     */
+    @Override
+    public void validateEdit(GenTable genTable)
+    {
+        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory()))
+        {
+            String options = JSON.toJSONString(genTable.getParams());
+            JSONObject paramsObj = JSON.parseObject(options);
+            if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE)))
+            {
+                throw new ServiceException("树编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE)))
+            {
+                throw new ServiceException("树父编码字段不能为空");
+            }
+            else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME)))
+            {
+                throw new ServiceException("树名称字段不能为空");
+            }
+            else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory()))
+            {
+                if (StringUtils.isEmpty(genTable.getSubTableName()))
+                {
+                    throw new ServiceException("关联子表的表名不能为空");
+                }
+                else if (StringUtils.isEmpty(genTable.getSubTableFkName()))
+                {
+                    throw new ServiceException("子表关联的外键名不能为空");
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置主键列信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setPkColumn(GenTable table)
+    {
+        for (GenTableColumn column : table.getColumns())
+        {
+            if (column.isPk())
+            {
+                table.setPkColumn(column);
+                break;
+            }
+        }
+        if (StringUtils.isNull(table.getPkColumn()))
+        {
+            table.setPkColumn(table.getColumns().get(0));
+        }
+        if (GenConstants.TPL_SUB.equals(table.getTplCategory()))
+        {
+            for (GenTableColumn column : table.getSubTable().getColumns())
+            {
+                if (column.isPk())
+                {
+                    table.getSubTable().setPkColumn(column);
+                    break;
+                }
+            }
+            if (StringUtils.isNull(table.getSubTable().getPkColumn()))
+            {
+                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
+            }
+        }
+    }
+
+    /**
+     * 设置主子表信息
+     * 
+     * @param table 业务表信息
+     */
+    public void setSubTable(GenTable table)
+    {
+        String subTableName = table.getSubTableName();
+        if (StringUtils.isNotEmpty(subTableName))
+        {
+            table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
+        }
+    }
+
+    /**
+     * 设置代码生成其他选项值
+     * 
+     * @param genTable 设置后的生成对象
+     */
+    public void setTableFromOptions(GenTable genTable)
+    {
+        JSONObject paramsObj = JSON.parseObject(genTable.getOptions());
+        if (StringUtils.isNotNull(paramsObj))
+        {
+            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
+            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
+            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
+            String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
+            String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);
+
+            genTable.setTreeCode(treeCode);
+            genTable.setTreeParentCode(treeParentCode);
+            genTable.setTreeName(treeName);
+            genTable.setParentMenuId(parentMenuId);
+            genTable.setParentMenuName(parentMenuName);
+        }
+    }
+
+    /**
+     * 获取代码生成地址
+     * 
+     * @param table 业务表信息
+     * @param template 模板文件路径
+     * @return 生成地址
+     */
+    public static String getGenPath(GenTable table, String template)
+    {
+        String genPath = table.getGenPath();
+        if (StringUtils.equals(genPath, "/"))
+        {
+            return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
+        }
+        return genPath + File.separator + VelocityUtils.getFileName(template, table);
+    }
+}

+ 44 - 0
ruoyi-generator/src/main/resources/vm/js/api.js.vm

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询${functionName}列表
+export function list${BusinessName}(query) {
+  return request({
+    url: '/${moduleName}/${businessName}/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询${functionName}详细
+export function get${BusinessName}(${pkColumn.javaField}) {
+  return request({
+    url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+    method: 'get'
+  })
+}
+
+// 新增${functionName}
+export function add${BusinessName}(data) {
+  return request({
+    url: '/${moduleName}/${businessName}',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改${functionName}
+export function update${BusinessName}(data) {
+  return request({
+    url: '/${moduleName}/${businessName}',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除${functionName}
+export function del${BusinessName}(${pkColumn.javaField}) {
+  return request({
+    url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
+    method: 'delete'
+  })
+}

+ 502 - 0
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -0,0 +1,502 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+	    <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-sort"
+          size="mini"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="${businessName}List"
+      row-key="${treeCode}"
+      :default-expand-all="isExpandAll"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template slot-scope="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+      <el-table-column label="${comment}" prop="${javaField}" />
+#else
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-plus"
+            @click="handleAdd(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:add']"
+          >新增</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+        <el-form-item label="${comment}" prop="${treeParentCode}">
+          <treeselect v-model="form.${treeParentCode}" :options="${businessName}Options" :normalizer="normalizer" placeholder="请选择${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
+
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
+
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+
+export default {
+  name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
+  components: {
+    Treeselect
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // ${functionName}表格数据
+      ${businessName}List: [],
+      // ${functionName}树选项
+      ${businessName}Options: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      // $comment时间范围
+      daterange${AttrName}: [],
+#end
+#end
+      // 查询参数
+      queryParams: {
+#foreach ($column in $columns)
+#if($column.query)
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+#foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+        $column.javaField: [
+          { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
+        ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询${functionName}列表 */
+    getList() {
+      this.loading = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      this.queryParams.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
+        this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
+        this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
+      }
+#end
+#end
+      list${BusinessName}(this.queryParams).then(response => {
+        this.${businessName}List = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+        this.loading = false;
+      });
+    },
+    /** 转换${functionName}数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.${treeCode},
+        label: node.${treeName},
+        children: node.children
+      };
+    },
+	/** 查询${functionName}下拉树结构 */
+    getTreeselect() {
+      list${BusinessName}().then(response => {
+        this.${businessName}Options = [];
+        const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+        data.children = this.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+        this.${businessName}Options.push(data);
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+#foreach ($column in $columns)
+#if($column.htmlType == "radio")
+        $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
+
+#elseif($column.htmlType == "checkbox")
+        $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.daterange${AttrName} = [];
+#end
+#end
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null && row.${treeCode}) {
+        this.form.${treeParentCode} = row.${treeCode};
+      } else {
+        this.form.${treeParentCode} = 0;
+      }
+      this.open = true;
+      this.title = "添加${functionName}";
+    },
+    /** 展开/折叠操作 */
+    toggleExpandAll() {
+      this.refreshTable = false;
+      this.isExpandAll = !this.isExpandAll;
+      this.$nextTick(() => {
+        this.refreshTable = true;
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null) {
+        this.form.${treeParentCode} = row.${treeCode};
+      }
+      get${BusinessName}(row.${pkColumn.javaField}).then(response => {
+        this.form = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        this.form.$column.javaField = this.form.${column.javaField}.split(",");
+#end
+#end
+        this.open = true;
+        this.title = "修改${functionName}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.#[[$]]#refs["form"].validate(valid => {
+        if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+          this.form.$column.javaField = this.form.${column.javaField}.join(",");
+#end
+#end
+          if (this.form.${pkColumn.javaField} != null) {
+            update${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() {
+        return del${BusinessName}(row.${pkColumn.javaField});
+      }).then(() => {
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 598 - 0
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -0,0 +1,598 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in dict.type.${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="请选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          style="width: 240px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['${moduleName}:${businessName}:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['${moduleName}:${businessName}:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['${moduleName}:${businessName}:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template slot-scope="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
+
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in dict.type.${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
+
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="请选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+#if($table.sub)
+        <el-divider content-position="center">${subTable.functionName}信息</el-divider>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd${subClassName}">添加</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete${subClassName}">删除</el-button>
+          </el-col>
+        </el-row>
+        <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="序号" align="center" prop="index" width="50"/>
+#foreach($column in $subTable.columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk || $javaField == ${subTableFkclassName})
+#elseif($column.list && $column.htmlType == "input")
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && $column.htmlType == "datetime")
+          <el-table-column label="$comment" prop="${javaField}" width="240">
+            <template slot-scope="scope">
+              <el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd" placeholder="请选择$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option
+                  v-for="dict in dict.type.$column.dictType"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                ></el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template slot-scope="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </template>
+          </el-table-column>
+#end
+#end
+        </el-table>
+#end
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+export default {
+  name: "${BusinessName}",
+#if(${dicts} != '')
+  dicts: [${dicts}],
+#end
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+#if($table.sub)
+      // 子表选中数据
+      checked${subClassName}: [],
+#end
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // ${functionName}表格数据
+      ${businessName}List: [],
+#if($table.sub)
+      // ${subTable.functionName}表格数据
+      ${subclassName}List: [],
+#end
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      // $comment时间范围
+      daterange${AttrName}: [],
+#end
+#end
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+#foreach ($column in $columns)
+#if($column.query)
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+#foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+        $column.javaField: [
+          { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
+        ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询${functionName}列表 */
+    getList() {
+      this.loading = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      this.queryParams.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
+        this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
+        this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
+      }
+#end
+#end
+      list${BusinessName}(this.queryParams).then(response => {
+        this.${businessName}List = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+#foreach ($column in $columns)
+#if($column.htmlType == "radio")
+        $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
+#elseif($column.htmlType == "checkbox")
+        $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+        $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+      };
+#if($table.sub)
+      this.${subclassName}List = [];
+#end
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+      this.daterange${AttrName} = [];
+#end
+#end
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.${pkColumn.javaField})
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加${functionName}";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const ${pkColumn.javaField} = row.${pkColumn.javaField} || this.ids
+      get${BusinessName}(${pkColumn.javaField}).then(response => {
+        this.form = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+        this.form.$column.javaField = this.form.${column.javaField}.split(",");
+#end
+#end
+#if($table.sub)
+        this.${subclassName}List = response.data.${subclassName}List;
+#end
+        this.open = true;
+        this.title = "修改${functionName}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.#[[$]]#refs["form"].validate(valid => {
+        if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+          this.form.$column.javaField = this.form.${column.javaField}.join(",");
+#end
+#end
+#if($table.sub)
+          this.form.${subclassName}List = this.${subclassName}List;
+#end
+          if (this.form.${pkColumn.javaField} != null) {
+            update${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add${BusinessName}(this.form).then(response => {
+              this.#[[$modal]]#.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
+      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
+        return del${BusinessName}(${pkColumn.javaField}s);
+      }).then(() => {
+        this.getList();
+        this.#[[$modal]]#.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+#if($table.sub)
+	/** ${subTable.functionName}序号 */
+    row${subClassName}Index({ row, rowIndex }) {
+      row.index = rowIndex + 1;
+    },
+    /** ${subTable.functionName}添加按钮操作 */
+    handleAdd${subClassName}() {
+      let obj = {};
+#foreach($column in $subTable.columns)
+#if($column.pk || $column.javaField == ${subTableFkclassName})
+#elseif($column.list && "" != $javaField)
+      obj.$column.javaField = "";
+#end
+#end
+      this.${subclassName}List.push(obj);
+    },
+    /** ${subTable.functionName}删除按钮操作 */
+    handleDelete${subClassName}() {
+      if (this.checked${subClassName}.length == 0) {
+        this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
+      } else {
+        const ${subclassName}List = this.${subclassName}List;
+        const checked${subClassName} = this.checked${subClassName};
+        this.${subclassName}List = ${subclassName}List.filter(function(item) {
+          return checked${subClassName}.indexOf(item.index) == -1
+        });
+      }
+    },
+    /** 复选框选中数据 */
+    handle${subClassName}SelectionChange(selection) {
+      this.checked${subClassName} = selection.map(item => item.index)
+    },
+#end
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('${moduleName}/${businessName}/export', {
+        ...this.queryParams
+      }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+    }
+  }
+};
+</script>

+ 486 - 0
ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm

@@ -0,0 +1,486 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in ${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}" style="width: 308px">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          value-format="YYYY-MM-DD"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="Sort"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="${businessName}List"
+      row-key="${treeCode}"
+      :default-expand-all="isExpandAll"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    >
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template #default="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template #default="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+#if(${foreach.index} == 1)
+      <el-table-column label="${comment}" prop="${javaField}" />
+#else
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+            type="text"
+            icon="Edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            type="text"
+            icon="Plus"
+            @click="handleAdd(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:add']"
+          >新增</el-button>
+          <el-button
+            type="text"
+            icon="Delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if("" != $treeParentCode && $column.javaField == $treeParentCode)
+        <el-form-item label="${comment}" prop="${treeParentCode}">
+          <el-tree-select
+            v-model="form.${treeParentCode}"
+            :data="${businessName}Options"
+            :props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
+            value-key="${treeCode}"
+            placeholder="请选择${comment}"
+            check-strictly
+          />
+        </el-form-item>
+#elseif($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
+
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
+
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+const { proxy } = getCurrentInstance();
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = proxy.useDict(${dicts});
+#end
+
+const ${businessName}List = ref([]);
+const ${businessName}Options = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const title = ref("");
+const isExpandAll = ref(true);
+const refreshTable = ref(true);
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const daterange${AttrName} = ref([]);
+#end
+#end
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    #foreach ($column in $columns)
+#if($column.query)
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  },
+  rules: {
+    #foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+function getList() {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  if (null != daterange${AttrName} && '' != daterange${AttrName}) {
+    queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0];
+    queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1];
+  }
+#end
+#end
+  list${BusinessName}(queryParams.value).then(response => {
+    ${businessName}List.value = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+    loading.value = false;
+  });
+}
+
+/** 查询${functionName}下拉树结构 */
+function getTreeselect() {
+  list${BusinessName}().then(response => {
+    ${businessName}Options.value = [];
+    const data = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
+    data.children = proxy.handleTree(response.data, "${treeCode}", "${treeParentCode}");
+    ${businessName}Options.value.push(data);
+  });
+}
+	
+// 取消按钮
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = {
+#foreach ($column in $columns)
+#if($column.htmlType == "radio")
+    $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
+
+#elseif($column.htmlType == "checkbox")
+    $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  };
+  proxy.resetForm("${businessName}Ref");
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  daterange${AttrName}.value = [];
+#end
+#end
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+
+/** 新增按钮操作 */
+function handleAdd(row) {
+  reset();
+  getTreeselect();
+  if (row != null && row.${treeCode}) {
+    form.value.${treeParentCode} = row.${treeCode};
+  } else {
+    form.value.${treeParentCode} = 0;
+  }
+  open.value = true;
+  title.value = "添加${functionName}";
+}
+
+/** 展开/折叠操作 */
+function toggleExpandAll() {
+  refreshTable.value = false;
+  isExpandAll.value = !isExpandAll.value;
+  nextTick(() => {
+    refreshTable.value = true;
+  });
+}
+
+/** 修改按钮操作 */
+async function handleUpdate(row) {
+  reset();
+  await getTreeselect();
+  if (row != null) {
+    form.value.${treeParentCode} = row.${treeCode};
+  }
+  get${BusinessName}(row.${pkColumn.javaField}).then(response => {
+    form.value = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    form.value.$column.javaField = form.value.${column.javaField}.split(",");
+#end
+#end
+    open.value = true;
+    title.value = "修改${functionName}";
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => {
+    if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+      form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+      if (form.value.${pkColumn.javaField} != null) {
+        update${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        add${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?').then(function() {
+    return del${BusinessName}(row.${pkColumn.javaField});
+  }).then(() => {
+    getList();
+    proxy.#[[$modal]]#.msgSuccess("删除成功");
+  }).catch(() => {});
+}
+
+getList();
+</script>

+ 596 - 0
ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm

@@ -0,0 +1,596 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+#foreach($column in $columns)
+#if($column.query)
+#set($dictType=$column.dictType)
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.htmlType == "input")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-input
+          v-model="queryParams.${column.javaField}"
+          placeholder="请输入${comment}"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option
+            v-for="dict in ${dictType}"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
+      <el-form-item label="${comment}" prop="${column.javaField}">
+        <el-date-picker clearable
+          v-model="queryParams.${column.javaField}"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="请选择${comment}">
+        </el-date-picker>
+      </el-form-item>
+#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+      <el-form-item label="${comment}" style="width: 308px">
+        <el-date-picker
+          v-model="daterange${AttrName}"
+          value-format="YYYY-MM-DD"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+      </el-form-item>
+#end
+#end
+#end
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+          v-hasPermi="['${moduleName}:${businessName}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="Edit"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['${moduleName}:${businessName}:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="Delete"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['${moduleName}:${businessName}:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="Download"
+          @click="handleExport"
+          v-hasPermi="['${moduleName}:${businessName}:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+#foreach($column in $columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#elseif($column.list && $column.htmlType == "datetime")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+#elseif($column.list && $column.htmlType == "imageUpload")
+      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
+        <template #default="scope">
+          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $column.dictType)
+      <el-table-column label="${comment}" align="center" prop="${javaField}">
+        <template #default="scope">
+#if($column.htmlType == "checkbox")
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
+#else
+          <dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
+#end
+        </template>
+      </el-table-column>
+#elseif($column.list && "" != $javaField)
+      <el-table-column label="${comment}" align="center" prop="${javaField}" />
+#end
+#end
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+            type="text"
+            icon="Edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:edit']"
+          >修改</el-button>
+          <el-button
+            type="text"
+            icon="Delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['${moduleName}:${businessName}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改${functionName}对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="${businessName}Ref" :model="form" :rules="rules" label-width="80px">
+#foreach($column in $columns)
+#set($field=$column.javaField)
+#if($column.insert && !$column.pk)
+#if(($column.usableColumn) || (!$column.superColumn))
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#set($dictType=$column.dictType)
+#if($column.htmlType == "input")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
+        </el-form-item>
+#elseif($column.htmlType == "imageUpload")
+        <el-form-item label="${comment}">
+          <image-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "fileUpload")
+        <el-form-item label="${comment}">
+          <file-upload v-model="form.${field}"/>
+        </el-form-item>
+#elseif($column.htmlType == "editor")
+        <el-form-item label="${comment}">
+          <editor v-model="form.${field}" :min-height="192"/>
+        </el-form-item>
+#elseif($column.htmlType == "select" && "" != $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.label"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
+
+            ></el-option>
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "select" && $dictType)
+        <el-form-item label="${comment}" prop="${field}">
+          <el-select v-model="form.${field}" placeholder="请选择${comment}">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              :label="dict.value">
+              {{dict.label}}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "checkbox" && $dictType)
+        <el-form-item label="${comment}">
+          <el-checkbox-group v-model="form.${field}">
+            <el-checkbox>请选择字典生成</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && "" != $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio
+              v-for="dict in ${dictType}"
+              :key="dict.value"
+              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
+
+            >{{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "radio" && $dictType)
+        <el-form-item label="${comment}">
+          <el-radio-group v-model="form.${field}">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+#elseif($column.htmlType == "datetime")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-date-picker clearable
+            v-model="form.${field}"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="请选择${comment}">
+          </el-date-picker>
+        </el-form-item>
+#elseif($column.htmlType == "textarea")
+        <el-form-item label="${comment}" prop="${field}">
+          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+#end
+#end
+#end
+#end
+#if($table.sub)
+        <el-divider content-position="center">${subTable.functionName}信息</el-divider>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" icon="Plus" @click="handleAdd${subClassName}">添加</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" icon="Delete" @click="handleDelete${subClassName}">删除</el-button>
+          </el-col>
+        </el-row>
+        <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
+          <el-table-column type="selection" width="50" align="center" />
+          <el-table-column label="序号" align="center" prop="index" width="50"/>
+#foreach($column in $subTable.columns)
+#set($javaField=$column.javaField)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+#if($column.pk || $javaField == ${subTableFkclassName})
+#elseif($column.list && $column.htmlType == "input")
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
+            </template>
+          </el-table-column>
+#elseif($column.list && $column.htmlType == "datetime")
+          <el-table-column label="$comment" prop="${javaField}" width="240">
+            <template #default="scope">
+              <el-date-picker clearable
+                v-model="scope.row.$javaField"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择$comment">
+              </el-date-picker>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option
+                  v-for="dict in $column.dictType"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                ></el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
+          <el-table-column label="$comment" prop="${javaField}" width="150">
+            <template #default="scope">
+              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
+                <el-option label="请选择字典生成" value="" />
+              </el-select>
+            </template>
+          </el-table-column>
+#end
+#end
+        </el-table>
+#end
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="${BusinessName}">
+import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
+
+const { proxy } = getCurrentInstance();
+#if(${dicts} != '')
+#set($dictsNoSymbol=$dicts.replace("'", ""))
+const { ${dictsNoSymbol} } = proxy.useDict(${dicts});
+#end
+
+const ${businessName}List = ref([]);
+#if($table.sub)
+const ${subclassName}List = ref([]);
+#end
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+#if($table.sub)
+const checked${subClassName} = ref([]);
+#end
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+const daterange${AttrName} = ref([]);
+#end
+#end
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    #foreach ($column in $columns)
+#if($column.query)
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  },
+  rules: {
+    #foreach ($column in $columns)
+#if($column.required)
+#set($parentheseIndex=$column.columnComment.indexOf("("))
+#if($parentheseIndex != -1)
+#set($comment=$column.columnComment.substring(0, $parentheseIndex))
+#else
+#set($comment=$column.columnComment)
+#end
+    $column.javaField: [
+      { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
+    ]#if($foreach.count != $columns.size()),#end
+#end
+#end
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询${functionName}列表 */
+function getList() {
+  loading.value = true;
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+  queryParams.value.params = {};
+#break
+#end
+#end
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  if (null != daterange${AttrName} && '' != daterange${AttrName}) {
+    queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0];
+    queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1];
+  }
+#end
+#end
+  list${BusinessName}(queryParams.value).then(response => {
+    ${businessName}List.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+
+// 取消按钮
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+// 表单重置
+function reset() {
+  form.value = {
+#foreach ($column in $columns)
+#if($column.htmlType == "radio")
+    $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
+#elseif($column.htmlType == "checkbox")
+    $column.javaField: []#if($foreach.count != $columns.size()),#end
+#else
+    $column.javaField: null#if($foreach.count != $columns.size()),#end
+#end
+#end
+  };
+#if($table.sub)
+  ${subclassName}List.value = [];
+#end
+  proxy.resetForm("${businessName}Ref");
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+#foreach ($column in $columns)
+#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
+#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  daterange${AttrName}.value = [];
+#end
+#end
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.${pkColumn.javaField});
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "添加${functionName}";
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset();
+  const ${pkColumn.javaField} = row.${pkColumn.javaField} || ids.value
+  get${BusinessName}(${pkColumn.javaField}).then(response => {
+    form.value = response.data;
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+    form.value.$column.javaField = form.value.${column.javaField}.split(",");
+#end
+#end
+#if($table.sub)
+    ${subclassName}List.value = response.data.${subclassName}List;
+#end
+    open.value = true;
+    title.value = "修改${functionName}";
+  });
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.#[[$]]#refs["${businessName}Ref"].validate(valid => {
+    if (valid) {
+#foreach ($column in $columns)
+#if($column.htmlType == "checkbox")
+      form.value.$column.javaField = form.value.${column.javaField}.join(",");
+#end
+#end
+#if($table.sub)
+      form.value.${subclassName}List = ${subclassName}List.value;
+#end
+      if (form.value.${pkColumn.javaField} != null) {
+        update${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        add${BusinessName}(form.value).then(response => {
+          proxy.#[[$modal]]#.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const ${pkColumn.javaField}s = row.${pkColumn.javaField} || ids.value;
+  proxy.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
+    return del${BusinessName}(${pkColumn.javaField}s);
+  }).then(() => {
+    getList();
+    proxy.#[[$modal]]#.msgSuccess("删除成功");
+  }).catch(() => {});
+}
+
+#if($table.sub)
+/** ${subTable.functionName}序号 */
+function row${subClassName}Index({ row, rowIndex }) {
+  row.index = rowIndex + 1;
+}
+
+/** ${subTable.functionName}添加按钮操作 */
+function handleAdd${subClassName}() {
+  let obj = {};
+#foreach($column in $subTable.columns)
+#if($column.pk || $column.javaField == ${subTableFkclassName})
+#elseif($column.list && "" != $javaField)
+  obj.$column.javaField = "";
+#end
+#end
+  ${subclassName}List.value.push(obj);
+}
+
+/** ${subTable.functionName}删除按钮操作 */
+function handleDelete${subClassName}() {
+  if (checked${subClassName}.value.length == 0) {
+    proxy.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
+  } else {
+    const ${subclassName}s = ${subclassName}List.value;
+    const checked${subClassName}s = checked${subClassName}.value;
+    ${subclassName}List.value = ${subclassName}s.filter(function(item) {
+      return checked${subClassName}s.indexOf(item.index) == -1
+    });
+  }
+}
+
+/** 复选框选中数据 */
+function handle${subClassName}SelectionChange(selection) {
+  checked${subClassName}.value = selection.map(item => item.index)
+}
+
+#end
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download('${moduleName}/${businessName}/export', {
+    ...queryParams.value
+  }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
+}
+
+getList();
+</script>

+ 1 - 0
ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt

@@ -0,0 +1 @@
+如果使用的是RuoYi-Vue3前端,那么需要覆盖一下此目录的模板index.vue.vm、index-tree.vue.vm文件到上级vue目录。

+ 114 - 0
ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceController.java

@@ -0,0 +1,114 @@
+package com.ruoyi.system.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.BusDevice;
+import com.ruoyi.system.service.IBusDeviceService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 设备Controller
+ * 
+ * @author ruoyi
+ * @date 2022-05-31
+ */
+@Api("设备管理")
+@RestController
+@RequestMapping("/system/device")
+public class BusDeviceController extends BaseController
+{
+    @Autowired
+    private IBusDeviceService busDeviceService;
+
+    /**
+     * 查询设备列表
+     */
+    @ApiOperation("查询设备列表")
+    @PreAuthorize("@ss.hasPermi('system:device:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BusDevice busDevice)
+    {
+        startPage();
+        List<BusDevice> list = busDeviceService.selectBusDeviceList(busDevice);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出设备列表
+     */
+    @ApiOperation("导出设备列表")
+    @PreAuthorize("@ss.hasPermi('system:device:export')")
+    @Log(title = "设备", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BusDevice busDevice)
+    {
+        List<BusDevice> list = busDeviceService.selectBusDeviceList(busDevice);
+        ExcelUtil<BusDevice> util = new ExcelUtil<BusDevice>(BusDevice.class);
+        util.exportExcel(response, list, "设备数据");
+    }
+
+    /**
+     * 获取设备详细信息
+     */
+    @ApiOperation("获取设备详细信息")
+    @PreAuthorize("@ss.hasPermi('system:device:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(busDeviceService.selectBusDeviceById(id));
+    }
+
+    /**
+     * 新增设备
+     */
+    @ApiOperation("新增设备")
+    @PreAuthorize("@ss.hasPermi('system:device:add')")
+    @Log(title = "设备", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BusDevice busDevice)
+    {
+        return toAjax(busDeviceService.insertBusDevice(busDevice));
+    }
+
+    /**
+     * 修改设备
+     */
+    @ApiOperation("修改设备")
+    @PreAuthorize("@ss.hasPermi('system:device:edit')")
+    @Log(title = "设备", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BusDevice busDevice)
+    {
+        return toAjax(busDeviceService.updateBusDevice(busDevice));
+    }
+
+    /**
+     * 删除设备
+     */
+    @ApiOperation("删除设备")
+    @PreAuthorize("@ss.hasPermi('system:device:remove')")
+    @Log(title = "设备", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(busDeviceService.deleteBusDeviceByIds(ids));
+    }
+}

+ 149 - 0
ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceHistoryController.java

@@ -0,0 +1,149 @@
+package com.ruoyi.system.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.system.domain.BusDevice;
+import com.ruoyi.system.domain.BusPatient;
+import com.ruoyi.system.domain.dto.Cure;
+import com.ruoyi.system.domain.dto.SignDto;
+import com.ruoyi.system.service.IBusDeviceHistoryService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.BusDeviceHistory;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 透析管理Controller
+ * 
+ * @author zsl
+ * @date 2022-06-07
+ */
+@Api("透析管理")
+@RestController
+@RequestMapping("/system/history")
+public class BusDeviceHistoryController extends BaseController
+{
+    @Autowired
+    private IBusDeviceHistoryService busDeviceHistoryService;
+
+    /**
+     * 查询透析管理列表
+     */
+    @ApiOperation("查询透析管理列表")
+    @PreAuthorize("@ss.hasPermi('system:history:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BusDeviceHistory busDeviceHistory)
+    {
+        startPage();
+        List<BusDeviceHistory> list = busDeviceHistoryService.selectBusDeviceHistoryList(busDeviceHistory);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出透析管理列表
+     */
+    @ApiOperation("导出透析管理列表")
+    @PreAuthorize("@ss.hasPermi('system:history:export')")
+    @Log(title = "透析管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, BusDeviceHistory busDeviceHistory)
+    {
+        List<BusDeviceHistory> list = busDeviceHistoryService.selectBusDeviceHistoryList(busDeviceHistory);
+        ExcelUtil<BusDeviceHistory> util = new ExcelUtil<BusDeviceHistory>(BusDeviceHistory.class);
+        util.exportExcel(response, list, "透析管理数据");
+    }
+
+
+    /**
+     * 获取透析管理详细信息
+     */
+    @ApiOperation("获取透析管理详细信息")
+    @PreAuthorize("@ss.hasPermi('system:history:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(busDeviceHistoryService.selectBusDeviceHistoryById(id));
+    }
+
+    /**
+     * 新增透析管理
+     */
+    @ApiOperation("新增透析管理")
+    @PreAuthorize("@ss.hasPermi('system:history:add')")
+    @Log(title = "透析管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody BusDeviceHistory busDeviceHistory)
+    {
+        return toAjax(busDeviceHistoryService.insertBusDeviceHistory(busDeviceHistory));
+    }
+
+    /**
+     * 签到管理
+     */
+    @ApiOperation("患者签到")
+    @PreAuthorize("@ss.hasPermi('system:history:sign')")
+    @Log(title = "患者签到", businessType = BusinessType.INSERT)
+    @PostMapping("/sign")
+    public AjaxResult sign(@RequestBody SignDto sign)
+    {
+        return toAjax(busDeviceHistoryService.sign(sign));
+    }
+
+    /**
+     * 修改透析管理
+     */
+    @ApiOperation("修改透析管理")
+    @PreAuthorize("@ss.hasPermi('system:history:edit')")
+    @Log(title = "透析管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody BusDeviceHistory busDeviceHistory)
+    {
+        return toAjax(busDeviceHistoryService.updateBusDeviceHistory(busDeviceHistory));
+    }
+
+    /**
+     * 删除透析管理
+     */
+    @ApiOperation("删除透析管理")
+    @PreAuthorize("@ss.hasPermi('system:history:remove')")
+    @Log(title = "透析管理", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(busDeviceHistoryService.deleteBusDeviceHistoryByIds(ids));
+    }
+    /**
+     * 删除透析管理
+     */
+    @ApiOperation("批量上机管理")
+    @PreAuthorize("@ss.hasPermi('system:history:cure')")
+    @Log(title = "透析管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/cure")
+    public AjaxResult cure(@RequestBody Cure cure)
+    {
+        Long[] ids = new Long[1];
+        ids[0] = cure.getIds();
+        Long[] pids = new Long[1];
+        pids[0] = cure.getPids();
+
+        return toAjax(busDeviceHistoryService.cure(ids,pids));
+    }
+
+
+}

+ 117 - 0
ruoyi-system/src/main/java/com/ruoyi/system/controller/BusDeviceRunningController.java

@@ -0,0 +1,117 @@
+package com.ruoyi.system.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.system.domain.dto.DeviceRunningDto;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.BusDeviceRunning;
+import com.ruoyi.system.service.IBusDeviceRunningService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.page.TableDataInfo;
+
+/**
+ * 运行状态Controller
+ * 
+ * @author zsl
+ * @date 2022-06-17
+ */
+@Api("运行管理")
+@RestController
+@RequestMapping("/system/running")
+public class BusDeviceRunningController extends BaseController
+{
+    @Autowired
+    private IBusDeviceRunningService busDeviceRunningService;
+
+    /**
+     * 查询运行状态列表
+     */
+   // @PreAuthorize("@ss.hasPermi('system:running:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询设备运行状态")
+    public TableDataInfo list(BusDeviceRunning busDeviceRunning)
+    {
+        startPage();
+        System.out.println(busDeviceRunning.getId());
+        List<BusDeviceRunning> list = busDeviceRunningService.selectBusDeviceRunningList(busDeviceRunning);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出运行状态列表
+     */
+ //   @PreAuthorize("@ss.hasPermi('system:running:export')")
+    @Log(title = "运行状态", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出运行状态列表")
+    public void export(HttpServletResponse response, BusDeviceRunning busDeviceRunning)
+    {
+        List<BusDeviceRunning> list = busDeviceRunningService.selectBusDeviceRunningList(busDeviceRunning);
+        ExcelUtil<BusDeviceRunning> util = new ExcelUtil<BusDeviceRunning>(BusDeviceRunning.class);
+        util.exportExcel(response, list, "运行状态数据");
+    }
+
+    /**
+     * 获取运行状态详细信息
+     */
+    //@PreAuthorize("@ss.hasPermi('system:running:query')")
+    @GetMapping(value = "/{id}")
+    @ApiOperation("获取运行状态详细信息")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(busDeviceRunningService.selectBusDeviceRunningById(id));
+    }
+
+    /**
+     * 新增运行状态
+     */
+ //   @PreAuthorize("@ss.hasPermi('system:running:add')")
+    @Log(title = "运行状态")
+    @PostMapping("/add")
+    @ApiOperation("新增运行状态")
+    public AjaxResult add(@RequestBody DeviceRunningDto deviceRunningDto)
+    {
+
+        System.out.println(deviceRunningDto);
+        return toAjax(1==1);
+    }
+    /**
+     * 修改运行状态
+     */
+   // @PreAuthorize("@ss.hasPermi('system:running:edit')")
+    @Log(title = "运行状态", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改运行状态")
+    public AjaxResult edit(@RequestBody BusDeviceRunning busDeviceRunning)
+    {
+        return toAjax(busDeviceRunningService.updateBusDeviceRunning(busDeviceRunning));
+    }
+
+    /**
+     * 删除运行状态
+     */
+ //   @PreAuthorize("@ss.hasPermi('system:running:remove')")
+    @Log(title = "运行状态", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    @ApiOperation("删除运行状态")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(busDeviceRunningService.deleteBusDeviceRunningByIds(ids));
+    }
+}

+ 207 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDevice.java

@@ -0,0 +1,207 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 设备对象 bus_device
+ * 
+ * @author ruoyi
+ * @date 2022-06-10
+ */
+public class BusDevice extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /**  */
+    private Long id;
+
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String deviceId;
+
+    /** 设备区域 */
+    @Excel(name = "设备区域")
+    private String area;
+
+    /** 床位号 */
+    @Excel(name = "床位号")
+    private String bed;
+
+    /** 品牌名称 */
+    @Excel(name = "品牌名称")
+    private String brandName;
+
+    /** 型号 */
+    @Excel(name = "型号")
+    private String model;
+
+    /** 出厂编码 */
+    @Excel(name = "出厂编码")
+    private String manufactureId;
+
+    /** 价格 */
+    @Excel(name = "价格")
+    private String price;
+
+    /** 保修期 */
+    @Excel(name = "保修期")
+    private String warranty;
+
+    /** 是否启用,0检修中,1使用中 */
+    @Excel(name = "是否启用,0检修中,1使用中")
+    private Boolean enable;
+
+    /** 设备状态,0未激活,1在线,2离线 */
+    @Excel(name = "设备状态,0未激活,1在线,2离线")
+    private Integer status;
+
+    /** 删除标志(0代表存在 1代表删除) */
+    @Excel(name = "删除标志", readConverterExp = "0=代表存在,1=代表删除")
+    private Boolean isDelete;
+
+    /** 感染标志,0无,1甲肝,2乙肝,3丙肝,4艾滋,5肺结核,6其他 */
+    @Excel(name = "感染标志,0无,1甲肝,2乙肝,3丙肝,4艾滋,5肺结核,6其他")
+    private Integer infection;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setDeviceId(String deviceId) 
+    {
+        this.deviceId = deviceId;
+    }
+
+    public String getDeviceId() 
+    {
+        return deviceId;
+    }
+    public void setArea(String area) 
+    {
+        this.area = area;
+    }
+
+    public String getArea() 
+    {
+        return area;
+    }
+    public void setBed(String bed) 
+    {
+        this.bed = bed;
+    }
+
+    public String getBed() 
+    {
+        return bed;
+    }
+    public void setBrandName(String brandName) 
+    {
+        this.brandName = brandName;
+    }
+
+    public String getBrandName() 
+    {
+        return brandName;
+    }
+    public void setModel(String model) 
+    {
+        this.model = model;
+    }
+
+    public String getModel() 
+    {
+        return model;
+    }
+    public void setManufactureId(String manufactureId) 
+    {
+        this.manufactureId = manufactureId;
+    }
+
+    public String getManufactureId() 
+    {
+        return manufactureId;
+    }
+    public void setPrice(String price) 
+    {
+        this.price = price;
+    }
+
+    public String getPrice() 
+    {
+        return price;
+    }
+    public void setWarranty(String warranty) 
+    {
+        this.warranty = warranty;
+    }
+
+    public String getWarranty() 
+    {
+        return warranty;
+    }
+    public void setEnable(Boolean enable) 
+    {
+        this.enable = enable;
+    }
+
+    public Boolean getEnable() 
+    {
+        return enable;
+    }
+    public void setStatus(Integer status) 
+    {
+        this.status = status;
+    }
+
+    public Integer getStatus() 
+    {
+        return status;
+    }
+    public void setIsDelete(Boolean isDelete) 
+    {
+        this.isDelete = isDelete;
+    }
+
+    public Boolean getIsDelete() 
+    {
+        return isDelete;
+    }
+    public void setInfection(Integer infection) 
+    {
+        this.infection = infection;
+    }
+
+    public Integer getInfection() 
+    {
+        return infection;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("deviceId", getDeviceId())
+            .append("area", getArea())
+            .append("bed", getBed())
+            .append("brandName", getBrandName())
+            .append("model", getModel())
+            .append("manufactureId", getManufactureId())
+            .append("createTime", getCreateTime())
+            .append("price", getPrice())
+            .append("warranty", getWarranty())
+            .append("updateTime", getUpdateTime())
+            .append("enable", getEnable())
+            .append("status", getStatus())
+            .append("isDelete", getIsDelete())
+            .append("infection", getInfection())
+            .toString();
+    }
+}

+ 980 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDeviceHistory.java

@@ -0,0 +1,980 @@
+package com.ruoyi.system.domain;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 透析管理对象 bus_device_history
+ * 
+ * @author zsl
+ * @date 2022-06-10
+ */
+public class BusDeviceHistory extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 设备id */
+    @Excel(name = "设备id")
+    private Long dId;
+
+    /** 病号id */
+    @Excel(name = "病号id")
+    private Long pId;
+
+    /** 签到时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "签到时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date signTime;
+
+    /** 治疗项目 */
+    @Excel(name = "治疗项目")
+    private String treatmentProject;
+
+    /** 报警信息 */
+    @Excel(name = "报警信息")
+    private Integer alarm;
+
+    /** 数据上传时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "数据上传时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date uploadTime;
+
+    /** 透析机型号 */
+    @Excel(name = "透析机型号")
+    private String machineModel;
+
+    /** 血滤器型号 */
+    @Excel(name = "血滤器型号")
+    private String filterModel;
+
+    /** 血透器型号 */
+    @Excel(name = "血透器型号")
+    private String hemodialysisModel;
+
+    /** 治疗时长 */
+    @Excel(name = "治疗时长")
+    private String treatmentTime;
+
+    /** 目标脱水量 */
+    @Excel(name = "目标脱水量")
+    private String targetDehydration;
+
+    /** 血泵速率 */
+    @Excel(name = "血泵速率")
+    private String bloodPumpRate;
+
+    /** 流量 */
+    @Excel(name = "流量")
+    private String flow;
+
+    /** 钾 */
+    @Excel(name = "钾")
+    private String k;
+
+    /** 钙 */
+    @Excel(name = "钙")
+    private String ca;
+
+    /** 钠 */
+    @Excel(name = "钠")
+    private String na;
+
+    /** 碳酸氢根 */
+    @Excel(name = "碳酸氢根")
+    private String hco3;
+
+    /** 抗凝剂 */
+    @Excel(name = "抗凝剂")
+    private String antithrombotic;
+
+    /** 首剂量 */
+    @Excel(name = "首剂量")
+    private String firstDose;
+
+    /** 追加 */
+    @Excel(name = "追加")
+    private String additionalDose;
+
+    /** 血管通路 */
+    @Excel(name = "血管通路")
+    private String vascularAccess;
+
+    /** 医生 */
+    @Excel(name = "医生")
+    private String doctor;
+
+    /** 护士 */
+    @Excel(name = "护士")
+    private String nurse;
+
+    /** 处方确认(0未确认,1已确认) */
+    @Excel(name = "处方确认", readConverterExp = "0=未确认,1已确认")
+    private Boolean verifyPrescription;
+
+    /** 干体重 */
+    @Excel(name = "干体重")
+    private String targetWeight;
+
+    /** 上次透析后体重 */
+    @Excel(name = "上次透析后体重")
+    private String lastWeight;
+
+    /** 透前体重 */
+    @Excel(name = "透前体重")
+    private String beforeWeight;
+
+    /** 体重增加 */
+    @Excel(name = "体重增加")
+    private String addWeight;
+
+    /** 收缩压 */
+    @Excel(name = "收缩压")
+    private String beforeSbp;
+
+    /** 舒张压 */
+    @Excel(name = "舒张压")
+    private String beforeDbp;
+
+    /** 脉搏 */
+    @Excel(name = "脉搏")
+    private String beforeSphygmus;
+
+    /** 呼吸频率 */
+    @Excel(name = "呼吸频率")
+    private String beforeRr;
+
+    /** 透前体温 */
+    @Excel(name = "透前体温")
+    private String beforeTemperature;
+
+    /** 尿量 */
+    @Excel(name = "尿量")
+    private String urineVolume;
+
+    /** 体重增加 */
+    @Excel(name = "体重增加")
+    private String gainWeight;
+
+    /** 透前评估(0未确认,1已确认) */
+    @Excel(name = "透前评估", readConverterExp = "0=未确认,1已确认")
+    private Boolean verifyBefore;
+
+    /** 透后体重 */
+    @Excel(name = "透后体重")
+    private String preDialysisWeight;
+
+    /** 体重减少 */
+    @Excel(name = "体重减少")
+    private String lossWeight;
+
+    /** 透后体温 */
+    @Excel(name = "透后体温")
+    private String afterTemperature;
+
+    /** 透后收缩压 */
+    @Excel(name = "透后收缩压")
+    private String afterSbp;
+
+    /** 透后舒张压 */
+    @Excel(name = "透后舒张压")
+    private String afterDbp;
+
+    /** 透后脉搏 */
+    @Excel(name = "透后脉搏")
+    private String afterSphygmus;
+
+    /** 透后呼吸频率 */
+    @Excel(name = "透后呼吸频率")
+    private String afterRr;
+
+    /** 透后评估(0未确认,1已确认) */
+    @Excel(name = "透后评估", readConverterExp = "0=未确认,1已确认")
+    private Boolean verifyAfter;
+
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String deviceId;
+
+    /** 设备区域 */
+    @Excel(name = "设备区域")
+    private String area;
+
+    /** 床位号 */
+    @Excel(name = "床位号")
+    private String bed;
+
+    /** 品牌名称 */
+    @Excel(name = "品牌名称")
+    private String brandName;
+
+    /** 型号 */
+    @Excel(name = "型号")
+    private String model;
+
+    /** 出厂编码 */
+    @Excel(name = "出厂编码")
+    private String manufactureId;
+
+    /** 价格 */
+    @Excel(name = "价格")
+    private String price;
+
+    /** 感染标志,0无,1甲肝,2乙肝,3丙肝,4艾滋,5肺结核,6其他 */
+    @Excel(name = "感染标志,0无,1甲肝,2乙肝,3丙肝,4艾滋,5肺结核,6其他")
+    private Integer infection;
+
+    /** 住院号 */
+    @Excel(name = "住院号")
+    private String code;
+
+    /** 姓名 */
+    @Excel(name = "姓名")
+    private String name;
+
+    /** 性别,1男2女 */
+    @Excel(name = "性别,1男2女")
+    private Integer gender;
+
+    /** 身高 */
+    @Excel(name = "身高")
+    private BigDecimal height;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Long age;
+
+    /** 体重 */
+    @Excel(name = "体重")
+    private Long weight;
+
+    /** 来源,1门诊,2急诊 */
+    @Excel(name = "来源,1门诊,2急诊")
+    private Integer source;
+
+    /** 住址 */
+    @Excel(name = "住址")
+    private String address;
+
+    /** 手机号 */
+    private String phone;
+
+    /** 亲属姓名 */
+    @Excel(name = "亲属姓名")
+    private String relationName;
+
+    /** 亲属电话 */
+    @Excel(name = "亲属电话")
+    private String relationPhone;
+
+    /** 死亡日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "死亡日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date dieTime;
+
+    /** 删除标志(0代表存在 1代表删除) */
+    @Excel(name = "删除标志", readConverterExp = "0=代表存在,1=代表删除")
+    private Integer isDelete;
+
+    /** 状态(0已签到 1正在治疗 2 已下机) */
+    @Excel(name = "状态", readConverterExp = "0=已签到,1=正在治疗,2=,已=下机")
+    private Integer status;
+
+    /** 运行状态 */
+    @Excel(name = "运行状态")
+    private String currentStatus;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+
+    public Long getdId() {
+        return dId;
+    }
+
+    public void setdId(Long dId) {
+        this.dId = dId;
+    }
+
+    public Long getpId() {
+        return pId;
+    }
+
+    public void setpId(Long pId) {
+        this.pId = pId;
+    }
+
+    public void setSignTime(Date signTime)
+    {
+        this.signTime = signTime;
+    }
+
+    public Date getSignTime() 
+    {
+        return signTime;
+    }
+    public void setTreatmentProject(String treatmentProject) 
+    {
+        this.treatmentProject = treatmentProject;
+    }
+
+    public String getTreatmentProject() 
+    {
+        return treatmentProject;
+    }
+    public void setAlarm(Integer alarm) 
+    {
+        this.alarm = alarm;
+    }
+
+    public Integer getAlarm() 
+    {
+        return alarm;
+    }
+    public void setUploadTime(Date uploadTime) 
+    {
+        this.uploadTime = uploadTime;
+    }
+
+    public Date getUploadTime() 
+    {
+        return uploadTime;
+    }
+    public void setMachineModel(String machineModel) 
+    {
+        this.machineModel = machineModel;
+    }
+
+    public String getMachineModel() 
+    {
+        return machineModel;
+    }
+    public void setFilterModel(String filterModel) 
+    {
+        this.filterModel = filterModel;
+    }
+
+    public String getFilterModel() 
+    {
+        return filterModel;
+    }
+    public void setHemodialysisModel(String hemodialysisModel) 
+    {
+        this.hemodialysisModel = hemodialysisModel;
+    }
+
+    public String getHemodialysisModel() 
+    {
+        return hemodialysisModel;
+    }
+    public void setTreatmentTime(String treatmentTime) 
+    {
+        this.treatmentTime = treatmentTime;
+    }
+
+    public String getTreatmentTime() 
+    {
+        return treatmentTime;
+    }
+    public void setTargetDehydration(String targetDehydration) 
+    {
+        this.targetDehydration = targetDehydration;
+    }
+
+    public String getTargetDehydration() 
+    {
+        return targetDehydration;
+    }
+    public void setBloodPumpRate(String bloodPumpRate) 
+    {
+        this.bloodPumpRate = bloodPumpRate;
+    }
+
+    public String getBloodPumpRate() 
+    {
+        return bloodPumpRate;
+    }
+    public void setFlow(String flow) 
+    {
+        this.flow = flow;
+    }
+
+    public String getFlow() 
+    {
+        return flow;
+    }
+    public void setK(String k) 
+    {
+        this.k = k;
+    }
+
+    public String getK() 
+    {
+        return k;
+    }
+    public void setCa(String ca) 
+    {
+        this.ca = ca;
+    }
+
+    public String getCa() 
+    {
+        return ca;
+    }
+    public void setNa(String na) 
+    {
+        this.na = na;
+    }
+
+    public String getNa() 
+    {
+        return na;
+    }
+    public void setHco3(String hco3) 
+    {
+        this.hco3 = hco3;
+    }
+
+    public String getHco3() 
+    {
+        return hco3;
+    }
+    public void setAntithrombotic(String antithrombotic) 
+    {
+        this.antithrombotic = antithrombotic;
+    }
+
+    public String getAntithrombotic() 
+    {
+        return antithrombotic;
+    }
+    public void setFirstDose(String firstDose) 
+    {
+        this.firstDose = firstDose;
+    }
+
+    public String getFirstDose() 
+    {
+        return firstDose;
+    }
+    public void setAdditionalDose(String additionalDose) 
+    {
+        this.additionalDose = additionalDose;
+    }
+
+    public String getAdditionalDose() 
+    {
+        return additionalDose;
+    }
+    public void setVascularAccess(String vascularAccess) 
+    {
+        this.vascularAccess = vascularAccess;
+    }
+
+    public String getVascularAccess() 
+    {
+        return vascularAccess;
+    }
+    public void setDoctor(String doctor) 
+    {
+        this.doctor = doctor;
+    }
+
+    public String getDoctor() 
+    {
+        return doctor;
+    }
+    public void setNurse(String nurse) 
+    {
+        this.nurse = nurse;
+    }
+
+    public String getNurse() 
+    {
+        return nurse;
+    }
+    public void setVerifyPrescription(Boolean verifyPrescription) 
+    {
+        this.verifyPrescription = verifyPrescription;
+    }
+
+    public Boolean getVerifyPrescription() 
+    {
+        return verifyPrescription;
+    }
+    public void setTargetWeight(String targetWeight) 
+    {
+        this.targetWeight = targetWeight;
+    }
+
+    public String getTargetWeight() 
+    {
+        return targetWeight;
+    }
+    public void setLastWeight(String lastWeight) 
+    {
+        this.lastWeight = lastWeight;
+    }
+
+    public String getLastWeight() 
+    {
+        return lastWeight;
+    }
+    public void setBeforeWeight(String beforeWeight) 
+    {
+        this.beforeWeight = beforeWeight;
+    }
+
+    public String getBeforeWeight() 
+    {
+        return beforeWeight;
+    }
+    public void setAddWeight(String addWeight) 
+    {
+        this.addWeight = addWeight;
+    }
+
+    public String getAddWeight() 
+    {
+        return addWeight;
+    }
+    public void setBeforeSbp(String beforeSbp) 
+    {
+        this.beforeSbp = beforeSbp;
+    }
+
+    public String getBeforeSbp() 
+    {
+        return beforeSbp;
+    }
+    public void setBeforeDbp(String beforeDbp) 
+    {
+        this.beforeDbp = beforeDbp;
+    }
+
+    public String getBeforeDbp() 
+    {
+        return beforeDbp;
+    }
+    public void setBeforeSphygmus(String beforeSphygmus) 
+    {
+        this.beforeSphygmus = beforeSphygmus;
+    }
+
+    public String getBeforeSphygmus() 
+    {
+        return beforeSphygmus;
+    }
+    public void setBeforeRr(String beforeRr) 
+    {
+        this.beforeRr = beforeRr;
+    }
+
+    public String getBeforeRr() 
+    {
+        return beforeRr;
+    }
+    public void setBeforeTemperature(String beforeTemperature) 
+    {
+        this.beforeTemperature = beforeTemperature;
+    }
+
+    public String getBeforeTemperature() 
+    {
+        return beforeTemperature;
+    }
+    public void setUrineVolume(String urineVolume) 
+    {
+        this.urineVolume = urineVolume;
+    }
+
+    public String getUrineVolume() 
+    {
+        return urineVolume;
+    }
+    public void setGainWeight(String gainWeight) 
+    {
+        this.gainWeight = gainWeight;
+    }
+
+    public String getGainWeight() 
+    {
+        return gainWeight;
+    }
+    public void setVerifyBefore(Boolean verifyBefore) 
+    {
+        this.verifyBefore = verifyBefore;
+    }
+
+    public Boolean getVerifyBefore() 
+    {
+        return verifyBefore;
+    }
+    public void setPreDialysisWeight(String preDialysisWeight) 
+    {
+        this.preDialysisWeight = preDialysisWeight;
+    }
+
+    public String getPreDialysisWeight() 
+    {
+        return preDialysisWeight;
+    }
+    public void setLossWeight(String lossWeight) 
+    {
+        this.lossWeight = lossWeight;
+    }
+
+    public String getLossWeight() 
+    {
+        return lossWeight;
+    }
+    public void setAfterTemperature(String afterTemperature) 
+    {
+        this.afterTemperature = afterTemperature;
+    }
+
+    public String getAfterTemperature() 
+    {
+        return afterTemperature;
+    }
+    public void setAfterSbp(String afterSbp) 
+    {
+        this.afterSbp = afterSbp;
+    }
+
+    public String getAfterSbp() 
+    {
+        return afterSbp;
+    }
+    public void setAfterDbp(String afterDbp) 
+    {
+        this.afterDbp = afterDbp;
+    }
+
+    public String getAfterDbp() 
+    {
+        return afterDbp;
+    }
+    public void setAfterSphygmus(String afterSphygmus) 
+    {
+        this.afterSphygmus = afterSphygmus;
+    }
+
+    public String getAfterSphygmus() 
+    {
+        return afterSphygmus;
+    }
+    public void setAfterRr(String afterRr) 
+    {
+        this.afterRr = afterRr;
+    }
+
+    public String getAfterRr() 
+    {
+        return afterRr;
+    }
+    public void setVerifyAfter(Boolean verifyAfter) 
+    {
+        this.verifyAfter = verifyAfter;
+    }
+
+    public Boolean getVerifyAfter() 
+    {
+        return verifyAfter;
+    }
+    public void setDeviceId(String deviceId) 
+    {
+        this.deviceId = deviceId;
+    }
+
+    public String getDeviceId() 
+    {
+        return deviceId;
+    }
+    public void setArea(String area) 
+    {
+        this.area = area;
+    }
+
+    public String getArea() 
+    {
+        return area;
+    }
+    public void setBed(String bed) 
+    {
+        this.bed = bed;
+    }
+
+    public String getBed() 
+    {
+        return bed;
+    }
+    public void setBrandName(String brandName) 
+    {
+        this.brandName = brandName;
+    }
+
+    public String getBrandName() 
+    {
+        return brandName;
+    }
+    public void setModel(String model) 
+    {
+        this.model = model;
+    }
+
+    public String getModel() 
+    {
+        return model;
+    }
+    public void setManufactureId(String manufactureId) 
+    {
+        this.manufactureId = manufactureId;
+    }
+
+    public String getManufactureId() 
+    {
+        return manufactureId;
+    }
+    public void setPrice(String price) 
+    {
+        this.price = price;
+    }
+
+    public String getPrice() 
+    {
+        return price;
+    }
+    public void setInfection(Integer infection) 
+    {
+        this.infection = infection;
+    }
+
+    public Integer getInfection() 
+    {
+        return infection;
+    }
+    public void setCode(String code) 
+    {
+        this.code = code;
+    }
+
+    public String getCode() 
+    {
+        return code;
+    }
+    public void setName(String name) 
+    {
+        this.name = name;
+    }
+
+    public String getName() 
+    {
+        return name;
+    }
+    public void setGender(Integer gender) 
+    {
+        this.gender = gender;
+    }
+
+    public Integer getGender() 
+    {
+        return gender;
+    }
+    public void setHeight(BigDecimal height) 
+    {
+        this.height = height;
+    }
+
+    public BigDecimal getHeight() 
+    {
+        return height;
+    }
+    public void setAge(Long age) 
+    {
+        this.age = age;
+    }
+
+    public Long getAge() 
+    {
+        return age;
+    }
+    public void setWeight(Long weight) 
+    {
+        this.weight = weight;
+    }
+
+    public Long getWeight() 
+    {
+        return weight;
+    }
+    public void setSource(Integer source) 
+    {
+        this.source = source;
+    }
+
+    public Integer getSource() 
+    {
+        return source;
+    }
+    public void setAddress(String address) 
+    {
+        this.address = address;
+    }
+
+    public String getAddress() 
+    {
+        return address;
+    }
+    public void setPhone(String phone) 
+    {
+        this.phone = phone;
+    }
+
+    public String getPhone() 
+    {
+        return phone;
+    }
+    public void setRelationName(String relationName) 
+    {
+        this.relationName = relationName;
+    }
+
+    public String getRelationName() 
+    {
+        return relationName;
+    }
+    public void setRelationPhone(String relationPhone) 
+    {
+        this.relationPhone = relationPhone;
+    }
+
+    public String getRelationPhone() 
+    {
+        return relationPhone;
+    }
+    public void setDieTime(Date dieTime) 
+    {
+        this.dieTime = dieTime;
+    }
+
+    public Date getDieTime() 
+    {
+        return dieTime;
+    }
+    public void setIsDelete(Integer isDelete) 
+    {
+        this.isDelete = isDelete;
+    }
+
+    public Integer getIsDelete() 
+    {
+        return isDelete;
+    }
+    public void setStatus(Integer status) 
+    {
+        this.status = status;
+    }
+
+    public Integer getStatus() 
+    {
+        return status;
+    }
+    public void setCurrentStatus(String currentStatus) 
+    {
+        this.currentStatus = currentStatus;
+    }
+
+    public String getCurrentStatus() 
+    {
+        return currentStatus;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("dId", getdId())
+            .append("pId", getpId())
+            .append("signTime", getSignTime())
+            .append("treatmentProject", getTreatmentProject())
+            .append("alarm", getAlarm())
+            .append("uploadTime", getUploadTime())
+            .append("machineModel", getMachineModel())
+            .append("filterModel", getFilterModel())
+            .append("hemodialysisModel", getHemodialysisModel())
+            .append("treatmentTime", getTreatmentTime())
+            .append("targetDehydration", getTargetDehydration())
+            .append("bloodPumpRate", getBloodPumpRate())
+            .append("flow", getFlow())
+            .append("k", getK())
+            .append("ca", getCa())
+            .append("na", getNa())
+            .append("hco3", getHco3())
+            .append("antithrombotic", getAntithrombotic())
+            .append("firstDose", getFirstDose())
+            .append("additionalDose", getAdditionalDose())
+            .append("vascularAccess", getVascularAccess())
+            .append("doctor", getDoctor())
+            .append("nurse", getNurse())
+            .append("verifyPrescription", getVerifyPrescription())
+            .append("targetWeight", getTargetWeight())
+            .append("lastWeight", getLastWeight())
+            .append("beforeWeight", getBeforeWeight())
+            .append("addWeight", getAddWeight())
+            .append("beforeSbp", getBeforeSbp())
+            .append("beforeDbp", getBeforeDbp())
+            .append("beforeSphygmus", getBeforeSphygmus())
+            .append("beforeRr", getBeforeRr())
+            .append("beforeTemperature", getBeforeTemperature())
+            .append("urineVolume", getUrineVolume())
+            .append("gainWeight", getGainWeight())
+            .append("verifyBefore", getVerifyBefore())
+            .append("preDialysisWeight", getPreDialysisWeight())
+            .append("lossWeight", getLossWeight())
+            .append("afterTemperature", getAfterTemperature())
+            .append("afterSbp", getAfterSbp())
+            .append("afterDbp", getAfterDbp())
+            .append("afterSphygmus", getAfterSphygmus())
+            .append("afterRr", getAfterRr())
+            .append("verifyAfter", getVerifyAfter())
+            .append("deviceId", getDeviceId())
+            .append("area", getArea())
+            .append("bed", getBed())
+            .append("brandName", getBrandName())
+            .append("model", getModel())
+            .append("manufactureId", getManufactureId())
+            .append("price", getPrice())
+            .append("infection", getInfection())
+            .append("code", getCode())
+            .append("name", getName())
+            .append("gender", getGender())
+            .append("height", getHeight())
+            .append("age", getAge())
+            .append("weight", getWeight())
+            .append("source", getSource())
+            .append("address", getAddress())
+            .append("createTime", getCreateTime())
+            .append("phone", getPhone())
+            .append("relationName", getRelationName())
+            .append("relationPhone", getRelationPhone())
+            .append("dieTime", getDieTime())
+            .append("isDelete", getIsDelete())
+            .append("status", getStatus())
+            .append("currentStatus", getCurrentStatus())
+            .toString();
+    }
+}

+ 53 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/BusDeviceRunning.java

@@ -0,0 +1,53 @@
+package com.ruoyi.system.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.io.Serializable;
+
+/**
+ * 运行状态对象 bus_device_running
+ * 
+ * @author zsl
+ * @date 2022-06-17
+ */
+public class BusDeviceRunning implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private String json;
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getId() 
+    {
+        return id;
+    }
+    public void setJson(String json) 
+    {
+        this.json = json;
+    }
+
+    public String getJson() 
+    {
+        return json;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("json", getJson())
+            .toString();
+    }
+}

+ 85 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/BloodPressure.java

@@ -0,0 +1,85 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName BloodPressure.java
+ * @Description TODO
+ * @createTime 2022/6/2116:43
+ */
+public class BloodPressure implements Serializable {
+    private String bloodPressureOpening;//血压是否在开启状态
+    private String intervalMeasurement;//血压定时测量
+    private String systolicPressure;//收缩压
+    private String diastolicPressure;//舒张压
+    private String theMap;//平均动脉压
+    private String pulse;//脉搏
+    private String inflationPressure;//充气压力
+    private String intervalTime;//间隔时间
+
+    public String getBloodPressureOpening() {
+        return bloodPressureOpening;
+    }
+
+    public void setBloodPressureOpening(String bloodPressureOpening) {
+        this.bloodPressureOpening = bloodPressureOpening;
+    }
+
+    public String getIntervalMeasurement() {
+        return intervalMeasurement;
+    }
+
+    public void setIntervalMeasurement(String intervalMeasurement) {
+        this.intervalMeasurement = intervalMeasurement;
+    }
+
+    public String getSystolicPressure() {
+        return systolicPressure;
+    }
+
+    public void setSystolicPressure(String systolicPressure) {
+        this.systolicPressure = systolicPressure;
+    }
+
+    public String getDiastolicPressure() {
+        return diastolicPressure;
+    }
+
+    public void setDiastolicPressure(String diastolicPressure) {
+        this.diastolicPressure = diastolicPressure;
+    }
+
+    public String getTheMap() {
+        return theMap;
+    }
+
+    public void setTheMap(String theMap) {
+        this.theMap = theMap;
+    }
+
+    public String getPulse() {
+        return pulse;
+    }
+
+    public void setPulse(String pulse) {
+        this.pulse = pulse;
+    }
+
+    public String getInflationPressure() {
+        return inflationPressure;
+    }
+
+    public void setInflationPressure(String inflationPressure) {
+        this.inflationPressure = inflationPressure;
+    }
+
+    public String getIntervalTime() {
+        return intervalTime;
+    }
+
+    public void setIntervalTime(String intervalTime) {
+        this.intervalTime = intervalTime;
+    }
+}

+ 35 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/Cure.java

@@ -0,0 +1,35 @@
+package com.ruoyi.system.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName Cure.java
+ * @Description TODO
+ * @createTime 2022/6/218:58
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class Cure implements Serializable {
+
+    private Long ids;
+    private Long pids;
+
+    public Long getIds() {
+        return ids;
+    }
+
+    public void setIds(Long ids) {
+        this.ids = ids;
+    }
+
+    public Long getPids() {
+        return pids;
+    }
+
+    public void setPids(Long pids) {
+        this.pids = pids;
+    }
+}

+ 94 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/CurrentValue.java

@@ -0,0 +1,94 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName CurrentValue.java
+ * @Description TODO
+ * @createTime 2022/6/2116:54
+ */
+public class CurrentValue implements Serializable {
+    public String arterialPressure;//动脉压
+    public String venousPressure;//静脉压
+    public String conductivity;//电导度
+    public String transmembranePressure;//跨膜压
+    public String temperature;//温度
+    public String bloodVolumeDone;//已经透析血量
+    public String timeRemain;//剩余透析时间
+    public String bloodVolumeReturn;//已回血量
+    public String rateBPReal;//血泵真实速度.
+
+    public String getArterialPressure() {
+        return arterialPressure;
+    }
+
+    public void setArterialPressure(String arterialPressure) {
+        this.arterialPressure = arterialPressure;
+    }
+
+    public String getVenousPressure() {
+        return venousPressure;
+    }
+
+    public void setVenousPressure(String venousPressure) {
+        this.venousPressure = venousPressure;
+    }
+
+    public String getConductivity() {
+        return conductivity;
+    }
+
+    public void setConductivity(String conductivity) {
+        this.conductivity = conductivity;
+    }
+
+    public String getTransmembranePressure() {
+        return transmembranePressure;
+    }
+
+    public void setTransmembranePressure(String transmembranePressure) {
+        this.transmembranePressure = transmembranePressure;
+    }
+
+    public String getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(String temperature) {
+        this.temperature = temperature;
+    }
+
+    public String getBloodVolumeDone() {
+        return bloodVolumeDone;
+    }
+
+    public void setBloodVolumeDone(String bloodVolumeDone) {
+        this.bloodVolumeDone = bloodVolumeDone;
+    }
+
+    public String getTimeRemain() {
+        return timeRemain;
+    }
+
+    public void setTimeRemain(String timeRemain) {
+        this.timeRemain = timeRemain;
+    }
+
+    public String getBloodVolumeReturn() {
+        return bloodVolumeReturn;
+    }
+
+    public void setBloodVolumeReturn(String bloodVolumeReturn) {
+        this.bloodVolumeReturn = bloodVolumeReturn;
+    }
+
+    public String getRateBPReal() {
+        return rateBPReal;
+    }
+
+    public void setRateBPReal(String rateBPReal) {
+        this.rateBPReal = rateBPReal;
+    }
+}

+ 18 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceBody.java

@@ -0,0 +1,18 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName DeviceBody.java
+ * @Description TODO
+ * @createTime 2022/6/2116:42
+ */
+public class DeviceBody implements Serializable {
+    private BloodPressure bloodPressure;
+    private DialysisParam dialysisParam;
+    private UfParam ufParam;
+    private CurrentValue currentValue;
+
+}

+ 52 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceHeader.java

@@ -0,0 +1,52 @@
+package com.ruoyi.system.domain.dto;
+
+import org.springframework.core.SpringVersion;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName DeviceHeader.java
+ * @Description TODO
+ * @createTime 2022/6/2116:38
+ */
+public class DeviceHeader implements Serializable {
+    private Long id;
+    private Date time;
+    private String currentStatus;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Date getTime() {
+        return time;
+    }
+
+    public void setTime(Date time) {
+        this.time = time;
+    }
+
+    public String getCurrentStatus() {
+        return currentStatus;
+    }
+
+    public void setCurrentStatus(String currentStatus) {
+        this.currentStatus = currentStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceHeader{" +
+                "id=" + id +
+                ", time=" + time +
+                ", currentStatus='" + currentStatus + '\'' +
+                '}';
+    }
+}

+ 18 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DeviceRunningDto.java

@@ -0,0 +1,18 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName DeviceRunningDto.java
+ * @Description TODO
+ * @createTime 2022/6/2116:35
+ */
+public class DeviceRunningDto implements Serializable {
+
+    private DeviceHeader header;
+
+    private DeviceBody body;
+
+}

+ 67 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/DialysisParam.java

@@ -0,0 +1,67 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName DialysisParam.java
+ * @Description TODO
+ * @createTime 2022/6/2116:47
+ */
+public class DialysisParam implements Serializable {
+    private String basicNa;//基础钠
+    private String prescriptionNa;//处方钠
+    private String prescriptionBic;//处方bic
+    private String dialysisFlow;//流量
+    private String temperature;//设置温度
+    private String naCurveID;//钠曲线
+
+    public String getBasicNa() {
+        return basicNa;
+    }
+
+    public void setBasicNa(String basicNa) {
+        this.basicNa = basicNa;
+    }
+
+    public String getPrescriptionNa() {
+        return prescriptionNa;
+    }
+
+    public void setPrescriptionNa(String prescriptionNa) {
+        this.prescriptionNa = prescriptionNa;
+    }
+
+    public String getPrescriptionBic() {
+        return prescriptionBic;
+    }
+
+    public void setPrescriptionBic(String prescriptionBic) {
+        this.prescriptionBic = prescriptionBic;
+    }
+
+    public String getDialysisFlow() {
+        return dialysisFlow;
+    }
+
+    public void setDialysisFlow(String dialysisFlow) {
+        this.dialysisFlow = dialysisFlow;
+    }
+
+    public String getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(String temperature) {
+        this.temperature = temperature;
+    }
+
+    public String getNaCurveID() {
+        return naCurveID;
+    }
+
+    public void setNaCurveID(String naCurveID) {
+        this.naCurveID = naCurveID;
+    }
+}

+ 47 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/SignDto.java

@@ -0,0 +1,47 @@
+package com.ruoyi.system.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName SignDto.java
+ * @Description TODO
+ * @createTime 2022/6/139:35
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class SignDto implements Serializable {
+
+    /**  */
+    private Long patientId;
+
+    /**  */
+    private Long DeviceId;
+
+    public Long getPatientId() {
+        return patientId;
+    }
+
+    public void setPatientId(Long patientId) {
+        this.patientId = patientId;
+    }
+
+    public Long getDeviceId() {
+        return DeviceId;
+    }
+
+    public void setDeviceId(Long deviceId) {
+        DeviceId = deviceId;
+    }
+
+    @Override
+    public String toString() {
+        return "SignDto{" +
+                "patientId=" + patientId +
+                ", DeviceId=" + DeviceId +
+                '}';
+    }
+}

+ 58 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/UfParam.java

@@ -0,0 +1,58 @@
+package com.ruoyi.system.domain.dto;
+
+import java.io.Serializable;
+
+/**
+ * @author zsl
+ * @version 1.0.0
+ * @ClassName UfParam.java
+ * @Description TODO
+ * @createTime 2022/6/2116:50
+ */
+public class UfParam implements Serializable {
+    public String ufTarget;//超滤目标
+    public String ufTime;//超滤时间
+    public String ufRate;//超滤速率
+    public String ufVolume;//已超滤总量
+    public String ufCurveID;//超滤曲线
+
+    public String getUfTarget() {
+        return ufTarget;
+    }
+
+    public void setUfTarget(String ufTarget) {
+        this.ufTarget = ufTarget;
+    }
+
+    public String getUfTime() {
+        return ufTime;
+    }
+
+    public void setUfTime(String ufTime) {
+        this.ufTime = ufTime;
+    }
+
+    public String getUfRate() {
+        return ufRate;
+    }
+
+    public void setUfRate(String ufRate) {
+        this.ufRate = ufRate;
+    }
+
+    public String getUfVolume() {
+        return ufVolume;
+    }
+
+    public void setUfVolume(String ufVolume) {
+        this.ufVolume = ufVolume;
+    }
+
+    public String getUfCurveID() {
+        return ufCurveID;
+    }
+
+    public void setUfCurveID(String ufCurveID) {
+        this.ufCurveID = ufCurveID;
+    }
+}

+ 106 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/MetaVo.java

@@ -0,0 +1,106 @@
+package com.ruoyi.system.domain.vo;
+
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 路由显示信息
+ * 
+ * @author ruoyi
+ */
+public class MetaVo
+{
+    /**
+     * 设置该路由在侧边栏和面包屑中展示的名字
+     */
+    private String title;
+
+    /**
+     * 设置该路由的图标,对应路径src/assets/icons/svg
+     */
+    private String icon;
+
+    /**
+     * 设置为true,则不会被 <keep-alive>缓存
+     */
+    private boolean noCache;
+
+    /**
+     * 内链地址(http(s)://开头)
+     */
+    private String link;
+
+    public MetaVo()
+    {
+    }
+
+    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;
+        }
+    }
+
+    public boolean isNoCache()
+    {
+        return noCache;
+    }
+
+    public void setNoCache(boolean noCache)
+    {
+        this.noCache = noCache;
+    }
+
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public String getIcon()
+    {
+        return icon;
+    }
+
+    public void setIcon(String icon)
+    {
+        this.icon = icon;
+    }
+
+    public String getLink()
+    {
+        return link;
+    }
+
+    public void setLink(String link)
+    {
+        this.link = link;
+    }
+}

+ 148 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/RouterVo.java

@@ -0,0 +1,148 @@
+package com.ruoyi.system.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.List;
+
+/**
+ * 路由配置信息
+ * 
+ * @author ruoyi
+ */
+@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<RouterVo> children;
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean getHidden()
+    {
+        return hidden;
+    }
+
+    public void setHidden(boolean hidden)
+    {
+        this.hidden = hidden;
+    }
+
+    public String getRedirect()
+    {
+        return redirect;
+    }
+
+    public void setRedirect(String redirect)
+    {
+        this.redirect = redirect;
+    }
+
+    public String getComponent()
+    {
+        return component;
+    }
+
+    public void setComponent(String component)
+    {
+        this.component = component;
+    }
+
+    public String getQuery()
+    {
+        return query;
+    }
+
+    public void setQuery(String query)
+    {
+        this.query = query;
+    }
+
+    public Boolean getAlwaysShow()
+    {
+        return alwaysShow;
+    }
+
+    public void setAlwaysShow(Boolean alwaysShow)
+    {
+        this.alwaysShow = alwaysShow;
+    }
+
+    public MetaVo getMeta()
+    {
+        return meta;
+    }
+
+    public void setMeta(MetaVo meta)
+    {
+        this.meta = meta;
+    }
+
+    public List<RouterVo> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<RouterVo> children)
+    {
+        this.children = children;
+    }
+}

+ 72 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceHistoryMapper.java

@@ -0,0 +1,72 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDeviceHistory;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 透析管理Mapper接口
+ * 
+ * @author zsl
+ * @date 2022-06-10
+ */
+public interface BusDeviceHistoryMapper 
+{
+    /**
+     * 查询透析管理
+     * 
+     * @param id 透析管理主键
+     * @return 透析管理
+     */
+    public BusDeviceHistory selectBusDeviceHistoryById(Long id);
+
+    /**
+     * 查询透析管理列表
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 透析管理集合
+     */
+    public List<BusDeviceHistory> selectBusDeviceHistoryList(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 新增透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    public int insertBusDeviceHistory(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 修改透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    public int updateBusDeviceHistory(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 修改透析状态
+     *
+     * @param ids 需要修改的数据主键集合
+     * @param status  需要修改的状态
+     * @return 结果
+     */
+    public int updateStatusByIds(@Param("ids") Long[] ids ,@Param("status") Integer status);
+
+
+    /**
+     * 删除透析管理
+     * 
+     * @param id 透析管理主键
+     * @return 结果
+     */
+    public int deleteBusDeviceHistoryById(Long id);
+
+    /**
+     * 批量删除透析管理
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceHistoryByIds(Long[] ids);
+}

+ 71 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceMapper.java

@@ -0,0 +1,71 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDevice;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 设备Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2022-05-31
+ */
+public interface BusDeviceMapper 
+{
+    /**
+     * 查询设备
+     * 
+     * @param id 设备主键
+     * @return 设备
+     */
+    public BusDevice selectBusDeviceById(Long id);
+
+    /**
+     * 查询设备列表
+     * 
+     * @param busDevice 设备
+     * @return 设备集合
+     */
+    public List<BusDevice> selectBusDeviceList(BusDevice busDevice);
+
+    /**
+     * 新增设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    public int insertBusDevice(BusDevice busDevice);
+
+    /**
+     * 修改设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    public int updateBusDevice(BusDevice busDevice);
+
+    /**
+     * 删除设备
+     * 
+     * @param id 设备主键
+     * @return 结果
+     */
+    public int deleteBusDeviceById(Long id);
+
+    /**
+     * 批量删除设备
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceByIds(Long[] ids);
+
+    /**
+     * 修改设备状态
+     *
+     * @param ids 需要修改的数据主键集合
+     * @param status  需要修改的状态
+     * @return 结果
+     */
+    public int updateStatusByIds(@Param("ids") Long[] ids , @Param("status") Integer status);
+}

+ 61 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/BusDeviceRunningMapper.java

@@ -0,0 +1,61 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDeviceRunning;
+
+/**
+ * 运行状态Mapper接口
+ * 
+ * @author zsl
+ * @date 2022-06-17
+ */
+public interface BusDeviceRunningMapper 
+{
+    /**
+     * 查询运行状态
+     * 
+     * @param id 运行状态主键
+     * @return 运行状态
+     */
+    public BusDeviceRunning selectBusDeviceRunningById(Long id);
+
+    /**
+     * 查询运行状态列表
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 运行状态集合
+     */
+    public List<BusDeviceRunning> selectBusDeviceRunningList(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 新增运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    public int insertBusDeviceRunning(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 修改运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    public int updateBusDeviceRunning(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 删除运行状态
+     * 
+     * @param id 运行状态主键
+     * @return 结果
+     */
+    public int deleteBusDeviceRunningById(Long id);
+
+    /**
+     * 批量删除运行状态
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceRunningByIds(Long[] ids);
+}

+ 78 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceHistoryService.java

@@ -0,0 +1,78 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDeviceHistory;
+import com.ruoyi.system.domain.dto.SignDto;
+
+/**
+ * 透析管理Service接口
+ * 
+ * @author zsl
+ * @date 2022-06-10
+ */
+public interface IBusDeviceHistoryService 
+{
+    /**
+     * 查询透析管理
+     * 
+     * @param id 透析管理主键
+     * @return 透析管理
+     */
+    public BusDeviceHistory selectBusDeviceHistoryById(Long id);
+
+    /**
+     * 查询透析管理列表
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 透析管理集合
+     */
+    public List<BusDeviceHistory> selectBusDeviceHistoryList(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 新增透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    public int insertBusDeviceHistory(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 修改透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    public int updateBusDeviceHistory(BusDeviceHistory busDeviceHistory);
+
+    /**
+     * 批量删除透析管理
+     * 
+     * @param ids 需要删除的透析管理主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceHistoryByIds(Long[] ids);
+
+    /**
+     * 删除透析管理信息
+     * 
+     * @param id 透析管理主键
+     * @return 结果
+     */
+    public int deleteBusDeviceHistoryById(Long id);
+
+    /**
+     * 签到管理
+     *
+     * @param sign 患者ID
+     * @return 结果
+     */
+    public int sign( SignDto sign);
+
+    /**
+     * 治疗中
+     *
+     * @param ids 患者ID
+     * @return 结果
+     */
+    public  int cure(Long[] ids , Long[] pids);
+}

+ 61 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceRunningService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDeviceRunning;
+
+/**
+ * 运行状态Service接口
+ * 
+ * @author zsl
+ * @date 2022-06-17
+ */
+public interface IBusDeviceRunningService 
+{
+    /**
+     * 查询运行状态
+     * 
+     * @param id 运行状态主键
+     * @return 运行状态
+     */
+    public BusDeviceRunning selectBusDeviceRunningById(Long id);
+
+    /**
+     * 查询运行状态列表
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 运行状态集合
+     */
+    public List<BusDeviceRunning> selectBusDeviceRunningList(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 新增运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    public int insertBusDeviceRunning(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 修改运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    public int updateBusDeviceRunning(BusDeviceRunning busDeviceRunning);
+
+    /**
+     * 批量删除运行状态
+     * 
+     * @param ids 需要删除的运行状态主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceRunningByIds(Long[] ids);
+
+    /**
+     * 删除运行状态信息
+     * 
+     * @param id 运行状态主键
+     * @return 结果
+     */
+    public int deleteBusDeviceRunningById(Long id);
+}

+ 61 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/IBusDeviceService.java

@@ -0,0 +1,61 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.BusDevice;
+
+/**
+ * 设备Service接口
+ * 
+ * @author ruoyi
+ * @date 2022-05-31
+ */
+public interface IBusDeviceService 
+{
+    /**
+     * 查询设备
+     * 
+     * @param id 设备主键
+     * @return 设备
+     */
+    public BusDevice selectBusDeviceById(Long id);
+
+    /**
+     * 查询设备列表
+     * 
+     * @param busDevice 设备
+     * @return 设备集合
+     */
+    public List<BusDevice> selectBusDeviceList(BusDevice busDevice);
+
+    /**
+     * 新增设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    public int insertBusDevice(BusDevice busDevice);
+
+    /**
+     * 修改设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    public int updateBusDevice(BusDevice busDevice);
+
+    /**
+     * 批量删除设备
+     * 
+     * @param ids 需要删除的设备主键集合
+     * @return 结果
+     */
+    public int deleteBusDeviceByIds(Long[] ids);
+
+    /**
+     * 删除设备信息
+     * 
+     * @param id 设备主键
+     * @return 结果
+     */
+    public int deleteBusDeviceById(Long id);
+}

+ 190 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceHistoryServiceImpl.java

@@ -0,0 +1,190 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.system.domain.BusDevice;
+import com.ruoyi.system.domain.BusPatient;
+import com.ruoyi.system.domain.dto.SignDto;
+import com.ruoyi.system.mapper.BusDeviceMapper;
+import com.ruoyi.system.mapper.BusPatientMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.BusDeviceHistoryMapper;
+import com.ruoyi.system.domain.BusDeviceHistory;
+import com.ruoyi.system.service.IBusDeviceHistoryService;
+
+/**
+ * 透析管理Service业务层处理
+ * 
+ * @author zsl
+ * @date 2022-06-10
+ */
+@Service
+public class BusDeviceHistoryServiceImpl implements IBusDeviceHistoryService 
+{
+    @Autowired
+    private BusDeviceHistoryMapper busDeviceHistoryMapper;
+
+    @Autowired
+    private BusDeviceMapper busDeviceMapper;
+
+    @Autowired
+    private BusPatientMapper busPatientMapper;
+
+
+    /**
+     * 查询透析管理
+     * 
+     * @param id 透析管理主键
+     * @return 透析管理
+     */
+    @Override
+    public BusDeviceHistory selectBusDeviceHistoryById(Long id)
+    {
+        return busDeviceHistoryMapper.selectBusDeviceHistoryById(id);
+    }
+
+    /**
+     * 查询透析管理列表
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 透析管理
+     */
+    @Override
+    public List<BusDeviceHistory> selectBusDeviceHistoryList(BusDeviceHistory busDeviceHistory)
+    {
+        return busDeviceHistoryMapper.selectBusDeviceHistoryList(busDeviceHistory);
+    }
+
+    /**
+     * 新增透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    @Override
+    public int insertBusDeviceHistory(BusDeviceHistory busDeviceHistory)
+    {
+        busDeviceHistory.setCreateTime(DateUtils.getNowDate());
+        return busDeviceHistoryMapper.insertBusDeviceHistory(busDeviceHistory);
+    }
+
+    /**
+     * 修改透析管理
+     * 
+     * @param busDeviceHistory 透析管理
+     * @return 结果
+     */
+    @Override
+    public int updateBusDeviceHistory(BusDeviceHistory busDeviceHistory)
+    {
+        if(busDeviceHistory.getdId()!=null){
+            BusDevice busDevice = new BusDevice();
+            busDevice.setId(busDeviceHistory.getdId());
+            busDevice.setStatus(busDeviceHistory.getStatus());
+            busDeviceMapper.updateBusDevice(busDevice);
+        }
+        if (busDeviceHistory.getpId()!=null){
+            BusPatient busPatient = new BusPatient();
+            busPatient.setId(busDeviceHistory.getpId());
+            busPatient.setStatus(busDeviceHistory.getStatus());
+            busPatientMapper.updateBusPatient(busPatient);
+        }
+        return busDeviceHistoryMapper.updateBusDeviceHistory(busDeviceHistory);
+    }
+
+    /**
+     * 批量删除透析管理
+     * 
+     * @param ids 需要删除的透析管理主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceHistoryByIds(Long[] ids)
+    {
+        return busDeviceHistoryMapper.deleteBusDeviceHistoryByIds(ids);
+    }
+
+    /**
+     * 删除透析管理信息
+     * 
+     * @param id 透析管理主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceHistoryById(Long id)
+    {
+        return busDeviceHistoryMapper.deleteBusDeviceHistoryById(id);
+    }
+    /**
+     * 签到
+     *
+     * @param sign 透析管理主键
+     * @return 结果
+     */
+    @Override
+    public int sign( SignDto sign) {
+        Long patinetId = sign.getPatientId();
+        Long deviceId = sign.getDeviceId();
+
+        BusDeviceHistory busDeviceHistory = new BusDeviceHistory();
+        BusDevice busDevice = busDeviceMapper.selectBusDeviceById(deviceId);
+        BusPatient busPatient = busPatientMapper.selectBusPatientById(patinetId);
+        //修改设备状态
+        busDevice.setStatus(1);
+        busDeviceMapper.updateBusDevice(busDevice);
+        //修改患者状态
+        busPatient.setStatus(0);
+        busPatientMapper.updateBusPatient(busPatient);
+
+
+        //时间处理
+        busDeviceHistory.setSignTime(DateUtils.getNowDate());
+        busDeviceHistory.setUpdateTime(DateUtils.getNowDate());
+
+
+        busDeviceHistory.setdId(deviceId);
+        busDeviceHistory.setpId(patinetId);
+
+        busDeviceHistory.setDeviceId(busDevice.getDeviceId());
+        busDeviceHistory.setArea(busDevice.getArea());
+        busDeviceHistory.setBed(busDevice.getBed());
+        busDeviceHistory.setBrandName(busDevice.getBrandName());
+        busDeviceHistory.setModel(busDevice.getModel());
+        busDeviceHistory.setManufactureId(busDevice.getManufactureId());
+        busDeviceHistory.setPrice(busDevice.getPrice());
+        busDeviceHistory.setInfection(busDevice.getInfection());
+        busDeviceHistory.setCode(busPatient.getCode());
+        busDeviceHistory.setName(busPatient.getName());
+        busDeviceHistory.setGender(busPatient.getGender());
+        busDeviceHistory.setHeight(busPatient.getHeight());
+        busDeviceHistory.setAge(busPatient.getAge());
+        busDeviceHistory.setWeight(busPatient.getWeight());
+        busDeviceHistory.setSource(busPatient.getSource());
+        busDeviceHistory.setAddress(busPatient.getAddress());
+        busDeviceHistory.setCreateTime(busPatient.getCreateTime());
+        busDeviceHistory.setPhone(busPatient.getPhone());
+        busDeviceHistory.setRelationName(busPatient.getRelationName());
+        busDeviceHistory.setRelationPhone(busPatient.getRelationPhone());
+
+        busDeviceHistory.setVerifyPrescription(false);
+        busDeviceHistory.setVerifyBefore(false);
+        busDeviceHistory.setVerifyAfter(false);
+        busDeviceHistory.setStatus(0);
+
+        return busDeviceHistoryMapper.insertBusDeviceHistory(busDeviceHistory);
+    }
+    /**
+     * 治疗中
+     *
+     * @param ids 透析管理主键
+     * @return 结果
+     */
+    @Override
+    public int cure(Long[] ids ,Long[] pids) {
+
+        busPatientMapper.updateStatusByIds(pids,1);
+
+        return busDeviceHistoryMapper.updateStatusByIds(ids,1);
+    }
+}

+ 93 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceRunningServiceImpl.java

@@ -0,0 +1,93 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.BusDeviceRunningMapper;
+import com.ruoyi.system.domain.BusDeviceRunning;
+import com.ruoyi.system.service.IBusDeviceRunningService;
+
+/**
+ * 运行状态Service业务层处理
+ * 
+ * @author zsl
+ * @date 2022-06-17
+ */
+@Service
+public class BusDeviceRunningServiceImpl implements IBusDeviceRunningService 
+{
+    @Autowired
+    private BusDeviceRunningMapper busDeviceRunningMapper;
+
+    /**
+     * 查询运行状态
+     * 
+     * @param id 运行状态主键
+     * @return 运行状态
+     */
+    @Override
+    public BusDeviceRunning selectBusDeviceRunningById(Long id)
+    {
+        return busDeviceRunningMapper.selectBusDeviceRunningById(id);
+    }
+
+    /**
+     * 查询运行状态列表
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 运行状态
+     */
+    @Override
+    public List<BusDeviceRunning> selectBusDeviceRunningList(BusDeviceRunning busDeviceRunning)
+    {
+        return busDeviceRunningMapper.selectBusDeviceRunningList(busDeviceRunning);
+    }
+
+    /**
+     * 新增运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    @Override
+    public int insertBusDeviceRunning(BusDeviceRunning busDeviceRunning)
+    {
+        return busDeviceRunningMapper.insertBusDeviceRunning(busDeviceRunning);
+    }
+
+    /**
+     * 修改运行状态
+     * 
+     * @param busDeviceRunning 运行状态
+     * @return 结果
+     */
+    @Override
+    public int updateBusDeviceRunning(BusDeviceRunning busDeviceRunning)
+    {
+        return busDeviceRunningMapper.updateBusDeviceRunning(busDeviceRunning);
+    }
+
+    /**
+     * 批量删除运行状态
+     * 
+     * @param ids 需要删除的运行状态主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceRunningByIds(Long[] ids)
+    {
+        return busDeviceRunningMapper.deleteBusDeviceRunningByIds(ids);
+    }
+
+    /**
+     * 删除运行状态信息
+     * 
+     * @param id 运行状态主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceRunningById(Long id)
+    {
+        return busDeviceRunningMapper.deleteBusDeviceRunningById(id);
+    }
+}

+ 96 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/BusDeviceServiceImpl.java

@@ -0,0 +1,96 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.BusDeviceMapper;
+import com.ruoyi.system.domain.BusDevice;
+import com.ruoyi.system.service.IBusDeviceService;
+
+/**
+ * 设备Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2022-05-31
+ */
+@Service
+public class BusDeviceServiceImpl implements IBusDeviceService 
+{
+    @Autowired
+    private BusDeviceMapper busDeviceMapper;
+
+    /**
+     * 查询设备
+     * 
+     * @param id 设备主键
+     * @return 设备
+     */
+    @Override
+    public BusDevice selectBusDeviceById(Long id)
+    {
+        return busDeviceMapper.selectBusDeviceById(id);
+    }
+
+    /**
+     * 查询设备列表
+     * 
+     * @param busDevice 设备
+     * @return 设备
+     */
+    @Override
+    public List<BusDevice> selectBusDeviceList(BusDevice busDevice)
+    {
+        return busDeviceMapper.selectBusDeviceList(busDevice);
+    }
+
+    /**
+     * 新增设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    @Override
+    public int insertBusDevice(BusDevice busDevice)
+    {
+        busDevice.setCreateTime(DateUtils.getNowDate());
+        return busDeviceMapper.insertBusDevice(busDevice);
+    }
+
+    /**
+     * 修改设备
+     * 
+     * @param busDevice 设备
+     * @return 结果
+     */
+    @Override
+    public int updateBusDevice(BusDevice busDevice)
+    {
+        busDevice.setUpdateTime(DateUtils.getNowDate());
+        return busDeviceMapper.updateBusDevice(busDevice);
+    }
+
+    /**
+     * 批量删除设备
+     * 
+     * @param ids 需要删除的设备主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceByIds(Long[] ids)
+    {
+        return busDeviceMapper.deleteBusDeviceByIds(ids);
+    }
+
+    /**
+     * 删除设备信息
+     * 
+     * @param id 设备主键
+     * @return 结果
+     */
+    @Override
+    public int deleteBusDeviceById(Long id)
+    {
+        return busDeviceMapper.deleteBusDeviceById(id);
+    }
+}

+ 401 - 0
ruoyi-system/src/main/resources/mapper/system/BusDeviceHistoryMapper.xml

@@ -0,0 +1,401 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.BusDeviceHistoryMapper">
+    
+    <resultMap type="BusDeviceHistory" id="BusDeviceHistoryResult">
+        <result property="id"    column="id"    />
+        <result property="dId"    column="d_id"    />
+        <result property="pId"    column="p_id"    />
+        <result property="signTime"    column="sign_time"    />
+        <result property="treatmentProject"    column="treatment_project"    />
+        <result property="alarm"    column="alarm"    />
+        <result property="uploadTime"    column="upload_time"    />
+        <result property="machineModel"    column="machine_model"    />
+        <result property="filterModel"    column="filter_model"    />
+        <result property="hemodialysisModel"    column="hemodialysis_model"    />
+        <result property="treatmentTime"    column="treatment_time"    />
+        <result property="targetDehydration"    column="target_dehydration"    />
+        <result property="bloodPumpRate"    column="blood_pump_rate"    />
+        <result property="flow"    column="flow"    />
+        <result property="k"    column="k"    />
+        <result property="ca"    column="ca"    />
+        <result property="na"    column="na"    />
+        <result property="hco3"    column="hco3"    />
+        <result property="antithrombotic"    column="antithrombotic"    />
+        <result property="firstDose"    column="first_dose"    />
+        <result property="additionalDose"    column="additional_dose"    />
+        <result property="vascularAccess"    column="vascular_access"    />
+        <result property="doctor"    column="doctor"    />
+        <result property="nurse"    column="nurse"    />
+        <result property="verifyPrescription"    column="verify_prescription"    />
+        <result property="targetWeight"    column="target_weight"    />
+        <result property="lastWeight"    column="last_weight"    />
+        <result property="beforeWeight"    column="before_weight"    />
+        <result property="addWeight"    column="add_weight"    />
+        <result property="beforeSbp"    column="before_sbp"    />
+        <result property="beforeDbp"    column="before_dbp"    />
+        <result property="beforeSphygmus"    column="before_sphygmus"    />
+        <result property="beforeRr"    column="before_rr"    />
+        <result property="beforeTemperature"    column="before_temperature"    />
+        <result property="urineVolume"    column="urine_volume"    />
+        <result property="gainWeight"    column="gain_weight"    />
+        <result property="verifyBefore"    column="verify_before"    />
+        <result property="preDialysisWeight"    column="pre_dialysis_weight"    />
+        <result property="lossWeight"    column="loss_weight"    />
+        <result property="afterTemperature"    column="after_temperature"    />
+        <result property="afterSbp"    column="after_sbp"    />
+        <result property="afterDbp"    column="after_dbp"    />
+        <result property="afterSphygmus"    column="after_sphygmus"    />
+        <result property="afterRr"    column="after_rr"    />
+        <result property="verifyAfter"    column="verify_after"    />
+        <result property="deviceId"    column="device_id"    />
+        <result property="area"    column="area"    />
+        <result property="bed"    column="bed"    />
+        <result property="brandName"    column="brand_name"    />
+        <result property="model"    column="model"    />
+        <result property="manufactureId"    column="manufacture_id"    />
+        <result property="price"    column="price"    />
+        <result property="infection"    column="infection"    />
+        <result property="code"    column="code"    />
+        <result property="name"    column="name"    />
+        <result property="gender"    column="gender"    />
+        <result property="height"    column="height"    />
+        <result property="age"    column="age"    />
+        <result property="weight"    column="weight"    />
+        <result property="source"    column="source"    />
+        <result property="address"    column="address"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="phone"    column="phone"    />
+        <result property="relationName"    column="relation_name"    />
+        <result property="relationPhone"    column="relation_phone"    />
+        <result property="dieTime"    column="die_time"    />
+        <result property="isDelete"    column="is_delete"    />
+        <result property="status"    column="status"    />
+        <result property="currentStatus"    column="current_Status"    />
+    </resultMap>
+
+    <sql id="selectBusDeviceHistoryVo">
+        select id, d_id, p_id, sign_time, treatment_project, alarm, upload_time, machine_model, filter_model, hemodialysis_model, treatment_time, target_dehydration, blood_pump_rate, flow, k, ca, na, hco3, antithrombotic, first_dose, additional_dose, vascular_access, doctor, nurse, verify_prescription, target_weight, last_weight, before_weight, add_weight, before_sbp, before_dbp, before_sphygmus, before_rr, before_temperature, urine_volume, gain_weight, verify_before, pre_dialysis_weight, loss_weight, after_temperature, after_sbp, after_dbp, after_sphygmus, after_rr, verify_after, device_id, area, bed, brand_name, model, manufacture_id, price, infection, code, name, gender, height, age, weight, source, address, create_time, phone, relation_name, relation_phone, die_time, is_delete, status, current_Status from bus_device_history
+    </sql>
+
+    <select id="selectBusDeviceHistoryList" parameterType="BusDeviceHistory" resultMap="BusDeviceHistoryResult">
+        <include refid="selectBusDeviceHistoryVo"/>
+        <where>  
+            <if test="dId != null "> and d_id = #{dId}</if>
+            <if test="pId != null "> and p_id = #{pId}</if>
+            <if test="params.beginSignTime != null and params.beginSignTime != '' and params.endSignTime != null and params.endSignTime != ''"> and sign_time between #{params.beginSignTime} and #{params.endSignTime}</if>
+            <if test="treatmentProject != null  and treatmentProject != ''"> and treatment_project = #{treatmentProject}</if>
+            <if test="alarm != null "> and alarm = #{alarm}</if>
+            <if test="uploadTime != null "> and upload_time = #{uploadTime}</if>
+            <if test="machineModel != null  and machineModel != ''"> and machine_model = #{machineModel}</if>
+            <if test="filterModel != null  and filterModel != ''"> and filter_model = #{filterModel}</if>
+            <if test="hemodialysisModel != null  and hemodialysisModel != ''"> and hemodialysis_model = #{hemodialysisModel}</if>
+            <if test="treatmentTime != null  and treatmentTime != ''"> and treatment_time = #{treatmentTime}</if>
+            <if test="targetDehydration != null  and targetDehydration != ''"> and target_dehydration = #{targetDehydration}</if>
+            <if test="bloodPumpRate != null  and bloodPumpRate != ''"> and blood_pump_rate = #{bloodPumpRate}</if>
+            <if test="flow != null  and flow != ''"> and flow = #{flow}</if>
+            <if test="k != null  and k != ''"> and k = #{k}</if>
+            <if test="ca != null  and ca != ''"> and ca = #{ca}</if>
+            <if test="na != null  and na != ''"> and na = #{na}</if>
+            <if test="hco3 != null  and hco3 != ''"> and hco3 = #{hco3}</if>
+            <if test="antithrombotic != null  and antithrombotic != ''"> and antithrombotic = #{antithrombotic}</if>
+            <if test="firstDose != null  and firstDose != ''"> and first_dose = #{firstDose}</if>
+            <if test="additionalDose != null  and additionalDose != ''"> and additional_dose = #{additionalDose}</if>
+            <if test="vascularAccess != null  and vascularAccess != ''"> and vascular_access = #{vascularAccess}</if>
+            <if test="doctor != null  and doctor != ''"> and doctor = #{doctor}</if>
+            <if test="nurse != null  and nurse != ''"> and nurse = #{nurse}</if>
+            <if test="verifyPrescription != null "> and verify_prescription = #{verifyPrescription}</if>
+            <if test="targetWeight != null  and targetWeight != ''"> and target_weight = #{targetWeight}</if>
+            <if test="lastWeight != null  and lastWeight != ''"> and last_weight = #{lastWeight}</if>
+            <if test="beforeWeight != null  and beforeWeight != ''"> and before_weight = #{beforeWeight}</if>
+            <if test="addWeight != null  and addWeight != ''"> and add_weight = #{addWeight}</if>
+            <if test="beforeSbp != null  and beforeSbp != ''"> and before_sbp = #{beforeSbp}</if>
+            <if test="beforeDbp != null  and beforeDbp != ''"> and before_dbp = #{beforeDbp}</if>
+            <if test="beforeSphygmus != null  and beforeSphygmus != ''"> and before_sphygmus = #{beforeSphygmus}</if>
+            <if test="beforeRr != null  and beforeRr != ''"> and before_rr = #{beforeRr}</if>
+            <if test="beforeTemperature != null  and beforeTemperature != ''"> and before_temperature = #{beforeTemperature}</if>
+            <if test="urineVolume != null  and urineVolume != ''"> and urine_volume = #{urineVolume}</if>
+            <if test="gainWeight != null  and gainWeight != ''"> and gain_weight = #{gainWeight}</if>
+            <if test="verifyBefore != null "> and verify_before = #{verifyBefore}</if>
+            <if test="preDialysisWeight != null  and preDialysisWeight != ''"> and pre_dialysis_weight = #{preDialysisWeight}</if>
+            <if test="lossWeight != null  and lossWeight != ''"> and loss_weight = #{lossWeight}</if>
+            <if test="afterTemperature != null  and afterTemperature != ''"> and after_temperature = #{afterTemperature}</if>
+            <if test="afterSbp != null  and afterSbp != ''"> and after_sbp = #{afterSbp}</if>
+            <if test="afterDbp != null  and afterDbp != ''"> and after_dbp = #{afterDbp}</if>
+            <if test="afterSphygmus != null  and afterSphygmus != ''"> and after_sphygmus = #{afterSphygmus}</if>
+            <if test="afterRr != null  and afterRr != ''"> and after_rr = #{afterRr}</if>
+            <if test="verifyAfter != null "> and verify_after = #{verifyAfter}</if>
+            <if test="deviceId != null  and deviceId != ''"> and device_id = #{deviceId}</if>
+            <if test="area != null  and area != ''"> and area = #{area}</if>
+            <if test="bed != null  and bed != ''"> and bed = #{bed}</if>
+            <if test="brandName != null  and brandName != ''"> and brand_name like concat('%', #{brandName}, '%')</if>
+            <if test="model != null  and model != ''"> and model = #{model}</if>
+            <if test="manufactureId != null  and manufactureId != ''"> and manufacture_id = #{manufactureId}</if>
+            <if test="price != null  and price != ''"> and price = #{price}</if>
+            <if test="infection != null "> and infection = #{infection}</if>
+            <if test="code != null  and code != ''"> and code like concat('%', #{code}, '%')</if>
+            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
+            <if test="gender != null "> and gender = #{gender}</if>
+            <if test="height != null "> and height = #{height}</if>
+            <if test="age != null "> and age = #{age}</if>
+            <if test="weight != null "> and weight = #{weight}</if>
+            <if test="source != null "> and source = #{source}</if>
+            <if test="address != null  and address != ''"> and address = #{address}</if>
+            <if test="relationName != null  and relationName != ''"> and relation_name like concat('%', #{relationName}, '%')</if>
+            <if test="relationPhone != null  and relationPhone != ''"> and relation_phone = #{relationPhone}</if>
+            <if test="dieTime != null "> and die_time = #{dieTime}</if>
+            <if test="isDelete != null "> and is_delete = #{isDelete}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="currentStatus != null  and currentStatus != ''"> and current_Status = #{currentStatus}</if>
+        </where>
+    </select>
+    
+    <select id="selectBusDeviceHistoryById" parameterType="Long" resultMap="BusDeviceHistoryResult">
+        <include refid="selectBusDeviceHistoryVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertBusDeviceHistory" parameterType="BusDeviceHistory" useGeneratedKeys="true" keyProperty="id">
+        insert into bus_device_history
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="dId != null">d_id,</if>
+            <if test="pId != null">p_id,</if>
+            <if test="signTime != null">sign_time,</if>
+            <if test="treatmentProject != null">treatment_project,</if>
+            <if test="alarm != null">alarm,</if>
+            <if test="uploadTime != null">upload_time,</if>
+            <if test="machineModel != null">machine_model,</if>
+            <if test="filterModel != null">filter_model,</if>
+            <if test="hemodialysisModel != null">hemodialysis_model,</if>
+            <if test="treatmentTime != null">treatment_time,</if>
+            <if test="targetDehydration != null">target_dehydration,</if>
+            <if test="bloodPumpRate != null">blood_pump_rate,</if>
+            <if test="flow != null">flow,</if>
+            <if test="k != null">k,</if>
+            <if test="ca != null">ca,</if>
+            <if test="na != null">na,</if>
+            <if test="hco3 != null">hco3,</if>
+            <if test="antithrombotic != null">antithrombotic,</if>
+            <if test="firstDose != null">first_dose,</if>
+            <if test="additionalDose != null">additional_dose,</if>
+            <if test="vascularAccess != null">vascular_access,</if>
+            <if test="doctor != null">doctor,</if>
+            <if test="nurse != null">nurse,</if>
+            <if test="verifyPrescription != null">verify_prescription,</if>
+            <if test="targetWeight != null">target_weight,</if>
+            <if test="lastWeight != null">last_weight,</if>
+            <if test="beforeWeight != null">before_weight,</if>
+            <if test="addWeight != null">add_weight,</if>
+            <if test="beforeSbp != null">before_sbp,</if>
+            <if test="beforeDbp != null">before_dbp,</if>
+            <if test="beforeSphygmus != null">before_sphygmus,</if>
+            <if test="beforeRr != null">before_rr,</if>
+            <if test="beforeTemperature != null">before_temperature,</if>
+            <if test="urineVolume != null">urine_volume,</if>
+            <if test="gainWeight != null">gain_weight,</if>
+            <if test="verifyBefore != null">verify_before,</if>
+            <if test="preDialysisWeight != null">pre_dialysis_weight,</if>
+            <if test="lossWeight != null">loss_weight,</if>
+            <if test="afterTemperature != null">after_temperature,</if>
+            <if test="afterSbp != null">after_sbp,</if>
+            <if test="afterDbp != null">after_dbp,</if>
+            <if test="afterSphygmus != null">after_sphygmus,</if>
+            <if test="afterRr != null">after_rr,</if>
+            <if test="verifyAfter != null">verify_after,</if>
+            <if test="deviceId != null">device_id,</if>
+            <if test="area != null">area,</if>
+            <if test="bed != null">bed,</if>
+            <if test="brandName != null">brand_name,</if>
+            <if test="model != null">model,</if>
+            <if test="manufactureId != null">manufacture_id,</if>
+            <if test="price != null">price,</if>
+            <if test="infection != null">infection,</if>
+            <if test="code != null">code,</if>
+            <if test="name != null">name,</if>
+            <if test="gender != null">gender,</if>
+            <if test="height != null">height,</if>
+            <if test="age != null">age,</if>
+            <if test="weight != null">weight,</if>
+            <if test="source != null">source,</if>
+            <if test="address != null">address,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="phone != null">phone,</if>
+            <if test="relationName != null">relation_name,</if>
+            <if test="relationPhone != null">relation_phone,</if>
+            <if test="dieTime != null">die_time,</if>
+            <if test="isDelete != null">is_delete,</if>
+            <if test="status != null">status,</if>
+            <if test="currentStatus != null">current_Status,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="dId != null">#{dId},</if>
+            <if test="pId != null">#{pId},</if>
+            <if test="signTime != null">#{signTime},</if>
+            <if test="treatmentProject != null">#{treatmentProject},</if>
+            <if test="alarm != null">#{alarm},</if>
+            <if test="uploadTime != null">#{uploadTime},</if>
+            <if test="machineModel != null">#{machineModel},</if>
+            <if test="filterModel != null">#{filterModel},</if>
+            <if test="hemodialysisModel != null">#{hemodialysisModel},</if>
+            <if test="treatmentTime != null">#{treatmentTime},</if>
+            <if test="targetDehydration != null">#{targetDehydration},</if>
+            <if test="bloodPumpRate != null">#{bloodPumpRate},</if>
+            <if test="flow != null">#{flow},</if>
+            <if test="k != null">#{k},</if>
+            <if test="ca != null">#{ca},</if>
+            <if test="na != null">#{na},</if>
+            <if test="hco3 != null">#{hco3},</if>
+            <if test="antithrombotic != null">#{antithrombotic},</if>
+            <if test="firstDose != null">#{firstDose},</if>
+            <if test="additionalDose != null">#{additionalDose},</if>
+            <if test="vascularAccess != null">#{vascularAccess},</if>
+            <if test="doctor != null">#{doctor},</if>
+            <if test="nurse != null">#{nurse},</if>
+            <if test="verifyPrescription != null">#{verifyPrescription},</if>
+            <if test="targetWeight != null">#{targetWeight},</if>
+            <if test="lastWeight != null">#{lastWeight},</if>
+            <if test="beforeWeight != null">#{beforeWeight},</if>
+            <if test="addWeight != null">#{addWeight},</if>
+            <if test="beforeSbp != null">#{beforeSbp},</if>
+            <if test="beforeDbp != null">#{beforeDbp},</if>
+            <if test="beforeSphygmus != null">#{beforeSphygmus},</if>
+            <if test="beforeRr != null">#{beforeRr},</if>
+            <if test="beforeTemperature != null">#{beforeTemperature},</if>
+            <if test="urineVolume != null">#{urineVolume},</if>
+            <if test="gainWeight != null">#{gainWeight},</if>
+            <if test="verifyBefore != null">#{verifyBefore},</if>
+            <if test="preDialysisWeight != null">#{preDialysisWeight},</if>
+            <if test="lossWeight != null">#{lossWeight},</if>
+            <if test="afterTemperature != null">#{afterTemperature},</if>
+            <if test="afterSbp != null">#{afterSbp},</if>
+            <if test="afterDbp != null">#{afterDbp},</if>
+            <if test="afterSphygmus != null">#{afterSphygmus},</if>
+            <if test="afterRr != null">#{afterRr},</if>
+            <if test="verifyAfter != null">#{verifyAfter},</if>
+            <if test="deviceId != null">#{deviceId},</if>
+            <if test="area != null">#{area},</if>
+            <if test="bed != null">#{bed},</if>
+            <if test="brandName != null">#{brandName},</if>
+            <if test="model != null">#{model},</if>
+            <if test="manufactureId != null">#{manufactureId},</if>
+            <if test="price != null">#{price},</if>
+            <if test="infection != null">#{infection},</if>
+            <if test="code != null">#{code},</if>
+            <if test="name != null">#{name},</if>
+            <if test="gender != null">#{gender},</if>
+            <if test="height != null">#{height},</if>
+            <if test="age != null">#{age},</if>
+            <if test="weight != null">#{weight},</if>
+            <if test="source != null">#{source},</if>
+            <if test="address != null">#{address},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="phone != null">#{phone},</if>
+            <if test="relationName != null">#{relationName},</if>
+            <if test="relationPhone != null">#{relationPhone},</if>
+            <if test="dieTime != null">#{dieTime},</if>
+            <if test="isDelete != null">#{isDelete},</if>
+            <if test="status != null">#{status},</if>
+            <if test="currentStatus != null">#{currentStatus},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBusDeviceHistory" parameterType="BusDeviceHistory">
+        update bus_device_history
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="dId != null">d_id = #{dId},</if>
+            <if test="pId != null">p_id = #{pId},</if>
+            <if test="signTime != null">sign_time = #{signTime},</if>
+            <if test="treatmentProject != null">treatment_project = #{treatmentProject},</if>
+            <if test="alarm != null">alarm = #{alarm},</if>
+            <if test="uploadTime != null">upload_time = #{uploadTime},</if>
+            <if test="machineModel != null">machine_model = #{machineModel},</if>
+            <if test="filterModel != null">filter_model = #{filterModel},</if>
+            <if test="hemodialysisModel != null">hemodialysis_model = #{hemodialysisModel},</if>
+            <if test="treatmentTime != null">treatment_time = #{treatmentTime},</if>
+            <if test="targetDehydration != null">target_dehydration = #{targetDehydration},</if>
+            <if test="bloodPumpRate != null">blood_pump_rate = #{bloodPumpRate},</if>
+            <if test="flow != null">flow = #{flow},</if>
+            <if test="k != null">k = #{k},</if>
+            <if test="ca != null">ca = #{ca},</if>
+            <if test="na != null">na = #{na},</if>
+            <if test="hco3 != null">hco3 = #{hco3},</if>
+            <if test="antithrombotic != null">antithrombotic = #{antithrombotic},</if>
+            <if test="firstDose != null">first_dose = #{firstDose},</if>
+            <if test="additionalDose != null">additional_dose = #{additionalDose},</if>
+            <if test="vascularAccess != null">vascular_access = #{vascularAccess},</if>
+            <if test="doctor != null">doctor = #{doctor},</if>
+            <if test="nurse != null">nurse = #{nurse},</if>
+            <if test="verifyPrescription != null">verify_prescription = #{verifyPrescription},</if>
+            <if test="targetWeight != null">target_weight = #{targetWeight},</if>
+            <if test="lastWeight != null">last_weight = #{lastWeight},</if>
+            <if test="beforeWeight != null">before_weight = #{beforeWeight},</if>
+            <if test="addWeight != null">add_weight = #{addWeight},</if>
+            <if test="beforeSbp != null">before_sbp = #{beforeSbp},</if>
+            <if test="beforeDbp != null">before_dbp = #{beforeDbp},</if>
+            <if test="beforeSphygmus != null">before_sphygmus = #{beforeSphygmus},</if>
+            <if test="beforeRr != null">before_rr = #{beforeRr},</if>
+            <if test="beforeTemperature != null">before_temperature = #{beforeTemperature},</if>
+            <if test="urineVolume != null">urine_volume = #{urineVolume},</if>
+            <if test="gainWeight != null">gain_weight = #{gainWeight},</if>
+            <if test="verifyBefore != null">verify_before = #{verifyBefore},</if>
+            <if test="preDialysisWeight != null">pre_dialysis_weight = #{preDialysisWeight},</if>
+            <if test="lossWeight != null">loss_weight = #{lossWeight},</if>
+            <if test="afterTemperature != null">after_temperature = #{afterTemperature},</if>
+            <if test="afterSbp != null">after_sbp = #{afterSbp},</if>
+            <if test="afterDbp != null">after_dbp = #{afterDbp},</if>
+            <if test="afterSphygmus != null">after_sphygmus = #{afterSphygmus},</if>
+            <if test="afterRr != null">after_rr = #{afterRr},</if>
+            <if test="verifyAfter != null">verify_after = #{verifyAfter},</if>
+            <if test="deviceId != null">device_id = #{deviceId},</if>
+            <if test="area != null">area = #{area},</if>
+            <if test="bed != null">bed = #{bed},</if>
+            <if test="brandName != null">brand_name = #{brandName},</if>
+            <if test="model != null">model = #{model},</if>
+            <if test="manufactureId != null">manufacture_id = #{manufactureId},</if>
+            <if test="price != null">price = #{price},</if>
+            <if test="infection != null">infection = #{infection},</if>
+            <if test="code != null">code = #{code},</if>
+            <if test="name != null">name = #{name},</if>
+            <if test="gender != null">gender = #{gender},</if>
+            <if test="height != null">height = #{height},</if>
+            <if test="age != null">age = #{age},</if>
+            <if test="weight != null">weight = #{weight},</if>
+            <if test="source != null">source = #{source},</if>
+            <if test="address != null">address = #{address},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="phone != null">phone = #{phone},</if>
+            <if test="relationName != null">relation_name = #{relationName},</if>
+            <if test="relationPhone != null">relation_phone = #{relationPhone},</if>
+            <if test="dieTime != null">die_time = #{dieTime},</if>
+            <if test="isDelete != null">is_delete = #{isDelete},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="currentStatus != null">current_Status = #{currentStatus},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <update id="updateStatusByIds">
+        update bus_device_history
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+
+
+    <delete id="deleteBusDeviceHistoryById" parameterType="Long">
+        delete from bus_device_history where id = #{id}
+    </delete>
+
+    <delete id="deleteBusDeviceHistoryByIds" parameterType="String">
+        delete from bus_device_history where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 132 - 0
ruoyi-system/src/main/resources/mapper/system/BusDeviceMapper.xml

@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.BusDeviceMapper">
+    
+    <resultMap type="BusDevice" id="BusDeviceResult">
+        <result property="id"    column="id"    />
+        <result property="deviceId"    column="device_id"    />
+        <result property="area"    column="area"    />
+        <result property="bed"    column="bed"    />
+        <result property="brandName"    column="brand_name"    />
+        <result property="model"    column="model"    />
+        <result property="manufactureId"    column="manufacture_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="price"    column="price"    />
+        <result property="warranty"    column="warranty"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="enable"    column="enable"    />
+        <result property="status"    column="status"    />
+        <result property="isDelete"    column="is_delete"    />
+        <result property="infection"    column="infection"    />
+    </resultMap>
+
+    <sql id="selectBusDeviceVo">
+        select id, device_id, area, bed, brand_name, model, manufacture_id, create_time, price, warranty, update_time, enable, status, is_delete, infection from bus_device
+    </sql>
+
+    <select id="selectBusDeviceList" parameterType="BusDevice" resultMap="BusDeviceResult">
+        <include refid="selectBusDeviceVo"/>
+        <where>  
+            <if test="deviceId != null  and deviceId != ''"> and device_id like concat('%', #{deviceId}, '%')</if>
+            <if test="area != null  and area != ''"> and area like concat('%', #{area}, '%')</if>
+            <if test="bed != null  and bed != ''"> and bed = #{bed}</if>
+            <if test="brandName != null  and brandName != ''"> and brand_name like concat('%', #{brandName}, '%')</if>
+            <if test="model != null  and model != ''"> and model like concat('%', #{model}, '%')</if>
+            <if test="manufactureId != null  and manufactureId != ''"> and manufacture_id like concat('%', #{manufactureId}, '%')</if>
+            <if test="price != null  and price != ''"> and price = #{price}</if>
+            <if test="warranty != null  and warranty != ''"> and warranty = #{warranty}</if>
+            <if test="enable != null "> and enable = #{enable}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="isDelete != null "> and is_delete = #{isDelete}</if>
+            <if test="infection != null "> and infection = #{infection}</if>
+        </where>
+    </select>
+    
+    <select id="selectBusDeviceById" parameterType="Long" resultMap="BusDeviceResult">
+        <include refid="selectBusDeviceVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertBusDevice" parameterType="BusDevice">
+        insert into bus_device
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="deviceId != null">device_id,</if>
+            <if test="area != null">area,</if>
+            <if test="bed != null">bed,</if>
+            <if test="brandName != null">brand_name,</if>
+            <if test="model != null">model,</if>
+            <if test="manufactureId != null">manufacture_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="price != null">price,</if>
+            <if test="warranty != null">warranty,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="enable != null">enable,</if>
+            <if test="status != null">status,</if>
+            <if test="isDelete != null">is_delete,</if>
+            <if test="infection != null">infection,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="deviceId != null">#{deviceId},</if>
+            <if test="area != null">#{area},</if>
+            <if test="bed != null">#{bed},</if>
+            <if test="brandName != null">#{brandName},</if>
+            <if test="model != null">#{model},</if>
+            <if test="manufactureId != null">#{manufactureId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="price != null">#{price},</if>
+            <if test="warranty != null">#{warranty},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="enable != null">#{enable},</if>
+            <if test="status != null">#{status},</if>
+            <if test="isDelete != null">#{isDelete},</if>
+            <if test="infection != null">#{infection},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBusDevice" parameterType="BusDevice">
+        update bus_device
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="deviceId != null">device_id = #{deviceId},</if>
+            <if test="area != null">area = #{area},</if>
+            <if test="bed != null">bed = #{bed},</if>
+            <if test="brandName != null">brand_name = #{brandName},</if>
+            <if test="model != null">model = #{model},</if>
+            <if test="manufactureId != null">manufacture_id = #{manufactureId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="price != null">price = #{price},</if>
+            <if test="warranty != null">warranty = #{warranty},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="enable != null">enable = #{enable},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="isDelete != null">is_delete = #{isDelete},</if>
+            <if test="infection != null">infection = #{infection},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBusDeviceById" parameterType="Long">
+        delete from bus_device where id = #{id}
+    </delete>
+
+    <delete id="deleteBusDeviceByIds" parameterType="String">
+        delete from bus_device where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <update id="updateStatusByIds">
+        update bus_device_history
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="status != null">status = #{status},</if>
+        </trim>
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+</mapper>

+ 58 - 0
ruoyi-system/src/main/resources/mapper/system/BusDeviceRunningMapper.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.BusDeviceRunningMapper">
+    
+    <resultMap type="BusDeviceRunning" id="BusDeviceRunningResult">
+        <result property="id"    column="id"    />
+        <result property="json"    column="json"    />
+    </resultMap>
+
+    <sql id="selectBusDeviceRunningVo">
+        select id, json from bus_device_running
+    </sql>
+
+    <select id="selectBusDeviceRunningList" parameterType="BusDeviceRunning" resultMap="BusDeviceRunningResult">
+        <include refid="selectBusDeviceRunningVo"/>
+        <where>  
+            <if test="json != null  and json != ''"> and json = #{json}</if>
+        </where>
+    </select>
+    
+    <select id="selectBusDeviceRunningById" parameterType="Long" resultMap="BusDeviceRunningResult">
+        <include refid="selectBusDeviceRunningVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertBusDeviceRunning" parameterType="BusDeviceRunning">
+        insert into bus_device_running
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="json != null">json,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="json != null">#{json},</if>
+         </trim>
+    </insert>
+
+    <update id="updateBusDeviceRunning" parameterType="BusDeviceRunning">
+        update bus_device_running
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="json != null">json = #{json},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteBusDeviceRunningById" parameterType="Long">
+        delete from bus_device_running where id = #{id}
+    </delete>
+
+    <delete id="deleteBusDeviceRunningByIds" parameterType="String">
+        delete from bus_device_running where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 22 - 0
ruoyi-ui/.editorconfig

@@ -0,0 +1,22 @@
+# 告诉EditorConfig插件,这是根文件,不用继续往上查找
+root = true
+
+# 匹配全部文件
+[*]
+# 设置字符集
+charset = utf-8
+# 缩进风格,可选space、tab
+indent_style = space
+# 缩进的空格数
+indent_size = 2
+# 结尾换行符,可选lf、cr、crlf
+end_of_line = lf
+# 在文件结尾插入新行
+insert_final_newline = true
+# 删除一行中的前后空格
+trim_trailing_whitespace = true
+
+# 匹配md结尾的文件
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 11 - 0
ruoyi-ui/.env.development

@@ -0,0 +1,11 @@
+# 页面标题
+VUE_APP_TITLE = 若依管理系统
+
+# 开发环境配置
+ENV = 'development'
+
+# 若依管理系统/开发环境
+VUE_APP_BASE_API = '/dev-api'
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 8 - 0
ruoyi-ui/.env.production

@@ -0,0 +1,8 @@
+# 页面标题
+VUE_APP_TITLE = 若依管理系统
+
+# 生产环境配置
+ENV = 'production'
+
+# 若依管理系统/生产环境
+VUE_APP_BASE_API = '/prod-api'

+ 10 - 0
ruoyi-ui/.env.staging

@@ -0,0 +1,10 @@
+# 页面标题
+VUE_APP_TITLE = 若依管理系统
+
+NODE_ENV = production
+
+# 测试环境配置
+ENV = 'staging'
+
+# 若依管理系统/测试环境
+VUE_APP_BASE_API = '/stage-api'

+ 10 - 0
ruoyi-ui/.eslintignore

@@ -0,0 +1,10 @@
+# 忽略build目录下类型为js的文件的语法检查
+build/*.js
+# 忽略src/assets目录下文件的语法检查
+src/assets
+# 忽略public目录下文件的语法检查
+public
+# 忽略当前目录下为js的文件的语法检查
+*.js
+# 忽略当前目录下为vue的文件的语法检查
+*.vue

+ 199 - 0
ruoyi-ui/.eslintrc.js

@@ -0,0 +1,199 @@
+// ESlint 检查配置
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  //it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 23 - 0
ruoyi-ui/.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock

+ 30 - 0
ruoyi-ui/README.md

@@ -0,0 +1,30 @@
+## 开发
+
+```bash
+# 克隆项目
+git clone https://gitee.com/y_project/RuoYi-Vue
+
+# 进入项目目录
+cd ruoyi-ui
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npmmirror.com
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 http://localhost:80
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```

+ 13 - 0
ruoyi-ui/babel.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+  presets: [
+    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}

+ 12 - 0
ruoyi-ui/bin/build.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 打包Web工程,生成dist文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+npm run build:prod
+
+pause

+ 12 - 0
ruoyi-ui/bin/package.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 安装Web工程,生成node_modules文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+npm install --registry=https://registry.npmmirror.com
+
+pause

+ 12 - 0
ruoyi-ui/bin/run-web.bat

@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 使用 Vue CLI 命令运行 Web 工程。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+npm run dev
+
+pause

+ 35 - 0
ruoyi-ui/build/index.js

@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}

+ 90 - 0
ruoyi-ui/package.json

@@ -0,0 +1,90 @@
+{
+  "name": "ruoyi",
+  "version": "3.8.2",
+  "description": "若依管理系统",
+  "author": "若依",
+  "license": "MIT",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "lint": "eslint --ext .js,.vue src"
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "src/**/*.{js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
+  "keywords": [
+    "vue",
+    "admin",
+    "dashboard",
+    "element-ui",
+    "boilerplate",
+    "admin-template",
+    "management-system"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://gitee.com/y_project/RuoYi-Vue.git"
+  },
+  "dependencies": {
+    "@riophae/vue-treeselect": "0.4.0",
+    "axios": "0.24.0",
+    "clipboard": "2.0.8",
+    "core-js": "3.19.1",
+    "echarts": "4.9.0",
+    "element-ui": "2.15.8",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.4.3",
+    "highlight.js": "9.18.5",
+    "js-beautify": "1.13.0",
+    "js-cookie": "3.0.1",
+    "jsencrypt": "3.0.0-rc.1",
+    "nprogress": "0.2.0",
+    "quill": "1.3.7",
+    "screenfull": "5.0.2",
+    "sortablejs": "1.10.2",
+    "vue": "2.6.12",
+    "vue-count-to": "1.0.13",
+    "vue-cropper": "0.5.5",
+    "vue-meta": "2.4.0",
+    "vue-router": "3.4.9",
+    "vuedraggable": "2.24.3",
+    "vuex": "3.6.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.6",
+    "@vue/cli-plugin-eslint": "4.4.6",
+    "@vue/cli-service": "4.4.6",
+    "babel-eslint": "10.1.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "4.1.0",
+    "compression-webpack-plugin": "5.0.2",
+    "connect": "3.6.6",
+    "eslint": "7.15.0",
+    "eslint-plugin-vue": "7.2.0",
+    "lint-staged": "10.5.3",
+    "runjs": "4.4.2",
+    "sass": "1.32.13",
+    "sass-loader": "10.1.1",
+    "script-ext-html-webpack-plugin": "2.1.5",
+    "svg-sprite-loader": "5.1.1",
+    "vue-template-compiler": "2.6.12"
+  },
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

BIN
ruoyi-ui/public/favicon.ico


File diff suppressed because it is too large
+ 21 - 0
ruoyi-ui/public/html/ie.html


+ 208 - 0
ruoyi-ui/public/index.html

@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="renderer" content="webkit">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= webpackConfig.name %></title>
+    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
+	  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #000;
+      padding: 0.2em 0;
+    }
+
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 150px;
+      height: 150px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      -ms-animation: spin 2s linear infinite;
+      -moz-animation: spin 2s linear infinite;
+      -o-animation: spin 2s linear infinite;
+      animation: spin 2s linear infinite;
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      -moz-animation: spin 3s linear infinite;
+      -o-animation: spin 3s linear infinite;
+      -ms-animation: spin 3s linear infinite;
+      animation: spin 3s linear infinite;
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -moz-animation: spin 1.5s linear infinite;
+      -o-animation: spin 1.5s linear infinite;
+      -ms-animation: spin 1.5s linear infinite;
+      -webkit-animation: spin 1.5s linear infinite;
+      animation: spin 1.5s linear infinite;
+    }
+
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #7171C6;
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      -ms-transform: translateX(0);
+      transform: translateX(0);
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      -ms-transform: translateX(-100%);
+      transform: translateX(-100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      -ms-transform: translateX(100%);
+      transform: translateX(100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      -ms-transform: translateY(-100%);
+      transform: translateY(-100%);
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    .no-js #loader-wrapper {
+      display: none;
+    }
+
+    .no-js h1 {
+      color: #222222;
+    }
+
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #FFF;
+      font-size: 19px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 13px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+  </style>
+  </head>
+  <body>
+    <div id="app">
+	    <div id="loader-wrapper">
+		    <div id="loader"></div>
+		    <div class="loader-section section-left"></div>
+		    <div class="loader-section section-right"></div>
+		    <div class="load_title">正在加载系统资源,请耐心等待</div>
+        </div>
+	</div>
+  </body>
+</html>

+ 2 - 0
ruoyi-ui/public/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /

+ 19 - 0
ruoyi-ui/src/App.vue

@@ -0,0 +1,19 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default  {
+  name:  'App',
+    metaInfo() {
+        return {
+            title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
+            titleTemplate: title => {
+                return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE
+            }
+        }
+    }
+}
+</script>

+ 59 - 0
ruoyi-ui/src/api/login.js

@@ -0,0 +1,59 @@
+import request from '@/utils/request'
+
+// 登录方法
+export function login(username, password, code, uuid) {
+  const data = {
+    username,
+    password,
+    code,
+    uuid
+  }
+  return request({
+    url: '/login',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 注册方法
+export function register(data) {
+  return request({
+    url: '/register',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取用户详细信息
+export function getInfo() {
+  return request({
+    url: '/getInfo',
+    method: 'get'
+  })
+}
+
+// 退出方法
+export function logout() {
+  return request({
+    url: '/logout',
+    method: 'post'
+  })
+}
+
+// 获取验证码
+export function getCodeImg() {
+  return request({
+    url: '/captchaImage',
+    headers: {
+      isToken: false
+    },
+    method: 'get',
+    timeout: 20000
+  })
+}

+ 9 - 0
ruoyi-ui/src/api/menu.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 获取路由
+export const getRouters = () => {
+  return request({
+    url: '/getRouters',
+    method: 'get'
+  })
+}

+ 9 - 0
ruoyi-ui/src/api/monitor/cache.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 查询缓存详细
+export function getCache() {
+  return request({
+    url: '/monitor/cache',
+    method: 'get'
+  })
+}

Some files were not shown because too many files changed in this diff