From 94e59f72de992c8b5484ed9d03ee00aa7edf8427 Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Fri, 16 Nov 2018 10:53:52 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E9=9B=86=E6=88=90=E9=80=9A=E7=94=A8mappe?= =?UTF-8?q?r,=E4=BF=AE=E6=94=B9=E5=A4=9A=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E5=88=87=E9=9D=A2=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=BD=A9=E8=89=B2?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=8C=E4=BF=AE=E6=94=B9=E5=A4=9A=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 48 ++++ ...ication-druid.yml => application-prod.yml} | 2 +- .../src/main/resources/application.yml | 11 +- .../src/main/resources/logback-spring.xml | 206 ++++++++++++++++++ ruoyi-admin/src/main/resources/logback.xml | 86 -------- ruoyi-common/pom.xml | 8 + .../ruoyi/common/annotation/DataSource.java | 22 -- .../com/ruoyi/common/base/BaseMapper.java | 24 ++ .../framework/aspectj/DataSourceAspect.java | 50 +++-- .../main/resources/mapper/mybatis-config.xml | 15 -- 10 files changed, 322 insertions(+), 150 deletions(-) create mode 100644 ruoyi-admin/src/main/resources/application-dev.yml rename ruoyi-admin/src/main/resources/{application-druid.yml => application-prod.yml} (95%) create mode 100644 ruoyi-admin/src/main/resources/logback-spring.xml delete mode 100644 ruoyi-admin/src/main/resources/logback.xml delete mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/base/BaseMapper.java delete mode 100644 ruoyi-system/src/main/resources/mapper/mybatis-config.xml diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml new file mode 100644 index 000000000..57b36d518 --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -0,0 +1,48 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true + username: root + password: root + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: true + url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true + username: root + password: root + # 初始连接数 + initial-size: 10 + # 最大连接池数量 + max-active: 100 + # 最小连接池数量 + min-idle: 10 + # 配置获取连接等待超时的时间 + max-wait: 60000 + # 打开PSCache,并且指定每个连接上PSCache的大小 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + min-evictable-idle-time-millis: 300000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + test-on-borrow: false + test-on-return: false + stat-view-servlet: + enabled: true + url-pattern: /monitor/druid/* + filter: + stat: + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: false + wall: + config: + multi-statement-allow: true \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-prod.yml similarity index 95% rename from ruoyi-admin/src/main/resources/application-druid.yml rename to ruoyi-admin/src/main/resources/application-prod.yml index da7a23d52..62dc55b67 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -8,7 +8,7 @@ spring: master: url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true username: root - password: password + password: root # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index a1acec922..07dda1ac4 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -55,7 +55,7 @@ spring: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss profiles: - active: druid + active: dev # 文件上传 servlet: multipart: @@ -66,6 +66,9 @@ spring: restart: # 热部署开关 enabled: true + output: + ansi: + enabled: always # MyBatis mybatis: @@ -73,8 +76,10 @@ mybatis: typeAliasesPackage: com.ruoyi # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml - # 加载全局的配置文件 - configLocation: classpath:mapper/mybatis-config.xml + +mapper: + not-empty: true + identity: MYSQL # PageHelper分页插件 pagehelper: diff --git a/ruoyi-admin/src/main/resources/logback-spring.xml b/ruoyi-admin/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..abdb98ba7 --- /dev/null +++ b/ruoyi-admin/src/main/resources/logback-spring.xml @@ -0,0 +1,206 @@ + + + + + + + ruoyi + + + + + + + + + + + + + + + + + + debug + + + ${CONSOLE_LOG_PATTERN} + + UTF-8 + + + + + + + + + + ${log.path}/web_debug.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - + %msg%n + UTF-8 + + + + + ${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log + + + 100MB + + + 15 + + + + debug + ACCEPT + DENY + + + + + + + ${log.path}/web_info.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - + %msg%n + UTF-8 + + + + + ${log.path}/web-info-%d{yyyy-MM-dd}.%i.log + + + 100MB + + + 15 + + + + info + ACCEPT + DENY + + + + + + + ${log.path}/web_warn.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - + %msg%n + UTF-8 + + + + ${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log + + + 100MB + + + 15 + + + + warn + ACCEPT + DENY + + + + + + + ${log.path}/web_error.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - + %msg%n + UTF-8 + + + + ${log.path}/web-error-%d{yyyy-MM-dd}.%i.log + + + 100MB + + + 15 + + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml deleted file mode 100644 index bd468488c..000000000 --- a/ruoyi-admin/src/main/resources/logback.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - ${log.pattern} - - - - - - ${log.path}/sys-info.log - - - ${log.path}/sys-info.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - INFO - ACCEPT - DENY - - - - - ${log.path}/sys-error.log - - ${log.path}/sys-error.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - ERROR - ACCEPT - DENY - - - - - - ${log.path}/sys-user.log - - - ${log.path}/sys-user.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 32552e111..e37811f65 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -20,6 +20,7 @@ 1.3.3 1.11.3 3.17 + 2.0.4 @@ -70,6 +71,13 @@ snakeyaml + + + tk.mybatis + mapper-spring-boot-starter + ${mapper.starter.version} + + \ No newline at end of file diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java deleted file mode 100644 index 5a41a2c6d..000000000 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ruoyi.common.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import com.ruoyi.common.enums.DataSourceType; - -/** - * 自定义多数据源切换注解 - * - * @author ruoyi - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface DataSource -{ - /** - * 切换数据源名称 - */ - public DataSourceType value() default DataSourceType.MASTER; -} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/base/BaseMapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/base/BaseMapper.java new file mode 100644 index 000000000..726b66392 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/base/BaseMapper.java @@ -0,0 +1,24 @@ +/* + * @(#)BaseMapper.java 2016-3-30 下午5:57:15 + * Copyright 2016 张孟如, Inc. All rights reserved. + * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +package com.ruoyi.common.base; + +import tk.mybatis.mapper.common.ConditionMapper; +import tk.mybatis.mapper.common.IdsMapper; +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.special.InsertListMapper; + +/** + *

File:BaseMapper.java

+ *

Title:

+ *

Description:

+ *

Copyright: Copyright (c) 2016 2016-3-30 下午5:57:15

+ *

Company:

+ * @author 张孟如 + * @version 1.0 + */ +public interface BaseMapper extends Mapper, IdsMapper,InsertListMapper,ConditionMapper +{ +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java index c4eab3fbc..a38c76b91 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -1,23 +1,20 @@ package com.ruoyi.framework.aspectj; -import java.lang.reflect.Method; +import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import com.ruoyi.common.annotation.DataSource; -import com.ruoyi.common.utils.StringUtils; + +import com.ruoyi.common.enums.DataSourceType; import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + /** * 多数据源处理 - * - * @author ruoyi */ @Aspect @Order(1) @@ -26,26 +23,21 @@ public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); - @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)") - public void dsPointCut() - { - - } - - @Around("dsPointCut()") + @Around("execution(* com.ruoyi..*ServiceImpl.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable { - MethodSignature signature = (MethodSignature) point.getSignature(); - - Method method = signature.getMethod(); - - DataSource dataSource = method.getAnnotation(DataSource.class); - - if (StringUtils.isNotNull(dataSource)) + // 获取到当前执行的方法名 + String methodName = point.getSignature().getName(); + if (isSlave(methodName)) { - DynamicDataSourceContextHolder.setDateSoureType(dataSource.value().name()); + // 标记为读库,可以自定义选择数据源 + DynamicDataSourceContextHolder.setDateSoureType(DataSourceType.SLAVE.name()); + } + else + { + // 标记为写库 + DynamicDataSourceContextHolder.setDateSoureType(DataSourceType.MASTER.name()); } - try { return point.proceed(); @@ -56,4 +48,16 @@ public class DataSourceAspect DynamicDataSourceContextHolder.clearDateSoureType(); } } + + /** + * 判断是否为读库 + * + * @param methodName + * @return + */ + private boolean isSlave(String methodName) + { + // 方法名以query、find、get开头的方法名走从库 + return StringUtils.startsWithAny(methodName, new String[]{"query", "find", "get", "select"}); + } } diff --git a/ruoyi-system/src/main/resources/mapper/mybatis-config.xml b/ruoyi-system/src/main/resources/mapper/mybatis-config.xml deleted file mode 100644 index c7c36e375..000000000 --- a/ruoyi-system/src/main/resources/mapper/mybatis-config.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - From 11fa736bf80d8bba2a33a65a591e5b102cc6b020 Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Fri, 16 Nov 2018 11:03:55 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2ca61db37..10b0cbed3 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,14 @@ ## 平台简介 +本项目FORK自 [若依/RuoYi](https://gitee.com/y_project/RuoYi) +暂时花了几分钟时间把自己整合过的功能传上来: +###### 多数据源切面 +删了多数据源注解,改成根据方法名自动切换,默认主从分离(当然主从地址一样),当只有一个数据源的时候虽然会打印日志,实际主从还是同一个,膈应就自己改一下 +###### 集成通用mapper +一开始想整合mybatis-plus,这玩意太重了,而且crud和本项目很多地方八字不合。mapper集成com.ruoyi.common.base.BaseMapper后就可以策马奔腾啦,谁用谁知道 +###### 控制台日志分等级彩色渲染和多环境修改 +具体看logback-spring.xml和application.xml改动 -2018年度最受欢迎中国开源软件评选 -请给若依/RuoYi 投票,谢谢支持。 -https://www.oschina.net/project/top_cn_2018?sort=1 - - -一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套后台系统。如此有了若依。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 - -性别男,若依是女儿的名字。 - -若依基于hplus和inspinia两套后台系统模板开发。有需要可自行到群内下载。 - -http://www.zi-han.net/theme/hplus - -http://webapplayers.com/inspinia_admin-v2.7.1 - -> RuoYi从3.0开始,进行模块拆分,将原先的单应用转变为多模块,如需单应用,请移步 [RuoYi-fast](https://gitee.com/y_project/RuoYi-fast) - -> 推荐使用阿里云部署,通用云产品代金券 :[点我领取](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=brki8iof) - ## 内置功能 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 From 2b2dd8f36f826aa79616b322ee4982192f5f96ff Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Fri, 16 Nov 2018 11:06:17 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10b0cbed3..dc0f2a40f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ###### 多数据源切面 删了多数据源注解,改成根据方法名自动切换,默认主从分离(当然主从地址一样),当只有一个数据源的时候虽然会打印日志,实际主从还是同一个,膈应就自己改一下 ###### 集成通用mapper -一开始想整合mybatis-plus,这玩意太重了,而且crud和本项目很多地方八字不合。mapper集成com.ruoyi.common.base.BaseMapper后就可以策马奔腾啦,谁用谁知道 +一开始想整合mybatis-plus,这玩意太重了,而且crud和本项目很多地方八字不合。mapper继承com.ruoyi.common.base.BaseMapper后就可以策马奔腾啦,谁用谁知道 ###### 控制台日志分等级彩色渲染和多环境修改 具体看logback-spring.xml和application.xml改动 From fb87c428c9f4fe36907dd4a7d3b796b8c75e99e1 Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Fri, 16 Nov 2018 18:24:24 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E5=8C=85?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=92=8C=E6=B3=A8=E8=A7=A3=E6=89=AB=E6=8F=8F?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/web/core/config/SwaggerConfig.java | 79 ++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java index 9cee0a47f..cd8b29dac 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -2,7 +2,14 @@ package com.ruoyi.web.core.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.ruoyi.common.config.Global; + +import io.swagger.annotations.ApiOperation; +import springfox.documentation.RequestHandler; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; @@ -29,13 +36,12 @@ public class SwaggerConfig { return new Docket(DocumentationType.SWAGGER_2) // 详细定制 - .apiInfo(apiInfo()) - .select() + .apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) // 指定当前包路径 - .apis(RequestHandlerSelectors.basePackage("com.ruoyi.web.controller.tool")) + // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.web.controller.tool")) + // .apis(basePackage("com.ruoyi.web.controller.system,com.ruoyi.web.controller.tool")) // 扫描所有 .apis(RequestHandlerSelectors.any()) - .paths(PathSelectors.any()) - .build(); + .paths(PathSelectors.any()).build(); } /** @@ -44,11 +50,62 @@ public class SwaggerConfig private ApiInfo apiInfo() { // 用ApiInfoBuilder进行定制 - return new ApiInfoBuilder() - .title("标题:若依管理系统_接口文档") - .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") - .contact(new Contact(Global.getName(), null, null)) - .version("版本号:" + Global.getVersion()) - .build(); + return new ApiInfoBuilder().title("标题:若依管理系统_接口文档").description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + .contact(new Contact(Global.getName(), null, null)).version("版本号:" + Global.getVersion()).build(); + } + + /** + * Predicate that matches RequestHandler with given base package name for the class of the handler method. + * This predicate includes all request handlers matching the provided basePackage + * + * @param basePackage - base package of the classes + * @return this + */ + public static Predicate basePackage(final String basePackage) + { + return new Predicate() + { + @Override + public boolean apply(RequestHandler input) + { + return declaringClass(input).transform(handlerPackage(basePackage)).or(true); + } + }; + } + + /** + * 处理包路径配置规则,支持多路径扫描匹配以逗号隔开 + * + * @param basePackage 扫描包路径 + * @return Function + */ + private static Function, Boolean> handlerPackage(final String basePackage) + { + return new Function, Boolean>() + { + @Override + public Boolean apply(Class input) + { + for (String strPackage : basePackage.split(",")) + { + boolean isMatch = input.getPackage().getName().startsWith(strPackage); + if (isMatch) + { + return true; + } + } + return false; + } + }; + } + + /** + * @param input RequestHandler + * @return Optional + */ + @SuppressWarnings("deprecation") + private static Optional> declaringClass(RequestHandler input) + { + return Optional.fromNullable(input.declaringClass()); } } From 9600806e5f1c8430449e2da84f8948ecd4aef614 Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Mon, 19 Nov 2018 10:00:45 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2ca61db37..dc0f2a40f 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,14 @@ ## 平台简介 +本项目FORK自 [若依/RuoYi](https://gitee.com/y_project/RuoYi) +暂时花了几分钟时间把自己整合过的功能传上来: +###### 多数据源切面 +删了多数据源注解,改成根据方法名自动切换,默认主从分离(当然主从地址一样),当只有一个数据源的时候虽然会打印日志,实际主从还是同一个,膈应就自己改一下 +###### 集成通用mapper +一开始想整合mybatis-plus,这玩意太重了,而且crud和本项目很多地方八字不合。mapper继承com.ruoyi.common.base.BaseMapper后就可以策马奔腾啦,谁用谁知道 +###### 控制台日志分等级彩色渲染和多环境修改 +具体看logback-spring.xml和application.xml改动 -2018年度最受欢迎中国开源软件评选 -请给若依/RuoYi 投票,谢谢支持。 -https://www.oschina.net/project/top_cn_2018?sort=1 - - -一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套后台系统。如此有了若依。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 - -性别男,若依是女儿的名字。 - -若依基于hplus和inspinia两套后台系统模板开发。有需要可自行到群内下载。 - -http://www.zi-han.net/theme/hplus - -http://webapplayers.com/inspinia_admin-v2.7.1 - -> RuoYi从3.0开始,进行模块拆分,将原先的单应用转变为多模块,如需单应用,请移步 [RuoYi-fast](https://gitee.com/y_project/RuoYi-fast) - -> 推荐使用阿里云部署,通用云产品代金券 :[点我领取](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=brki8iof) - ## 内置功能 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 From 71d30e6590e81823aa8f52a9437919946d18b944 Mon Sep 17 00:00:00 2001 From: zhangmrit Date: Mon, 19 Nov 2018 13:08:43 +0800 Subject: [PATCH 06/12] =?UTF-8?q?OSS=E6=A8=A1=E5=9D=97=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- pom.xml | 4 + ruoyi-admin/pom.xml | 189 ++--- .../main/java/com/ruoyi/RuoYiApplication.java | 3 +- .../controller/system/SysOssController.java | 178 +++++ .../cloud/AliyunCloudStorageService.java | 60 ++ .../system/cloud/CloudConstant.java | 39 + .../system/cloud/CloudStorageConfig.java | 288 ++++++++ .../system/cloud/CloudStorageService.java | 73 ++ .../controller/system/cloud/OSSFactory.java | 38 + .../cloud/QcloudCloudStorageService.java | 85 +++ .../cloud/QiniuCloudStorageService.java | 81 +++ .../system/cloud/valdator/AliyunGroup.java | 8 + .../system/cloud/valdator/QcloudGroup.java | 8 + .../system/cloud/valdator/QiniuGroup.java | 8 + .../static/ajax/libs/ajaxfile/ajaxupload.js | 673 ++++++++++++++++++ .../templates/system/oss/config.html | 166 +++++ .../resources/templates/system/oss/edit.html | 60 ++ .../resources/templates/system/oss/oss.html | 160 +++++ .../ruoyi/framework/util/ValidatorUtils.java | 39 + .../web/exception/user/OssException.java | 18 + .../java/com/ruoyi/system/domain/SysOss.java | 113 +++ .../com/ruoyi/system/mapper/SysOssMapper.java | 11 + .../system/service/ISysConfigService.java | 8 + .../ruoyi/system/service/ISysOssService.java | 46 ++ .../service/impl/SysConfigServiceImpl.java | 17 + .../service/impl/SysOssServiceImpl.java | 84 +++ sql/oss.sql | 12 + 28 files changed, 2390 insertions(+), 82 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java create mode 100644 ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js create mode 100644 ruoyi-admin/src/main/resources/templates/system/oss/config.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/oss/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/system/oss/oss.html create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/util/ValidatorUtils.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/user/OssException.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java create mode 100644 sql/oss.sql diff --git a/README.md b/README.md index dc0f2a40f..4bc26ded4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ 一开始想整合mybatis-plus,这玩意太重了,而且crud和本项目很多地方八字不合。mapper继承com.ruoyi.common.base.BaseMapper后就可以策马奔腾啦,谁用谁知道 ###### 控制台日志分等级彩色渲染和多环境修改 具体看logback-spring.xml和application.xml改动 - +###### 集成七牛云,阿里云,腾讯云OSS +详见sql/oss.sql,在上传页面配置好相关参数即可使用 ## 内置功能 diff --git a/pom.xml b/pom.xml index 20f6855cb..b3eac6d4a 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,10 @@ 2.3.2 2.7.0 1.2.5 + [7.2.0, 7.2.99] + 2.5.0 + 4.4 + 1.2.49 diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 2812cd584..9a2aa12ef 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -1,81 +1,110 @@ - - - - ruoyi - com.ruoyi - 3.0 - - 4.0.0 - jar - ruoyi-admin - - - web服务入口 - - - - - - - - org.springframework.boot - spring-boot-devtools - true - - - - - io.springfox - springfox-swagger2 - ${swagger.version} - - - - - io.springfox - springfox-swagger-ui - ${swagger.version} - - - - - com.ruoyi - ruoyi-framework - ${ruoyi.version} - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - true - - - - - repackage - - - - - - org.apache.maven.plugins - maven-war-plugin - 3.0.0 - - false - ${artifactId} - - - - ${artifactId} - - + + + + ruoyi + com.ruoyi + 3.0 + + 4.0.0 + jar + ruoyi-admin + + + web服务入口 + + + + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + + + com.ruoyi + ruoyi-framework + ${ruoyi.version} + + + + + + com.qiniu + qiniu-java-sdk + ${qiniu.version} + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun.oss.version} + + + com.qcloud + cos_api + ${qcloud.cos.version} + + + org.slf4j + slf4j-log4j12 + + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + false + ${artifactId} + + + + ${artifactId} + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index f72741ce0..8d622e66c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -1,10 +1,11 @@ package com.ruoyi; -import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import tk.mybatis.spring.annotation.MapperScan; + /** * 启动程序 * diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java new file mode 100644 index 000000000..13a79aa4d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java @@ -0,0 +1,178 @@ +package com.ruoyi.web.controller.system; + +import java.util.Date; +import java.util.List; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson.JSON; +import com.google.gson.Gson; +import com.ruoyi.common.base.AjaxResult; +import com.ruoyi.framework.util.ShiroUtils; +import com.ruoyi.framework.util.ValidatorUtils; +import com.ruoyi.framework.web.base.BaseController; +import com.ruoyi.framework.web.exception.user.OssException; +import com.ruoyi.framework.web.page.TableDataInfo; +import com.ruoyi.system.domain.SysOss; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysOssService; +import com.ruoyi.web.controller.system.cloud.CloudConstant; +import com.ruoyi.web.controller.system.cloud.CloudConstant.CloudService; +import com.ruoyi.web.controller.system.cloud.CloudStorageConfig; +import com.ruoyi.web.controller.system.cloud.CloudStorageService; +import com.ruoyi.web.controller.system.cloud.OSSFactory; +import com.ruoyi.web.controller.system.cloud.valdator.AliyunGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QcloudGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QiniuGroup; + +/** + * 文件上传 + */ +@Controller +@RequestMapping("system/oss") +public class SysOssController extends BaseController +{ + private String prefix = "system/oss"; + + private final static String KEY = CloudConstant.CLOUD_STORAGE_CONFIG_KEY; + + @Autowired + private ISysOssService sysOssService; + + @Autowired + private ISysConfigService sysConfigService; + + @RequiresPermissions("system:dept:view") + @GetMapping() + public String dept() + { + return prefix + "/oss"; + } + + /** + * 列表 + */ + @RequestMapping("list") + @RequiresPermissions("sys:oss:list") + @ResponseBody + public TableDataInfo list(SysOss sysOss) + { + startPage(); + List list = sysOssService.getList(sysOss); + return getDataTable(list); + } + + /** + * 云存储配置信息 + */ + @RequestMapping("config") + @RequiresPermissions("sys:oss:config") + public String config(Model model) + { + String jsonconfig = sysConfigService.selectConfigByKey(CloudConstant.CLOUD_STORAGE_CONFIG_KEY); + // 获取云存储配置信息 + CloudStorageConfig config = JSON.parseObject(jsonconfig, CloudStorageConfig.class); + model.addAttribute("config", config); + return prefix + "/config"; + } + + /** + * 保存云存储配置信息 + */ + @RequestMapping("saveConfig") + @RequiresPermissions("sys:oss:config") + @ResponseBody + public AjaxResult saveConfig(CloudStorageConfig config) + { + // 校验类型 + ValidatorUtils.validateEntity(config); + if (config.getType() == CloudService.QINIU.getValue()) + { + // 校验七牛数据 + ValidatorUtils.validateEntity(config, QiniuGroup.class); + } + else if (config.getType() == CloudService.ALIYUN.getValue()) + { + // 校验阿里云数据 + ValidatorUtils.validateEntity(config, AliyunGroup.class); + } + else if (config.getType() == CloudService.QCLOUD.getValue()) + { + // 校验腾讯云数据 + ValidatorUtils.validateEntity(config, QcloudGroup.class); + } + return toAjax(sysConfigService.updateValueByKey(KEY, new Gson().toJson(config))); + } + + /** + * 上传文件 + */ + @RequestMapping("/upload") + @RequiresPermissions("sys:oss:add") + @ResponseBody + public AjaxResult upload(@RequestParam("file") MultipartFile file) throws Exception + { + if (file.isEmpty()) + { + throw new OssException("上传文件不能为空"); + } + // 上传文件 + String fileName = file.getOriginalFilename(); + String suffix = fileName.substring(fileName.lastIndexOf(".")); + CloudStorageService storage=OSSFactory.build(); + String url = storage.uploadSuffix(file.getBytes(), suffix); + // 保存文件信息 + SysOss ossEntity = new SysOss(); + ossEntity.setUrl(url); + ossEntity.setFileSuffix(suffix); + ossEntity.setCreateBy(ShiroUtils.getLoginName()); + ossEntity.setFileName(fileName); + ossEntity.setCreateTime(new Date()); + ossEntity.setService(storage.getService()); + return toAjax(sysOssService.save(ossEntity)); + } + + /** + * 修改 + */ + @GetMapping("edit/{ossId}") + @RequiresPermissions("sys:oss:edit") + public String edit(@PathVariable("ossId") Long ossId, Model model) + { + SysOss sysOss = sysOssService.findById(ossId); + model.addAttribute("sysOss", sysOss); + return prefix + "/edit"; + } + + /** + * 修改 + */ + @PostMapping("edit") + @RequiresPermissions("sys:oss:edit") + @ResponseBody + public AjaxResult editSave(SysOss sysOss) + { + return toAjax(sysOssService.update(sysOss)); + } + + /** + * 删除 + */ + @RequestMapping("remove") + @RequiresPermissions("sys:oss:remove") + @ResponseBody + public AjaxResult delete(String ids) + { + return toAjax(sysOssService.deleteByIds(ids)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java new file mode 100644 index 000000000..3e9b13d5c --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java @@ -0,0 +1,60 @@ +package com.ruoyi.web.controller.system.cloud; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import com.aliyun.oss.OSSClient; +import com.ruoyi.framework.web.exception.user.OssException; + +/** + * 阿里云存储 + */ +public class AliyunCloudStorageService extends CloudStorageService +{ + private OSSClient client; + + public AliyunCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + client = new OSSClient(config.getAliyunEndPoint(), config.getAliyunAccessKeyId(), + config.getAliyunAccessKeySecret()); + } + + @Override + public String upload(byte[] data, String path) + { + return upload(new ByteArrayInputStream(data), path); + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + client.putObject(config.getAliyunBucketName(), path, inputStream); + } + catch (Exception e) + { + throw new OssException("上传文件失败,请检查配置信息"); + } + return config.getAliyunDomain() + "/" + path; + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getAliyunPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getAliyunPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java new file mode 100644 index 000000000..6fdd466ec --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.system.cloud; + +public class CloudConstant +{ + /** + * 云存储配置KEY + */ + public final static String CLOUD_STORAGE_CONFIG_KEY = "sys.oss.cloudStorage"; + + /** + * 云服务商 + */ + public enum CloudService + { + /** + * 七牛云 + */ + QINIU(1), + /** + * 阿里云 + */ + ALIYUN(2), + /** + * 腾讯云 + */ + QCLOUD(3); + private int value; + + CloudService(int value) + { + this.value = value; + } + + public int getValue() + { + return value; + } + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java new file mode 100644 index 000000000..c2f713ff7 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java @@ -0,0 +1,288 @@ +package com.ruoyi.web.controller.system.cloud; + +import java.io.Serializable; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.URL; + +import com.ruoyi.web.controller.system.cloud.valdator.AliyunGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QcloudGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QiniuGroup; + +/** + * 云存储配置信息 + */ +public class CloudStorageConfig implements Serializable +{ + // + private static final long serialVersionUID = 9035033846176792944L; + + // 类型 1:七牛 2:阿里云 3:腾讯云 + @Range(min = 1, max = 3, message = "类型错误") + private Integer type; + + // 七牛绑定的域名 + @NotBlank(message = "七牛绑定的域名不能为空", groups = QiniuGroup.class) + @URL(message = "七牛绑定的域名格式不正确", groups = QiniuGroup.class) + private String qiniuDomain; + + // 七牛路径前缀 + private String qiniuPrefix; + + // 七牛ACCESS_KEY + @NotBlank(message = "七牛AccessKey不能为空", groups = QiniuGroup.class) + private String qiniuAccessKey; + + // 七牛SECRET_KEY + @NotBlank(message = "七牛SecretKey不能为空", groups = QiniuGroup.class) + private String qiniuSecretKey; + + // 七牛存储空间名 + @NotBlank(message = "七牛空间名不能为空", groups = QiniuGroup.class) + private String qiniuBucketName; + + // 阿里云绑定的域名 + @NotBlank(message = "阿里云绑定的域名不能为空", groups = AliyunGroup.class) + @URL(message = "阿里云绑定的域名格式不正确", groups = AliyunGroup.class) + private String aliyunDomain; + + // 阿里云路径前缀 + private String aliyunPrefix; + + // 阿里云EndPoint + @NotBlank(message = "阿里云EndPoint不能为空", groups = AliyunGroup.class) + private String aliyunEndPoint; + + // 阿里云AccessKeyId + @NotBlank(message = "阿里云AccessKeyId不能为空", groups = AliyunGroup.class) + private String aliyunAccessKeyId; + + // 阿里云AccessKeySecret + @NotBlank(message = "阿里云AccessKeySecret不能为空", groups = AliyunGroup.class) + private String aliyunAccessKeySecret; + + // 阿里云BucketName + @NotBlank(message = "阿里云BucketName不能为空", groups = AliyunGroup.class) + private String aliyunBucketName; + + // 腾讯云绑定的域名 + @NotBlank(message = "腾讯云绑定的域名不能为空", groups = QcloudGroup.class) + @URL(message = "腾讯云绑定的域名格式不正确", groups = QcloudGroup.class) + private String qcloudDomain; + + // 腾讯云路径前缀 + private String qcloudPrefix; + + // 腾讯云AppId + @NotNull(message = "腾讯云AppId不能为空", groups = QcloudGroup.class) + private Integer qcloudAppId; + + // 腾讯云SecretId + @NotBlank(message = "腾讯云SecretId不能为空", groups = QcloudGroup.class) + private String qcloudSecretId; + + // 腾讯云SecretKey + @NotBlank(message = "腾讯云SecretKey不能为空", groups = QcloudGroup.class) + private String qcloudSecretKey; + + // 腾讯云BucketName + @NotBlank(message = "腾讯云BucketName不能为空", groups = QcloudGroup.class) + private String qcloudBucketName; + + // 腾讯云COS所属地区 + @NotBlank(message = "所属地区不能为空", groups = QcloudGroup.class) + private String qcloudRegion; + + public Integer getType() + { + return type; + } + + public void setType(Integer type) + { + this.type = type; + } + + public String getQiniuDomain() + { + return qiniuDomain; + } + + public void setQiniuDomain(String qiniuDomain) + { + this.qiniuDomain = qiniuDomain; + } + + public String getQiniuAccessKey() + { + return qiniuAccessKey; + } + + public void setQiniuAccessKey(String qiniuAccessKey) + { + this.qiniuAccessKey = qiniuAccessKey; + } + + public String getQiniuSecretKey() + { + return qiniuSecretKey; + } + + public void setQiniuSecretKey(String qiniuSecretKey) + { + this.qiniuSecretKey = qiniuSecretKey; + } + + public String getQiniuBucketName() + { + return qiniuBucketName; + } + + public void setQiniuBucketName(String qiniuBucketName) + { + this.qiniuBucketName = qiniuBucketName; + } + + public String getQiniuPrefix() + { + return qiniuPrefix; + } + + public void setQiniuPrefix(String qiniuPrefix) + { + this.qiniuPrefix = qiniuPrefix; + } + + public String getAliyunDomain() + { + return aliyunDomain; + } + + public void setAliyunDomain(String aliyunDomain) + { + this.aliyunDomain = aliyunDomain; + } + + public String getAliyunPrefix() + { + return aliyunPrefix; + } + + public void setAliyunPrefix(String aliyunPrefix) + { + this.aliyunPrefix = aliyunPrefix; + } + + public String getAliyunEndPoint() + { + return aliyunEndPoint; + } + + public void setAliyunEndPoint(String aliyunEndPoint) + { + this.aliyunEndPoint = aliyunEndPoint; + } + + public String getAliyunAccessKeyId() + { + return aliyunAccessKeyId; + } + + public void setAliyunAccessKeyId(String aliyunAccessKeyId) + { + this.aliyunAccessKeyId = aliyunAccessKeyId; + } + + public String getAliyunAccessKeySecret() + { + return aliyunAccessKeySecret; + } + + public void setAliyunAccessKeySecret(String aliyunAccessKeySecret) + { + this.aliyunAccessKeySecret = aliyunAccessKeySecret; + } + + public String getAliyunBucketName() + { + return aliyunBucketName; + } + + public void setAliyunBucketName(String aliyunBucketName) + { + this.aliyunBucketName = aliyunBucketName; + } + + public String getQcloudDomain() + { + return qcloudDomain; + } + + public void setQcloudDomain(String qcloudDomain) + { + this.qcloudDomain = qcloudDomain; + } + + public String getQcloudPrefix() + { + return qcloudPrefix; + } + + public void setQcloudPrefix(String qcloudPrefix) + { + this.qcloudPrefix = qcloudPrefix; + } + + public Integer getQcloudAppId() + { + return qcloudAppId; + } + + public void setQcloudAppId(Integer qcloudAppId) + { + this.qcloudAppId = qcloudAppId; + } + + public String getQcloudSecretId() + { + return qcloudSecretId; + } + + public void setQcloudSecretId(String qcloudSecretId) + { + this.qcloudSecretId = qcloudSecretId; + } + + public String getQcloudSecretKey() + { + return qcloudSecretKey; + } + + public void setQcloudSecretKey(String qcloudSecretKey) + { + this.qcloudSecretKey = qcloudSecretKey; + } + + public String getQcloudBucketName() + { + return qcloudBucketName; + } + + public void setQcloudBucketName(String qcloudBucketName) + { + this.qcloudBucketName = qcloudBucketName; + } + + public String getQcloudRegion() + { + return qcloudRegion; + } + + public void setQcloudRegion(String qcloudRegion) + { + this.qcloudRegion = qcloudRegion; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java new file mode 100644 index 000000000..74ae020ea --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java @@ -0,0 +1,73 @@ +package com.ruoyi.web.controller.system.cloud; + +import java.io.InputStream; +import java.util.UUID; + +import org.apache.commons.lang.StringUtils; + +import com.ruoyi.common.utils.DateUtils; + +/** + * 云存储(支持七牛、阿里云、腾讯云、又拍云) + */ +public abstract class CloudStorageService +{ + /** 云存储配置信息 */ + CloudStorageConfig config; + + public int getService() + { + return config.getType(); + } + + /** + * 文件路径 + * @param prefix 前缀 + * @param suffix 后缀 + * @return 返回上传路径 + */ + public String getPath(String prefix, String suffix) + { + // 生成uuid + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + // 文件路径 + String path = DateUtils.dateTime() + "/" + uuid; + if (StringUtils.isNotBlank(prefix)) + { + path = prefix + "/" + path; + } + return path + suffix; + } + + /** + * 文件上传 + * @param data 文件字节数组 + * @param path 文件路径,包含文件名 + * @return 返回http地址 + */ + public abstract String upload(byte[] data, String path); + + /** + * 文件上传 + * @param data 文件字节数组 + * @param suffix 后缀 + * @return 返回http地址 + */ + public abstract String uploadSuffix(byte[] data, String suffix); + + /** + * 文件上传 + * @param inputStream 字节流 + * @param path 文件路径,包含文件名 + * @return 返回http地址 + */ + public abstract String upload(InputStream inputStream, String path); + + /** + * 文件上传 + * @param inputStream 字节流 + * @param suffix 后缀 + * @return 返回http地址 + */ + public abstract String uploadSuffix(InputStream inputStream, String suffix); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java new file mode 100644 index 000000000..49bd7c5eb --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java @@ -0,0 +1,38 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.framework.util.SpringUtils; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.web.controller.system.cloud.CloudConstant.CloudService; + +/** + * 文件上传Factory + */ +public final class OSSFactory +{ + private static ISysConfigService sysConfigService; + static + { + OSSFactory.sysConfigService = (ISysConfigService) SpringUtils.getBean(ISysConfigService.class); + } + + public static CloudStorageService build() + { + String jsonconfig = sysConfigService.selectConfigByKey(CloudConstant.CLOUD_STORAGE_CONFIG_KEY); + // 获取云存储配置信息 + CloudStorageConfig config = JSON.parseObject(jsonconfig, CloudStorageConfig.class); + if (config.getType() == CloudService.QINIU.getValue()) + { + return new QiniuCloudStorageService(config); + } + else if (config.getType() == CloudService.ALIYUN.getValue()) + { + return new AliyunCloudStorageService(config); + } + else if (config.getType() == CloudService.QCLOUD.getValue()) + { + return new QcloudCloudStorageService(config); + } + return null; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java new file mode 100644 index 000000000..005f2d244 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java @@ -0,0 +1,85 @@ +package com.ruoyi.web.controller.system.cloud; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.io.IOUtils; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.request.UploadFileRequest; +import com.qcloud.cos.sign.Credentials; +import com.ruoyi.framework.web.exception.user.OssException; + +import net.sf.json.JSONObject; + +/** + * 腾讯云存储 + */ +public class QcloudCloudStorageService extends CloudStorageService +{ + private COSClient client; + + public QcloudCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + Credentials credentials = new Credentials(config.getQcloudAppId(), config.getQcloudSecretId(), + config.getQcloudSecretKey()); + // 初始化客户端配置 + ClientConfig clientConfig = new ClientConfig(); + // 设置bucket所在的区域,华南:gz 华北:tj 华东:sh + clientConfig.setRegion(config.getQcloudRegion()); + client = new COSClient(clientConfig, credentials); + } + + @Override + public String upload(byte[] data, String path) + { + // 腾讯云必需要以"/"开头 + if (!path.startsWith("/")) + { + path = "/" + path; + } + // 上传到腾讯云 + UploadFileRequest request = new UploadFileRequest(config.getQcloudBucketName(), path, data); + String response = client.uploadFile(request); + JSONObject jsonObject = JSONObject.fromObject(response); + if (jsonObject.getInt("code") != 0) + { + throw new OssException("文件上传失败," + jsonObject.getString("message")); + } + return config.getQcloudDomain() + path; + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + byte[] data = IOUtils.toByteArray(inputStream); + return this.upload(data, path); + } + catch (IOException e) + { + throw new OssException("上传文件失败"); + } + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getQcloudPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getQcloudPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java new file mode 100644 index 000000000..2384f537d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java @@ -0,0 +1,81 @@ +package com.ruoyi.web.controller.system.cloud; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.io.IOUtils; + +import com.qiniu.common.Zone; +import com.qiniu.http.Response; +import com.qiniu.storage.Configuration; +import com.qiniu.storage.UploadManager; +import com.qiniu.util.Auth; +import com.ruoyi.framework.web.exception.user.OssException; + +/** + * 七牛云存储 + */ +public class QiniuCloudStorageService extends CloudStorageService +{ + private UploadManager uploadManager; + + private String token; + + public QiniuCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + uploadManager = new UploadManager(new Configuration(Zone.autoZone())); + token = Auth.create(config.getQiniuAccessKey(), config.getQiniuSecretKey()) + .uploadToken(config.getQiniuBucketName()); + } + + @Override + public String upload(byte[] data, String path) + { + try + { + Response res = uploadManager.put(data, path, token); + if (!res.isOK()) + { + throw new RuntimeException("上传七牛出错:" + res.toString()); + } + } + catch (Exception e) + { + throw new OssException("上传文件失败,请核对七牛配置信息"); + } + return config.getQiniuDomain() + "/" + path; + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + byte[] data = IOUtils.toByteArray(inputStream); + return this.upload(data, path); + } + catch (IOException e) + { + throw new OssException("上传文件失败"); + } + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getQiniuPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getQiniuPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java new file mode 100644 index 000000000..42e74a72b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 阿里云 + */ +public interface AliyunGroup +{ +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java new file mode 100644 index 000000000..6d1012f97 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 腾讯云 + */ +public interface QcloudGroup +{ +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java new file mode 100644 index 000000000..9ab8c95a2 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 七牛 + */ +public interface QiniuGroup +{ +} diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js b/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js new file mode 100644 index 000000000..5e0eb9496 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js @@ -0,0 +1,673 @@ +/** + * AJAX Upload ( http://valums.com/ajax-upload/ ) + * Copyright (c) Andris Valums + * Licensed under the MIT license ( http://valums.com/mit-license/ ) + * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions + */ +(function () { + /* global window */ + /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */ + + /** + * Wrapper for FireBug's console.log + */ + function log(){ + if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){ + Array.prototype.unshift.call(arguments, '[Ajax Upload]'); + console.log( Array.prototype.join.call(arguments, ' ')); + } + } + + /** + * Attaches event to a dom element. + * @param {Element} el + * @param type event name + * @param fn callback This refers to the passed element + */ + function addEvent(el, type, fn){ + if (el.addEventListener) { + el.addEventListener(type, fn, false); + } else if (el.attachEvent) { + el.attachEvent('on' + type, function(){ + fn.call(el); + }); + } else { + throw new Error('not supported or DOM not loaded'); + } + } + + /** + * Attaches resize event to a window, limiting + * number of event fired. Fires only when encounteres + * delay of 100 after series of events. + * + * Some browsers fire event multiple times when resizing + * http://www.quirksmode.org/dom/events/resize.html + * + * @param fn callback This refers to the passed element + */ + function addResizeEvent(fn){ + var timeout; + + addEvent(window, 'resize', function(){ + if (timeout){ + clearTimeout(timeout); + } + timeout = setTimeout(fn, 100); + }); + } + + // Needs more testing, will be rewriten for next version + // getOffset function copied from jQuery lib (http://jquery.com/) + if (document.documentElement.getBoundingClientRect){ + // Get Offset using getBoundingClientRect + // http://ejohn.org/blog/getboundingclientrect-is-awesome/ + var getOffset = function(el){ + var box = el.getBoundingClientRect(); + var doc = el.ownerDocument; + var body = doc.body; + var docElem = doc.documentElement; // for ie + var clientTop = docElem.clientTop || body.clientTop || 0; + var clientLeft = docElem.clientLeft || body.clientLeft || 0; + + // In Internet Explorer 7 getBoundingClientRect property is treated as physical, + // while others are logical. Make all logical, like in IE8. + var zoom = 1; + if (body.getBoundingClientRect) { + var bound = body.getBoundingClientRect(); + zoom = (bound.right - bound.left) / body.clientWidth; + } + + if (zoom > 1) { + clientTop = 0; + clientLeft = 0; + } + + var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft; + + return { + top: top, + left: left + }; + }; + } else { + // Get offset adding all offsets + var getOffset = function(el){ + var top = 0, left = 0; + do { + top += el.offsetTop || 0; + left += el.offsetLeft || 0; + el = el.offsetParent; + } while (el); + + return { + left: left, + top: top + }; + }; + } + + /** + * Returns left, top, right and bottom properties describing the border-box, + * in pixels, with the top-left relative to the body + * @param {Element} el + * @return {Object} Contains left, top, right,bottom + */ + function getBox(el){ + var left, right, top, bottom; + var offset = getOffset(el); + left = offset.left; + top = offset.top; + + right = left + el.offsetWidth; + bottom = top + el.offsetHeight; + + return { + left: left, + right: right, + top: top, + bottom: bottom + }; + } + + /** + * Helper that takes object literal + * and add all properties to element.style + * @param {Element} el + * @param {Object} styles + */ + function addStyles(el, styles){ + for (var name in styles) { + if (styles.hasOwnProperty(name)) { + el.style[name] = styles[name]; + } + } + } + + /** + * Function places an absolutely positioned + * element on top of the specified element + * copying position and dimentions. + * @param {Element} from + * @param {Element} to + */ + function copyLayout(from, to){ + var box = getBox(from); + + addStyles(to, { + position: 'absolute', + left : box.left + 'px', + top : box.top + 'px', + width : from.offsetWidth + 'px', + height : from.offsetHeight + 'px' + }); + } + + /** + * Creates and returns element from html chunk + * Uses innerHTML to create an element + */ + var toElement = (function(){ + var div = document.createElement('div'); + return function(html){ + div.innerHTML = html; + var el = div.firstChild; + return div.removeChild(el); + }; + })(); + + /** + * Function generates unique id + * @return unique id + */ + var getUID = (function(){ + var id = 0; + return function(){ + return 'ValumsAjaxUpload' + id++; + }; + })(); + + /** + * Get file name from path + * @param {String} file path to file + * @return filename + */ + function fileFromPath(file){ + return file.replace(/.*(\/|\\)/, ""); + } + + /** + * Get file extension lowercase + * @param {String} file name + * @return file extenstion + */ + function getExt(file){ + return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : ''; + } + + function hasClass(el, name){ + var re = new RegExp('\\b' + name + '\\b'); + return re.test(el.className); + } + function addClass(el, name){ + if ( ! hasClass(el, name)){ + el.className += ' ' + name; + } + } + function removeClass(el, name){ + var re = new RegExp('\\b' + name + '\\b'); + el.className = el.className.replace(re, ''); + } + + function removeNode(el){ + el.parentNode.removeChild(el); + } + + /** + * Easy styling and uploading + * @constructor + * @param button An element you want convert to + * upload button. Tested dimentions up to 500x500px + * @param {Object} options See defaults below. + */ + window.AjaxUpload = function(button, options){ + this._settings = { + // Location of the server-side upload script + action: 'upload.php', + // File upload name + name: 'userfile', + // Additional data to send + data: {}, + // Submit file as soon as it's selected + autoSubmit: true, + // The type of data that you're expecting back from the server. + // html and xml are detected automatically. + // Only useful when you are using json data as a response. + // Set to "json" in that case. + responseType: false, + // Class applied to button when mouse is hovered + hoverClass: 'hover', + // Class applied to button when AU is disabled + disabledClass: 'disabled', + // When user selects a file, useful with autoSubmit disabled + // You can return false to cancel upload + onChange: function(file, extension){ + }, + // Callback to fire before file is uploaded + // You can return false to cancel upload + onSubmit: function(file, extension){ + }, + // Fired when file upload is completed + // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE! + onComplete: function(file, response){ + } + }; + + // Merge the users options with our defaults + for (var i in options) { + if (options.hasOwnProperty(i)){ + this._settings[i] = options[i]; + } + } + + // button isn't necessary a dom element + if (button.jquery){ + // jQuery object was passed + button = button[0]; + } else if (typeof button == "string") { + if (/^#.*/.test(button)){ + // If jQuery user passes #elementId don't break it + button = button.slice(1); + } + + button = document.getElementById(button); + } + + if ( ! button || button.nodeType !== 1){ + throw new Error("Please make sure that you're passing a valid element"); + } + + if ( button.nodeName.toUpperCase() == 'A'){ + // disable link + addEvent(button, 'click', function(e){ + if (e && e.preventDefault){ + e.preventDefault(); + } else if (window.event){ + window.event.returnValue = false; + } + }); + } + + // DOM element + this._button = button; + // DOM element + this._input = null; + // If disabled clicking on button won't do anything + this._disabled = false; + + // if the button was disabled before refresh if will remain + // disabled in FireFox, let's fix it + this.enable(); + + this._rerouteClicks(); + }; + + // assigning methods to our class + AjaxUpload.prototype = { + setData: function(data){ + this._settings.data = data; + }, + disable: function(){ + addClass(this._button, this._settings.disabledClass); + this._disabled = true; + + var nodeName = this._button.nodeName.toUpperCase(); + if (nodeName == 'INPUT' || nodeName == 'BUTTON'){ + this._button.setAttribute('disabled', 'disabled'); + } + + // hide input + if (this._input){ + // We use visibility instead of display to fix problem with Safari 4 + // The problem is that the value of input doesn't change if it + // has display none when user selects a file + this._input.parentNode.style.visibility = 'hidden'; + } + }, + enable: function(){ + removeClass(this._button, this._settings.disabledClass); + this._button.removeAttribute('disabled'); + this._disabled = false; + + }, + /** + * Creates invisible file input + * that will hover above the button + *
+ */ + _createInput: function(){ + var self = this; + + var input = document.createElement("input"); + input.setAttribute('type', 'file'); + input.setAttribute('name', this._settings.name); + + addStyles(input, { + 'position' : 'absolute', + // in Opera only 'browse' button + // is clickable and it is located at + // the right side of the input + 'right' : 0, + 'margin' : 0, + 'padding' : 0, + 'fontSize' : '480px', + 'cursor' : 'pointer' + }); + + var div = document.createElement("div"); + addStyles(div, { + 'display' : 'block', + 'position' : 'absolute', + 'overflow' : 'hidden', + 'margin' : 0, + 'padding' : 0, + 'opacity' : 0, + // Make sure browse button is in the right side + // in Internet Explorer + 'direction' : 'ltr', + //Max zIndex supported by Opera 9.0-9.2 + 'zIndex': 2147483583 + }); + + // Make sure that element opacity exists. + // Otherwise use IE filter + if ( div.style.opacity !== "0") { + if (typeof(div.filters) == 'undefined'){ + throw new Error('Opacity not supported by the browser'); + } + div.style.filter = "alpha(opacity=0)"; + } + + addEvent(input, 'change', function(){ + + if ( ! input || input.value === ''){ + return; + } + + // Get filename from input, required + // as some browsers have path instead of it + var file = fileFromPath(input.value); + + if (false === self._settings.onChange.call(self, file, getExt(file))){ + self._clearInput(); + return; + } + + // Submit form when value is changed + if (self._settings.autoSubmit) { + self.submit(); + } + }); + + addEvent(input, 'mouseover', function(){ + addClass(self._button, self._settings.hoverClass); + }); + + addEvent(input, 'mouseout', function(){ + removeClass(self._button, self._settings.hoverClass); + + // We use visibility instead of display to fix problem with Safari 4 + // The problem is that the value of input doesn't change if it + // has display none when user selects a file + input.parentNode.style.visibility = 'hidden'; + + }); + + div.appendChild(input); + document.body.appendChild(div); + + this._input = input; + }, + _clearInput : function(){ + if (!this._input){ + return; + } + + // this._input.value = ''; Doesn't work in IE6 + removeNode(this._input.parentNode); + this._input = null; + this._createInput(); + + removeClass(this._button, this._settings.hoverClass); + }, + /** + * Function makes sure that when user clicks upload button, + * the this._input is clicked instead + */ + _rerouteClicks: function(){ + var self = this; + + // IE will later display 'access denied' error + // if you use using self._input.click() + // other browsers just ignore click() + + addEvent(self._button, 'mouseover', function(){ + if (self._disabled){ + return; + } + + if ( ! self._input){ + self._createInput(); + } + + var div = self._input.parentNode; + copyLayout(self._button, div); + div.style.visibility = 'visible'; + + }); + + + // commented because we now hide input on mouseleave + /** + * When the window is resized the elements + * can be misaligned if button position depends + * on window size + */ + //addResizeEvent(function(){ + // if (self._input){ + // copyLayout(self._button, self._input.parentNode); + // } + //}); + + }, + /** + * Creates iframe with unique name + * @return {Element} iframe + */ + _createIframe: function(){ + // We can't use getTime, because it sometimes return + // same value in safari :( + var id = getUID(); + + // We can't use following code as the name attribute + // won't be properly registered in IE6, and new window + // on form submit will open + // var iframe = document.createElement('iframe'); + // iframe.setAttribute('name', id); + + var iframe = toElement('