| @@ -48,6 +48,5 @@ output/ | |||||
| .classpath | .classpath | ||||
| logs/ | logs/ | ||||
| /dubhe-k8s/src/main/resources/kubeconfig | /dubhe-k8s/src/main/resources/kubeconfig | ||||
| /dubhe-k8s/src/main/resources | |||||
| *.log | *.log | ||||
| /dubhe-admin/kubeconfig | /dubhe-admin/kubeconfig | ||||
| @@ -1,13 +1,22 @@ | |||||
| # 一站式开发平台-服务端 | |||||
| # 之江天枢-服务端 | |||||
| ## 本地开发 | |||||
| **之江天枢一站式人工智能开源平台**(简称:**之江天枢**),包括海量数据处理、交互式模型构建(包含Notebook和模型可视化)、AI模型高效训练。多维度产品形态满足从开发者到大型企业的不同需求,将提升人工智能技术的研发效率、扩大算法模型的应用范围,进一步构建人工智能生态“朋友圈”。 | |||||
| ## 源码部署 | |||||
| ### 准备环境 | ### 准备环境 | ||||
| 安装如下软件环境。 | 安装如下软件环境。 | ||||
| - OpenJDK:1.8+ | - OpenJDK:1.8+ | ||||
| - Redis: 3.0+ | - Redis: 3.0+ | ||||
| - Maven: 3.0+ | - Maven: 3.0+ | ||||
| - MYSQL: 5.5.0+ | |||||
| - MYSQL: 5.7.0+ | |||||
| ### 下载源码 | |||||
| ``` bash | |||||
| git clone https://codeup.teambition.com/zhejianglab/dubhe-server.git | |||||
| # 进入项目根目录 | |||||
| cd dubhe-server | |||||
| ``` | |||||
| ### 创建DB | ### 创建DB | ||||
| 在MySQL中依次执行如下sql文件 | 在MySQL中依次执行如下sql文件 | ||||
| @@ -20,14 +29,35 @@ sql/v1/02-Dubhe-DML.sql | |||||
| ### 配置 | ### 配置 | ||||
| 根据实际情况修改如下配置文件。 | 根据实际情况修改如下配置文件。 | ||||
| ``` | ``` | ||||
| dubhe-admin/src/main/resources/config/application-dev.yml | |||||
| dubhe-admin/src/main/resources/config/application-prod.yml | |||||
| ``` | ``` | ||||
| ### 启动: | |||||
| ### 构建 | |||||
| ``` bash | |||||
| # 构建,生成的 jar 包位于 ./dubhe-admin/target/dubhe-admin-1.0.jar | |||||
| mvn clean compile package | |||||
| ``` | ``` | ||||
| mvn spring-boot:run | |||||
| ### 启动 | |||||
| ``` bash | |||||
| # 指定启动环境为 prod | |||||
| ## admin模块 | |||||
| java -jar ./dubhe-admin/target/dubhe-admin-1.0-exec.jar --spring.profiles.active=prod | |||||
| ## task模块 | |||||
| java -jar ./dubhe-task/target/dubhe-task-1.0.jar --spring.profiles.active=prod | |||||
| ``` | ``` | ||||
| ## 本地开发 | |||||
| ### 必要条件: | |||||
| 导入maven项目,下载所需的依赖包 | |||||
| mysql下创建数据库dubhe,初始化数据脚本 | |||||
| 安装redis | |||||
| ### 启动: | |||||
| mvn spring-boot:run | |||||
| ## 代码结构: | ## 代码结构: | ||||
| ``` | ``` | ||||
| ├── common 公共模块 | ├── common 公共模块 | ||||
| @@ -49,6 +79,7 @@ mvn spring-boot:run | |||||
| ├── dubhe-data 数据处理模块 | ├── dubhe-data 数据处理模块 | ||||
| ├── dubhe-model 模型管理模块 | ├── dubhe-model 模型管理模块 | ||||
| ├── dubhe-system 系统管理 | ├── dubhe-system 系统管理 | ||||
| ├── dubhe-task 定时任务模块 | |||||
| ``` | ``` | ||||
| ## docker服务器 | ## docker服务器 | ||||
| @@ -29,11 +29,6 @@ | |||||
| <artifactId>guava</artifactId> | <artifactId>guava</artifactId> | ||||
| <version>21.0</version> | <version>21.0</version> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>com.github.penggle</groupId> | |||||
| <artifactId>kaptcha</artifactId> | |||||
| <version>${kaptcha.version}</version> | |||||
| </dependency> | |||||
| <!-- shiro --> | <!-- shiro --> | ||||
| <dependency> | <dependency> | ||||
| <groupId>org.apache.shiro</groupId> | <groupId>org.apache.shiro</groupId> | ||||
| @@ -95,6 +90,10 @@ | |||||
| <artifactId>commons-compress</artifactId> | <artifactId>commons-compress</artifactId> | ||||
| <version>1.20</version> | <version>1.20</version> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>com.github.whvcse</groupId> | |||||
| <artifactId>easy-captcha</artifactId> | |||||
| </dependency> | |||||
| </dependencies> | </dependencies> | ||||
| <build> | <build> | ||||
| <plugins> | <plugins> | ||||
| @@ -14,31 +14,28 @@ | |||||
| * limitations under the License. | * limitations under the License. | ||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.annotation; | package org.dubhe.annotation; | ||||
| import java.lang.annotation.*; | |||||
| /** | /** | ||||
| * 数据权限过滤Mapper拦截 | |||||
| * | |||||
| * @date 2020-06-22 | |||||
| * @description 数据权限注解 | |||||
| * @date 2020-09-24 | |||||
| */ | */ | ||||
| import java.lang.annotation.*; | |||||
| @Target({ElementType.METHOD, ElementType.TYPE}) | @Target({ElementType.METHOD, ElementType.TYPE}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | @Documented | ||||
| public @interface DataPermission { | public @interface DataPermission { | ||||
| /** | /** | ||||
| * 不需要数据权限的方法名 | |||||
| * 只在类的注解上使用,代表方法的数据权限类型 | |||||
| * @return | |||||
| */ | */ | ||||
| String[] ignores() default {}; | |||||
| String permission() default ""; | |||||
| /** | /** | ||||
| * 只在方法的注解上使用,代表方法的数据权限类型,如果不加注解,只会识别带"select"方法名的方法 | |||||
| * | |||||
| * 不需要数据权限的方法名 | |||||
| * @return | * @return | ||||
| */ | */ | ||||
| String[] permission() default {}; | |||||
| String[] ignoresMethod() default {}; | |||||
| } | } | ||||
| @@ -0,0 +1,47 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.annotation; | |||||
| import org.dubhe.enums.DatasetTypeEnum; | |||||
| import java.lang.annotation.ElementType; | |||||
| import java.lang.annotation.Retention; | |||||
| import java.lang.annotation.RetentionPolicy; | |||||
| import java.lang.annotation.Target; | |||||
| /** | |||||
| * @description 数据权限方法注解 | |||||
| * @date 2020-09-24 | |||||
| */ | |||||
| @Target(ElementType.METHOD) | |||||
| @Retention(RetentionPolicy.RUNTIME) | |||||
| public @interface DataPermissionMethod { | |||||
| /** | |||||
| * 是否需要拦截标识 true: 不拦截 false: 拦截 | |||||
| * | |||||
| * @return 拦截标识 | |||||
| */ | |||||
| boolean interceptFlag() default false; | |||||
| /** | |||||
| * 数据类型 | |||||
| * | |||||
| * @return 数据集类型 | |||||
| */ | |||||
| DatasetTypeEnum dataType() default DatasetTypeEnum.PRIVATE; | |||||
| } | |||||
| @@ -17,19 +17,20 @@ | |||||
| package org.dubhe.annotation; | package org.dubhe.annotation; | ||||
| import javax.validation.Constraint; | |||||
| import javax.validation.ConstraintValidator; | |||||
| import javax.validation.ConstraintValidatorContext; | |||||
| import javax.validation.Payload; | |||||
| import java.lang.annotation.ElementType; | import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | import java.lang.annotation.Target; | ||||
| import java.lang.reflect.InvocationTargetException; | import java.lang.reflect.InvocationTargetException; | ||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||
| import javax.validation.Constraint; | |||||
| import javax.validation.ConstraintValidator; | |||||
| import javax.validation.ConstraintValidatorContext; | |||||
| import javax.validation.Payload; | |||||
| /** | /** | ||||
| * @date: 2020-05-21 | |||||
| * @description 接口枚举类检测标注类 | |||||
| * @date 2020-05-21 | |||||
| */ | */ | ||||
| @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) | @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) | ||||
| @Retention(RetentionPolicy.RUNTIME) | @Retention(RetentionPolicy.RUNTIME) | ||||
| @@ -0,0 +1,66 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.annotation; | |||||
| import javax.validation.Constraint; | |||||
| import javax.validation.ConstraintValidator; | |||||
| import javax.validation.ConstraintValidatorContext; | |||||
| import javax.validation.Payload; | |||||
| import java.lang.annotation.*; | |||||
| import java.util.Arrays; | |||||
| /** | |||||
| * @description 自定义状态校验注解(传入值是否在指定状态范围内) | |||||
| * @date 2020-09-18 | |||||
| */ | |||||
| @Target({ElementType.FIELD, ElementType.PARAMETER}) | |||||
| @Retention(RetentionPolicy.RUNTIME) | |||||
| @Constraint(validatedBy = FlagValidator.Validator.class) | |||||
| @Documented | |||||
| public @interface FlagValidator { | |||||
| String[] value() default {}; | |||||
| String message() default "flag value is invalid"; | |||||
| Class<?>[] groups() default {}; | |||||
| Class<? extends Payload>[] payload() default {}; | |||||
| /** | |||||
| * @description 校验传入值是否在默认值范围校验逻辑 | |||||
| * @date 2020-09-18 | |||||
| */ | |||||
| class Validator implements ConstraintValidator<FlagValidator, Integer> { | |||||
| private String[] values; | |||||
| @Override | |||||
| public void initialize(FlagValidator flagValidator) { | |||||
| this.values = flagValidator.value(); | |||||
| } | |||||
| @Override | |||||
| public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) { | |||||
| if (value == null) { | |||||
| //当状态为空时,使用默认值 | |||||
| return false; | |||||
| } | |||||
| return Arrays.stream(values).anyMatch(value::equals); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -15,8 +15,7 @@ | |||||
| */ | */ | ||||
| package org.dubhe.aspect; | package org.dubhe.aspect; | ||||
| import java.util.UUID; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import org.aspectj.lang.JoinPoint; | import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | import org.aspectj.lang.annotation.Around; | ||||
| @@ -28,10 +27,11 @@ import org.slf4j.MDC; | |||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.StringUtils; | import org.springframework.util.StringUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import java.util.UUID; | |||||
| /** | /** | ||||
| * @date 2020/04/10 | |||||
| * @description 日志切面 | |||||
| * @date 2020-04-10 | |||||
| */ | */ | ||||
| @Component | @Component | ||||
| @Aspect | @Aspect | ||||
| @@ -54,7 +54,7 @@ public class LogAspect { | |||||
| public void taskAspect() { | public void taskAspect() { | ||||
| } | } | ||||
| @Pointcut(" serviceAspect() || taskAspect() ") | |||||
| @Pointcut(" serviceAspect() ") | |||||
| public void aroundAspect() { | public void aroundAspect() { | ||||
| } | } | ||||
| @@ -0,0 +1,116 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.aspect; | |||||
| 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.dubhe.annotation.DataPermissionMethod; | |||||
| import org.dubhe.base.BaseService; | |||||
| import org.dubhe.base.DataContext; | |||||
| import org.dubhe.domain.dto.CommonPermissionDataDTO; | |||||
| import org.dubhe.enums.DatasetTypeEnum; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.lang.reflect.Method; | |||||
| import java.util.HashSet; | |||||
| import java.util.Objects; | |||||
| import java.util.Set; | |||||
| /** | |||||
| * @description 数据权限切面 | |||||
| * @date 2020-09-24 | |||||
| */ | |||||
| @Aspect | |||||
| @Component | |||||
| public class PermissionAspect { | |||||
| /** | |||||
| * 公共数据的有用户ID | |||||
| */ | |||||
| public static final Long PUBLIC_DATA_USER_ID = 0L; | |||||
| /** | |||||
| * 基于注解的切面方法 | |||||
| */ | |||||
| @Pointcut("@annotation(org.dubhe.annotation.DataPermissionMethod)") | |||||
| private void cutMethod() { | |||||
| } | |||||
| /** | |||||
| *环绕通知 | |||||
| * @param joinPoint 切入参数对象 | |||||
| * @return 返回方法结果集 | |||||
| * @throws Throwable | |||||
| */ | |||||
| @Around("cutMethod()") | |||||
| public Object around(ProceedingJoinPoint joinPoint) throws Throwable { | |||||
| // 获取方法传入参数 | |||||
| Object[] params = joinPoint.getArgs(); | |||||
| DataPermissionMethod dataPermissionMethod = getDeclaredAnnotation(joinPoint); | |||||
| if (!Objects.isNull(JwtUtils.getCurrentUserDto()) && !Objects.isNull(dataPermissionMethod)) { | |||||
| Set<Long> ids = new HashSet<>(); | |||||
| ids.add(JwtUtils.getCurrentUserDto().getId()); | |||||
| CommonPermissionDataDTO commonPermissionDataDTO = CommonPermissionDataDTO.builder().type(dataPermissionMethod.interceptFlag()).resourceUserIds(ids).build(); | |||||
| if (DatasetTypeEnum.PUBLIC.equals(dataPermissionMethod.dataType())) { | |||||
| ids.add(PUBLIC_DATA_USER_ID); | |||||
| commonPermissionDataDTO.setResourceUserIds(ids); | |||||
| } | |||||
| DataContext.set(commonPermissionDataDTO); | |||||
| } | |||||
| // 执行源方法 | |||||
| try { | |||||
| return joinPoint.proceed(params); | |||||
| } finally { | |||||
| // 模拟进行验证 | |||||
| BaseService.removeContext(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取方法中声明的注解 | |||||
| * | |||||
| * @param joinPoint 切入参数对象 | |||||
| * @return DataPermissionMethod 方法注解类型 | |||||
| */ | |||||
| public DataPermissionMethod getDeclaredAnnotation(ProceedingJoinPoint joinPoint){ | |||||
| // 获取方法名 | |||||
| String methodName = joinPoint.getSignature().getName(); | |||||
| // 反射获取目标类 | |||||
| Class<?> targetClass = joinPoint.getTarget().getClass(); | |||||
| // 拿到方法对应的参数类型 | |||||
| Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); | |||||
| // 根据类、方法、参数类型(重载)获取到方法的具体信息 | |||||
| Method objMethod = null; | |||||
| try { | |||||
| objMethod = targetClass.getMethod(methodName, parameterTypes); | |||||
| } catch (NoSuchMethodException e) { | |||||
| LogUtil.error(LogEnum.BIZ_DATASET,"获取注解方法参数异常 error:{}",e); | |||||
| } | |||||
| // 拿到方法定义的注解信息 | |||||
| DataPermissionMethod annotation = objMethod.getDeclaredAnnotation(DataPermissionMethod.class); | |||||
| // 返回 | |||||
| return annotation; | |||||
| } | |||||
| } | |||||
| @@ -25,7 +25,7 @@ import java.io.Serializable; | |||||
| /** | /** | ||||
| * @description 镜像基础类DTO | * @description 镜像基础类DTO | ||||
| * @date: 2020-07-14 | |||||
| * @date 2020-07-14 | |||||
| */ | */ | ||||
| @Data | @Data | ||||
| @Accessors(chain = true) | @Accessors(chain = true) | ||||
| @@ -0,0 +1,77 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.base; | |||||
| import org.dubhe.constant.PermissionConstant; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.domain.entity.Role; | |||||
| import org.dubhe.exception.BaseErrorCode; | |||||
| import org.dubhe.exception.BusinessException; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.springframework.util.CollectionUtils; | |||||
| import java.util.List; | |||||
| import java.util.Objects; | |||||
| import java.util.stream.Collectors; | |||||
| /** | |||||
| * @description 服务层基础数据公共方法类 | |||||
| * @date 2020-03-27 | |||||
| */ | |||||
| public class BaseService { | |||||
| private BaseService (){} | |||||
| /** | |||||
| * 校验是否具有管理员权限 | |||||
| */ | |||||
| public static void checkAdminPermission() { | |||||
| if(!isAdmin()){ | |||||
| throw new BusinessException(BaseErrorCode.DATASET_ADMIN_PERMISSION_ERROR); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 校验是否是管理管理员 | |||||
| * | |||||
| * @return 校验标识 | |||||
| */ | |||||
| public static Boolean isAdmin() { | |||||
| UserDTO currentUserDto = JwtUtils.getCurrentUserDto(); | |||||
| if (currentUserDto != null && !CollectionUtils.isEmpty(currentUserDto.getRoles())) { | |||||
| List<Role> roles = currentUserDto.getRoles(); | |||||
| List<Role> roleList = roles.stream(). | |||||
| filter(a -> a.getId().compareTo(PermissionConstant.ADMIN_USER_ID) == 0) | |||||
| .collect(Collectors.toList()); | |||||
| if (!CollectionUtils.isEmpty(roleList)) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * 清除本地线程数据权限数据 | |||||
| */ | |||||
| public static void removeContext(){ | |||||
| if( !Objects.isNull(DataContext.get())){ | |||||
| DataContext.remove(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -24,8 +24,8 @@ import java.io.Serializable; | |||||
| import java.sql.Timestamp; | import java.sql.Timestamp; | ||||
| /** | /** | ||||
| * @description: VO基础类 | |||||
| * @date: 2020-05-22 | |||||
| * @description VO基础类 | |||||
| * @date 2020-05-22 | |||||
| */ | */ | ||||
| @Data | @Data | ||||
| public class BaseVO implements Serializable { | public class BaseVO implements Serializable { | ||||
| @@ -0,0 +1,62 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.base; | |||||
| import org.dubhe.domain.dto.CommonPermissionDataDTO; | |||||
| /** | |||||
| * @description 共享上下文数据集信息 | |||||
| * @date 2020-04-10 | |||||
| */ | |||||
| public class DataContext { | |||||
| /** | |||||
| * 私有化构造参数 | |||||
| */ | |||||
| private DataContext() { | |||||
| } | |||||
| private static final ThreadLocal<CommonPermissionDataDTO> CONTEXT = new ThreadLocal<>(); | |||||
| /** | |||||
| * 存放数据集信息 | |||||
| * | |||||
| * @param datasetVO | |||||
| */ | |||||
| public static void set(CommonPermissionDataDTO datasetVO) { | |||||
| CONTEXT.set(datasetVO); | |||||
| } | |||||
| /** | |||||
| * 获取用户信息 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public static CommonPermissionDataDTO get() { | |||||
| return CONTEXT.get(); | |||||
| } | |||||
| /** | |||||
| * 清除当前线程内引用,防止内存泄漏 | |||||
| */ | |||||
| public static void remove() { | |||||
| CONTEXT.remove(); | |||||
| } | |||||
| } | |||||
| @@ -18,7 +18,8 @@ | |||||
| package org.dubhe.base; | package org.dubhe.base; | ||||
| /** | /** | ||||
| * @date: 2020-05-14 | |||||
| * @description 常用常量类 | |||||
| * @date 2020-05-14 | |||||
| */ | */ | ||||
| public final class MagicNumConstant { | public final class MagicNumConstant { | ||||
| @@ -86,6 +87,7 @@ public final class MagicNumConstant { | |||||
| public static final long TWELVE_LONG = 12L; | public static final long TWELVE_LONG = 12L; | ||||
| public static final long SIXTY_LONG = 60L; | public static final long SIXTY_LONG = 60L; | ||||
| public static final long THOUSAND_LONG = 1000L; | |||||
| public static final long TEN_THOUSAND_LONG = 10000L; | public static final long TEN_THOUSAND_LONG = 10000L; | ||||
| public static final long ONE_ZERO_ONE_ZERO_ONE_ZERO_LONG = 101010L; | public static final long ONE_ZERO_ONE_ZERO_ONE_ZERO_LONG = 101010L; | ||||
| public static final long NINE_ZERO_NINE_ZERO_NINE_ZERO_LONG = 909090L; | public static final long NINE_ZERO_NINE_ZERO_NINE_ZERO_LONG = 909090L; | ||||
| @@ -26,8 +26,8 @@ import org.dubhe.constant.NumberConstant; | |||||
| import javax.validation.constraints.Min; | import javax.validation.constraints.Min; | ||||
| /** | /** | ||||
| * @description: 分页基类 | |||||
| * @date: 2020-05-8 | |||||
| * @description 分页基类 | |||||
| * @date 2020-05-08 | |||||
| */ | */ | ||||
| @Data | @Data | ||||
| @Accessors(chain = true) | @Accessors(chain = true) | ||||
| @@ -14,32 +14,31 @@ | |||||
| * limitations under the License. | * limitations under the License. | ||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.task; | |||||
| package org.dubhe.base; | |||||
| import org.dubhe.enums.LogEnum; | import org.dubhe.enums.LogEnum; | ||||
| import org.dubhe.service.PtImageService; | |||||
| import org.dubhe.utils.LogUtil; | import org.dubhe.utils.LogUtil; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.scheduling.annotation.Scheduled; | |||||
| import org.springframework.stereotype.Component; | |||||
| /** | /** | ||||
| * @description 从harbor同步projectName | |||||
| * @date 2020-6-23 | |||||
| **/ | |||||
| @Component | |||||
| public class HarborProjectNameSyncTask { | |||||
| * @description 定时任务处理器, 主要做日志标识 | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| public class ScheduleTaskHandler { | |||||
| public static void process(Handler handler) { | |||||
| LogUtil.startScheduleTrace(); | |||||
| try { | |||||
| handler.run(); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.BIZ_SYS, "There is something wrong in schedule task handler :{}", e); | |||||
| } finally { | |||||
| LogUtil.cleanTrace(); | |||||
| } | |||||
| } | |||||
| @Autowired | |||||
| private PtImageService ptImageService; | |||||
| /** | |||||
| * 每天晚上11点开始同步 | |||||
| **/ | |||||
| @Scheduled(cron = "0 0 23 * * ?") | |||||
| public void syncProjectName() { | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "开始到harbor同步projectName到harbor_project表。。。。。"); | |||||
| ptImageService.harborImageNameSync(); | |||||
| public interface Handler { | |||||
| void run(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,65 +0,0 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.config; | |||||
| import com.google.code.kaptcha.impl.DefaultKaptcha; | |||||
| import com.google.code.kaptcha.util.Config; | |||||
| import org.springframework.context.annotation.Bean; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.util.Properties; | |||||
| /** | |||||
| * @description 验证码配置 | |||||
| * @date 2020-02-23 | |||||
| */ | |||||
| @Component | |||||
| public class KaptchaConfig { | |||||
| private final static String CODE_LENGTH = "4"; | |||||
| private final static String SESSION_KEY = "verification_session_key"; | |||||
| @Bean | |||||
| public DefaultKaptcha defaultKaptcha() { | |||||
| DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); | |||||
| Properties properties = new Properties(); | |||||
| // 设置边框 | |||||
| properties.setProperty("kaptcha.border", "yes"); | |||||
| // 设置边框颜色 | |||||
| properties.setProperty("kaptcha.border.color", "105,179,90"); | |||||
| // 设置字体颜色 | |||||
| properties.setProperty("kaptcha.textproducer.font.color", "blue"); | |||||
| // 设置图片宽度 | |||||
| properties.setProperty("kaptcha.image.width", "108"); | |||||
| // 设置图片高度 | |||||
| properties.setProperty("kaptcha.image.height", "28"); | |||||
| // 设置字体尺寸 | |||||
| properties.setProperty("kaptcha.textproducer.font.size", "26"); | |||||
| // 设置session key | |||||
| properties.setProperty("kaptcha.session.key", SESSION_KEY); | |||||
| // 设置验证码长度 | |||||
| properties.setProperty("kaptcha.textproducer.char.length", CODE_LENGTH); | |||||
| // 设置字体 | |||||
| properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,黑体"); | |||||
| //去噪点 | |||||
| properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise"); | |||||
| Config config = new Config(properties); | |||||
| defaultKaptcha.setConfig(config); | |||||
| return defaultKaptcha; | |||||
| } | |||||
| } | |||||
| @@ -1,12 +1,12 @@ | |||||
| /** | /** | ||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | * Copyright 2020 Zhejiang Lab. All Rights Reserved. | ||||
| * | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| * you may not use this file except in compliance with the License. | * you may not use this file except in compliance with the License. | ||||
| * You may obtain a copy of the License at | * You may obtain a copy of the License at | ||||
| * | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | * http://www.apache.org/licenses/LICENSE-2.0 | ||||
| * | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | * Unless required by applicable law or agreed to in writing, software | ||||
| * distributed under the License is distributed on an "AS IS" BASIS, | * distributed under the License is distributed on an "AS IS" BASIS, | ||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| @@ -30,21 +30,17 @@ import java.util.Objects; | |||||
| /** | /** | ||||
| * @description 处理新增和更新的基础数据填充,配合BaseEntity和MyBatisPlusConfig使用 | * @description 处理新增和更新的基础数据填充,配合BaseEntity和MyBatisPlusConfig使用 | ||||
| * @date 2020-6-10 | |||||
| * @date 2020-06-10 | |||||
| */ | */ | ||||
| @Component | @Component | ||||
| public class MetaHandlerConfig implements MetaObjectHandler { | public class MetaHandlerConfig implements MetaObjectHandler { | ||||
| private final String LOCK_USER_ID = "LOCK_USER_ID"; | |||||
| /** | /** | ||||
| * 新增数据执行 | * 新增数据执行 | ||||
| * | * | ||||
| * @param metaObject | |||||
| * @param metaObject 基础数据 | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public void insertFill(MetaObject metaObject) { | public void insertFill(MetaObject metaObject) { | ||||
| if (Objects.isNull(getFieldValByName(StringConstant.CREATE_TIME, metaObject))) { | if (Objects.isNull(getFieldValByName(StringConstant.CREATE_TIME, metaObject))) { | ||||
| @@ -53,13 +49,14 @@ public class MetaHandlerConfig implements MetaObjectHandler { | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.UPDATE_TIME, metaObject))) { | if (Objects.isNull(getFieldValByName(StringConstant.UPDATE_TIME, metaObject))) { | ||||
| this.setFieldValByName(StringConstant.UPDATE_TIME, DateUtil.getCurrentTimestamp(), metaObject); | this.setFieldValByName(StringConstant.UPDATE_TIME, DateUtil.getCurrentTimestamp(), metaObject); | ||||
| } | } | ||||
| synchronized (LOCK_USER_ID){ | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.UPDATE_USER_ID, metaObject))) { | |||||
| this.setFieldValByName(StringConstant.UPDATE_USER_ID, getUserId(), metaObject); | |||||
| } | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.CREATE_USER_ID, metaObject))) { | |||||
| this.setFieldValByName(StringConstant.CREATE_USER_ID, getUserId(), metaObject); | |||||
| } | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.UPDATE_USER_ID, metaObject))) { | |||||
| this.setFieldValByName(StringConstant.UPDATE_USER_ID, getUserId(), metaObject); | |||||
| } | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.CREATE_USER_ID, metaObject))) { | |||||
| this.setFieldValByName(StringConstant.CREATE_USER_ID, getUserId(), metaObject); | |||||
| } | |||||
| if (Objects.isNull(getFieldValByName(StringConstant.ORIGIN_USER_ID, metaObject))) { | |||||
| this.setFieldValByName(StringConstant.ORIGIN_USER_ID, getUserId(), metaObject); | |||||
| } | } | ||||
| if (Objects.isNull(getFieldValByName(StringConstant.DELETED, metaObject))) { | if (Objects.isNull(getFieldValByName(StringConstant.DELETED, metaObject))) { | ||||
| this.setFieldValByName(StringConstant.DELETED, SwitchEnum.getBooleanValue(SwitchEnum.OFF.getValue()), metaObject); | this.setFieldValByName(StringConstant.DELETED, SwitchEnum.getBooleanValue(SwitchEnum.OFF.getValue()), metaObject); | ||||
| @@ -69,7 +66,7 @@ public class MetaHandlerConfig implements MetaObjectHandler { | |||||
| /** | /** | ||||
| * 更新数据执行 | * 更新数据执行 | ||||
| * | * | ||||
| * @param metaObject | |||||
| * @param metaObject 基础数据 | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public void updateFill(MetaObject metaObject) { | public void updateFill(MetaObject metaObject) { | ||||
| @@ -17,294 +17,27 @@ | |||||
| package org.dubhe.config; | package org.dubhe.config; | ||||
| import com.baomidou.mybatisplus.core.override.MybatisMapperProxy; | |||||
| import com.baomidou.mybatisplus.core.parser.ISqlParser; | |||||
| import com.baomidou.mybatisplus.core.parser.SqlParserHelper; | |||||
| import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; | |||||
| import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler; | |||||
| import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser; | |||||
| import com.google.common.collect.Sets; | |||||
| import net.sf.jsqlparser.expression.Expression; | |||||
| import net.sf.jsqlparser.expression.LongValue; | |||||
| import net.sf.jsqlparser.expression.operators.relational.ExpressionList; | |||||
| import net.sf.jsqlparser.expression.operators.relational.InExpression; | |||||
| import net.sf.jsqlparser.schema.Column; | |||||
| import org.apache.ibatis.mapping.MappedStatement; | |||||
| import org.apache.shiro.UnavailableSecurityManagerException; | |||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.base.MagicNumConstant; | |||||
| import org.dubhe.constant.PermissionConstant; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.domain.entity.Role; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.springframework.beans.BeansException; | |||||
| import org.springframework.context.ApplicationContext; | |||||
| import org.springframework.context.ApplicationContextAware; | |||||
| import org.springframework.context.ApplicationListener; | |||||
| import org.dubhe.interceptor.PaginationInterceptor; | |||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.event.ContextRefreshedEvent; | |||||
| import org.springframework.core.annotation.AnnotationUtils; | |||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
| import org.springframework.util.CollectionUtils; | |||||
| import java.lang.annotation.Annotation; | |||||
| import java.lang.reflect.Field; | |||||
| import java.lang.reflect.Method; | |||||
| import java.lang.reflect.Proxy; | |||||
| import java.util.*; | |||||
| import java.util.stream.Collectors; | |||||
| /** | /** | ||||
| * @description MybatisPlus配置类 | |||||
| * @date 2020-06-24 | |||||
| * @description mybatis plus拦截器 | |||||
| * @date 2020-06-10 | |||||
| */ | */ | ||||
| @EnableTransactionManagement | @EnableTransactionManagement | ||||
| @Configuration | @Configuration | ||||
| public class MybatisPlusConfig implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware { | |||||
| /** | |||||
| * 以此字段作为租户实现数据隔离 | |||||
| */ | |||||
| private static final String TENANT_ID_COLUMN = "create_user_id"; | |||||
| /** | |||||
| * 以0作为公共数据的标识 | |||||
| */ | |||||
| private static final long PUBLIC_TENANT_ID = MagicNumConstant.ZERO; | |||||
| private static final Set<Long> PUBLIC_TENANT_ID_SET = new HashSet<Long>() {{ | |||||
| add(PUBLIC_TENANT_ID); | |||||
| }}; | |||||
| private static final String PACKAGE_SEPARATOR = "."; | |||||
| private static final Set<String> SELECT_PERMISSION = new HashSet<String>() {{ | |||||
| add(PermissionConstant.SELECT); | |||||
| }}; | |||||
| private static final Set<String> UPDATE_DELETE_PERMISSION = new HashSet<String>() {{ | |||||
| add(PermissionConstant.UPDATE); | |||||
| add(PermissionConstant.DELETE); | |||||
| }}; | |||||
| public class MybatisPlusConfig { | |||||
| private static final String SELECT_STR = "select"; | |||||
| /** | /** | ||||
| * 优先级高于dataFilters,如果ignore,则不进行sql注入 | |||||
| */ | |||||
| private Map<String, Set<String>> dataFilters = new HashMap<>(); | |||||
| private ApplicationContext applicationContext; | |||||
| public Set<Long> tenantId; | |||||
| /** | |||||
| * mybatis plus 分页插件 | |||||
| * 其中增加了通过多租户实现了数据权限功能 | |||||
| * 注入 MybatisPlus 分页拦截器 | |||||
| * | * | ||||
| * @return | |||||
| * @return 自定义MybatisPlus分页拦截器 | |||||
| */ | */ | ||||
| @Bean | @Bean | ||||
| public PaginationInterceptor paginationInterceptor() { | public PaginationInterceptor paginationInterceptor() { | ||||
| PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); | PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); | ||||
| List<ISqlParser> sqlParserList = new ArrayList<>(); | |||||
| TenantSqlParser tenantSqlParser = new TenantSqlParser(); | |||||
| tenantSqlParser.setTenantHandler(new TenantHandler() { | |||||
| @Override | |||||
| public Expression getTenantId(boolean where) { | |||||
| Set<Long> tenants = tenantId; | |||||
| final boolean multipleTenantIds = tenants.size() > MagicNumConstant.ONE; | |||||
| if (multipleTenantIds) { | |||||
| return multipleTenantIdCondition(tenants); | |||||
| } else { | |||||
| return singleTenantIdCondition(tenants); | |||||
| } | |||||
| } | |||||
| private Expression singleTenantIdCondition(Set<Long> tenants) { | |||||
| return new LongValue((Long) tenants.toArray()[0]); | |||||
| } | |||||
| private Expression multipleTenantIdCondition(Set<Long> tenants) { | |||||
| final InExpression inExpression = new InExpression(); | |||||
| inExpression.setLeftExpression(new Column(getTenantIdColumn())); | |||||
| final ExpressionList itemsList = new ExpressionList(); | |||||
| final List<Expression> inValues = new ArrayList<>(tenants.size()); | |||||
| tenants.forEach(i -> | |||||
| inValues.add(new LongValue(i)) | |||||
| ); | |||||
| itemsList.setExpressions(inValues); | |||||
| inExpression.setRightItemsList(itemsList); | |||||
| return inExpression; | |||||
| } | |||||
| @Override | |||||
| public String getTenantIdColumn() { | |||||
| return TENANT_ID_COLUMN; | |||||
| } | |||||
| @Override | |||||
| public boolean doTableFilter(String tableName) { | |||||
| return false; | |||||
| } | |||||
| }); | |||||
| sqlParserList.add(tenantSqlParser); | |||||
| paginationInterceptor.setSqlParserList(sqlParserList); | |||||
| paginationInterceptor.setSqlParserFilter(metaObject -> { | |||||
| MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject); | |||||
| String method = ms.getId(); | |||||
| if (!dataFilters.containsKey(method) || isAdmin()) { | |||||
| return true; | |||||
| } | |||||
| Set<String> permission = dataFilters.get(method); | |||||
| tenantId = getTenantId(permission); | |||||
| return false; | |||||
| }); | |||||
| return paginationInterceptor; | return paginationInterceptor; | ||||
| } | } | ||||
| /** | |||||
| * 判断用户是否是管理员 | |||||
| * 如果未登录,无法请求任何接口,所以不会到该层,因此匿名认为是定时任务,给予admin权限。 | |||||
| * | |||||
| * @return 判断用户是否是管理员 | |||||
| */ | |||||
| private boolean isAdmin() { | |||||
| UserDTO user; | |||||
| try { | |||||
| user = JwtUtils.getCurrentUserDto(); | |||||
| } catch (UnavailableSecurityManagerException e) { | |||||
| return true; | |||||
| } | |||||
| if (Objects.isNull(user)) { | |||||
| return true; | |||||
| } | |||||
| List<Role> roles; | |||||
| if ((roles = user.getRoles()) == null) { | |||||
| return false; | |||||
| } | |||||
| Set<String> permissions = roles.stream().map(Role::getPermission).collect(Collectors.toSet()); | |||||
| if (CollectionUtils.isEmpty(permissions)) { | |||||
| return false; | |||||
| } | |||||
| return user.getId() == PermissionConstant.ANONYMOUS_USER || user.getId() == PermissionConstant.ADMIN_USER_ID; | |||||
| } | |||||
| /** | |||||
| * 如果是管理员,在前一步isAdmin已过滤; | |||||
| * 如果是匿名用户,在shiro层被过滤; | |||||
| * 因此只会是无角色、权限用户或普通用户 | |||||
| * | |||||
| * @return Set<Long> 租户ID集合 | |||||
| */ | |||||
| private Set<Long> getTenantId(Set<String> permission) { | |||||
| UserDTO user = JwtUtils.getCurrentUserDto(); | |||||
| List<Role> roles; | |||||
| if (Objects.isNull(user) || (roles = user.getRoles()) == null) { | |||||
| if (permission.contains(PermissionConstant.SELECT)) { | |||||
| return PUBLIC_TENANT_ID_SET; | |||||
| } | |||||
| return Collections.EMPTY_SET; | |||||
| } | |||||
| Set<String> permissions = roles.stream().map(Role::getPermission).collect(Collectors.toSet()); | |||||
| if (CollectionUtils.isEmpty(permissions)) { | |||||
| if (permission.contains(PermissionConstant.SELECT)) { | |||||
| return PUBLIC_TENANT_ID_SET; | |||||
| } | |||||
| return Collections.EMPTY_SET; | |||||
| } | |||||
| if (permission.contains(PermissionConstant.SELECT)) { | |||||
| return new HashSet<Long>() {{ | |||||
| add(PUBLIC_TENANT_ID); | |||||
| add(user.getId()); | |||||
| }}; | |||||
| } | |||||
| return new HashSet<Long>() {{ | |||||
| add(user.getId()); | |||||
| }}; | |||||
| } | |||||
| /** | |||||
| * 设置上下文 | |||||
| * #需要通过上下文 获取SpringBean | |||||
| * | |||||
| * @param applicationContext spring上下文 | |||||
| * @throws BeansException 找不到bean异常 | |||||
| */ | |||||
| @Override | |||||
| public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | |||||
| this.applicationContext = applicationContext; | |||||
| } | |||||
| @Override | |||||
| public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { | |||||
| Class<? extends Annotation> annotationClass = DataPermission.class; | |||||
| Map<String, Object> beanWithAnnotation = applicationContext.getBeansWithAnnotation(annotationClass); | |||||
| Set<Map.Entry<String, Object>> entitySet = beanWithAnnotation.entrySet(); | |||||
| for (Map.Entry<String, Object> entry : entitySet) { | |||||
| Proxy proxy = (Proxy) entry.getValue(); | |||||
| Class clazz = getMapperClass(proxy); | |||||
| populateDataFilters(clazz); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 根据mapper对应代理对象获取Class | |||||
| * | |||||
| * @param proxy mapper对应代理对象 | |||||
| * @return | |||||
| */ | |||||
| private Class getMapperClass(Proxy proxy) { | |||||
| try { | |||||
| Field field = proxy.getClass().getSuperclass().getDeclaredField("h"); | |||||
| field.setAccessible(true); | |||||
| MybatisMapperProxy mapperProxy = (MybatisMapperProxy) field.get(proxy); | |||||
| field = mapperProxy.getClass().getDeclaredField("mapperInterface"); | |||||
| field.setAccessible(true); | |||||
| return (Class) field.get(mapperProxy); | |||||
| } catch (NoSuchFieldException | IllegalAccessException e) { | |||||
| LogUtil.error(LogEnum.BIZ_DATASET, "reflect error", e); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * 填充数据权限过滤,处理那些需要排除的方法 | |||||
| * | |||||
| * @param clazz 需要处理的类(mapper) | |||||
| */ | |||||
| private void populateDataFilters(Class clazz) { | |||||
| if (clazz == null) { | |||||
| return; | |||||
| } | |||||
| Method[] methods = clazz.getMethods(); | |||||
| DataPermission dataPermission = AnnotationUtils.findAnnotation((Class<?>) clazz, DataPermission.class); | |||||
| Set<String> ignores = Sets.newHashSet(dataPermission.ignores()); | |||||
| for (Method method : methods) { | |||||
| if (ignores.contains(method.getName())) { | |||||
| continue; | |||||
| } | |||||
| Set<String> permission = getDataPermission(method); | |||||
| dataFilters.put(clazz.getName() + PACKAGE_SEPARATOR + method.getName(), permission); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取方法上权限注解 | |||||
| * 权限注解包含 | |||||
| * 1.用户拥有指定权限才可以执行该方法:比如 PermissionConstant.SELECT 表示用户必须拥有select权限,才可以使用该方法 | |||||
| * 2.方法权限校验排除:比如 ignores = {"insert"} 表示insert方法不做权限处理 | |||||
| * | |||||
| * @param method 方法对象 | |||||
| * @return | |||||
| */ | |||||
| private Set<String> getDataPermission(Method method) { | |||||
| DataPermission dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class); | |||||
| // 无注解时以方法名判断 | |||||
| if (dataPermission == null) { | |||||
| if (method.getName().contains(SELECT_STR)) { | |||||
| return SELECT_PERMISSION; | |||||
| } | |||||
| return UPDATE_DELETE_PERMISSION; | |||||
| } | |||||
| return Sets.newHashSet(dataPermission.permission()); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,62 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.config; | |||||
| import lombok.Data; | |||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||||
| import org.springframework.stereotype.Component; | |||||
| /** | |||||
| * @description 垃圾回收机制配置常量 | |||||
| * @date 2020-09-21 | |||||
| */ | |||||
| @Data | |||||
| @Component | |||||
| @ConfigurationProperties(prefix = "recycle.timeout") | |||||
| public class RecycleConfig { | |||||
| /** | |||||
| * 回收无效文件的默认有效时长 | |||||
| */ | |||||
| private Integer date; | |||||
| /** | |||||
| * 用户上传文件至临时路径下后文件最大有效时长,以小时为单位 | |||||
| */ | |||||
| private Integer fileValid; | |||||
| /** | |||||
| * 用户删除某一算法后,其算法文件最大有效时长,以天为单位 | |||||
| */ | |||||
| private Integer algorithmValid; | |||||
| /** | |||||
| * 用户删除某一模型后,其模型文件最大有效时长,以天为单位 | |||||
| */ | |||||
| private Integer modelValid; | |||||
| /** | |||||
| * 用户删除训练任务后,其训练管理文件最大有效时长,以天为单位 | |||||
| */ | |||||
| private Integer trainValid; | |||||
| /** | |||||
| * 删除服务器无效文件(大文件) | |||||
| * 示例:rsync --delete-before -d /空目录 /需要回收的源目录 | |||||
| */ | |||||
| public static final String DEL_COMMAND = "ssh %s@%s \"mkdir -p %s; rsync --delete-before -d %s %s; rmdir %s %s\""; | |||||
| } | |||||
| @@ -15,77 +15,71 @@ | |||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.constant; | |||||
| package org.dubhe.config; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import org.springframework.beans.factory.annotation.Value; | |||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| /** | /** | ||||
| * @description 训练常量 | * @description 训练常量 | ||||
| * @create: 2020-05-12 | |||||
| * @date 2020-05-12 | |||||
| */ | */ | ||||
| @Component | @Component | ||||
| @Data | @Data | ||||
| public class TrainJobConstant { | |||||
| @ConfigurationProperties(prefix = "train-job") | |||||
| public class TrainJobConfig { | |||||
| @Value("${train-job.namespace}") | |||||
| private String namespace; | private String namespace; | ||||
| @Value("${train-job.version-label}") | |||||
| private String versionLabel; | private String versionLabel; | ||||
| @Value("${train-job.separator}") | |||||
| private String separator; | private String separator; | ||||
| @Value("${train-job.pod-name}") | |||||
| private String podName; | private String podName; | ||||
| @Value("${train-job.python-format}") | |||||
| private String pythonFormat; | private String pythonFormat; | ||||
| @Value("${train-job.manage}") | |||||
| private String manage; | private String manage; | ||||
| @Value("${train-job.out-path}") | |||||
| private String outPath; | private String outPath; | ||||
| @Value("${train-job.log-path}") | |||||
| private String logPath; | private String logPath; | ||||
| @Value("${train-job.visualized-log-path}") | |||||
| private String visualizedLogPath; | private String visualizedLogPath; | ||||
| @Value("${train-job.docker-dataset-path}") | |||||
| private String dockerDatasetPath; | private String dockerDatasetPath; | ||||
| @Value("${train-job.docker-train-path}") | |||||
| private String dockerTrainPath; | private String dockerTrainPath; | ||||
| @Value("${train-job.docker-out-path}") | |||||
| private String dockerOutPath; | private String dockerOutPath; | ||||
| @Value("${train-job.docker-log-path}") | |||||
| private String dockerLogPath; | private String dockerLogPath; | ||||
| @Value("${train-job.docker-dataset}") | |||||
| private String dockerDataset; | private String dockerDataset; | ||||
| @Value("${train-job.docker-visualized-log-path}") | |||||
| private String dockerModelPath; | |||||
| private String dockerValDatasetPath; | |||||
| private String loadValDatasetKey; | |||||
| private String dockerVisualizedLogPath; | private String dockerVisualizedLogPath; | ||||
| @Value("${train-job.load-path}") | |||||
| private String loadPath; | private String loadPath; | ||||
| @Value("${train-job.load-key}") | |||||
| private String loadKey; | private String loadKey; | ||||
| @Value("${train-job.eight}") | |||||
| private String eight; | private String eight; | ||||
| @Value("${train-job.plus-eight}") | |||||
| private String plusEight; | private String plusEight; | ||||
| private String nodeIps; | |||||
| private String nodeNum; | |||||
| private String gpuNumPerNode; | |||||
| public static final String TRAIN_ID = "trainId"; | public static final String TRAIN_ID = "trainId"; | ||||
| public static final String TRAIN_VERSION = "trainVersion"; | public static final String TRAIN_VERSION = "trainVersion"; | ||||
| @@ -97,4 +91,5 @@ public class TrainJobConstant { | |||||
| public static final String CREATE_TIME = "createTime"; | public static final String CREATE_TIME = "createTime"; | ||||
| public static final String ALGORITHM_NAME = "algorithmName"; | public static final String ALGORITHM_NAME = "algorithmName"; | ||||
| } | } | ||||
| @@ -16,9 +16,13 @@ | |||||
| */ | */ | ||||
| package org.dubhe.config; | package org.dubhe.config; | ||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | |||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | |||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
| import java.util.concurrent.Executor; | import java.util.concurrent.Executor; | ||||
| @@ -29,7 +33,7 @@ import java.util.concurrent.ThreadPoolExecutor; | |||||
| * @date 2020-07-17 | * @date 2020-07-17 | ||||
| */ | */ | ||||
| @Configuration | @Configuration | ||||
| public class TrainPoolConfig { | |||||
| public class TrainPoolConfig implements AsyncConfigurer { | |||||
| @Value("${basepool.corePoolSize:40}") | @Value("${basepool.corePoolSize:40}") | ||||
| private Integer corePoolSize; | private Integer corePoolSize; | ||||
| @@ -44,8 +48,9 @@ public class TrainPoolConfig { | |||||
| * 训练任务异步处理线程池 | * 训练任务异步处理线程池 | ||||
| * @return Executor 线程实例 | * @return Executor 线程实例 | ||||
| */ | */ | ||||
| @Bean | |||||
| public Executor trainJobAsyncExecutor() { | |||||
| @Bean("trainExecutor") | |||||
| @Override | |||||
| public Executor getAsyncExecutor() { | |||||
| ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); | ||||
| //核心线程数 | //核心线程数 | ||||
| taskExecutor.setCorePoolSize(corePoolSize); | taskExecutor.setCorePoolSize(corePoolSize); | ||||
| @@ -57,10 +62,18 @@ public class TrainPoolConfig { | |||||
| //配置队列大小 | //配置队列大小 | ||||
| taskExecutor.setQueueCapacity(blockQueueSize); | taskExecutor.setQueueCapacity(blockQueueSize); | ||||
| //配置线程池前缀 | //配置线程池前缀 | ||||
| taskExecutor.setThreadNamePrefix("async-train-job-"); | |||||
| taskExecutor.setThreadNamePrefix("async-train-"); | |||||
| //拒绝策略 | //拒绝策略 | ||||
| taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); | taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); | ||||
| taskExecutor.initialize(); | taskExecutor.initialize(); | ||||
| return taskExecutor; | return taskExecutor; | ||||
| } | } | ||||
| @Override | |||||
| public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "开始捕获训练管理异步任务异常信息-----》》》"); | |||||
| return (ex, method, params) -> { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "训练管理方法名{}的异步任务执行失败,参数信息:{},异常信息:{}", method.getName(), params, ex); | |||||
| }; | |||||
| } | |||||
| } | } | ||||
| @@ -18,8 +18,8 @@ | |||||
| package org.dubhe.constant; | package org.dubhe.constant; | ||||
| /** | /** | ||||
| * @Description 数字常量 | |||||
| * @Date 2020-6-9 | |||||
| * @description 数字常量 | |||||
| * @date 2020-06-09 | |||||
| */ | */ | ||||
| public class NumberConstant { | public class NumberConstant { | ||||
| @@ -32,6 +32,8 @@ public class NumberConstant { | |||||
| public final static int NUMBER_30 = 30; | public final static int NUMBER_30 = 30; | ||||
| public final static int NUMBER_50 = 50; | public final static int NUMBER_50 = 50; | ||||
| public final static int NUMBER_60 = 60; | public final static int NUMBER_60 = 60; | ||||
| public final static int NUMBER_1024 = 1024; | |||||
| public final static int NUMBER_1000 = 1000; | |||||
| public final static int HOUR_SECOND = 60 * 60; | public final static int HOUR_SECOND = 60 * 60; | ||||
| public final static int DAY_SECOND = 60 * 60 * 24; | public final static int DAY_SECOND = 60 * 60 * 24; | ||||
| public final static int WEEK_SECOND = 60 * 60 * 24 * 7; | public final static int WEEK_SECOND = 60 * 60 * 24 * 7; | ||||
| @@ -21,8 +21,8 @@ import lombok.Data; | |||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| /** | /** | ||||
| * @description: 权限常量 | |||||
| * @since: 2020-05-25 14:39 | |||||
| * @description 权限常量 | |||||
| * @date 2020-05-25 | |||||
| */ | */ | ||||
| @Component | @Component | ||||
| @Data | @Data | ||||
| @@ -32,9 +32,10 @@ public class PermissionConstant { | |||||
| * 超级用户 | * 超级用户 | ||||
| */ | */ | ||||
| public static final long ADMIN_USER_ID = 1L; | public static final long ADMIN_USER_ID = 1L; | ||||
| public static final long ANONYMOUS_USER = -1L; | |||||
| public static final String SELECT = "select"; | |||||
| public static final String UPDATE = "update"; | |||||
| public static final String DELETE = "delete"; | |||||
| /** | |||||
| * 数据集模块类型 | |||||
| */ | |||||
| public static final Integer RESOURCE_DATA_MODEL = 1; | |||||
| } | } | ||||
| @@ -23,24 +23,29 @@ package org.dubhe.constant; | |||||
| */ | */ | ||||
| public final class StringConstant { | public final class StringConstant { | ||||
| public static final String MSIE = "MSIE"; | |||||
| public static final String MOZILLA = "Mozilla"; | |||||
| public static final String MSIE = "MSIE"; | |||||
| public static final String MOZILLA = "Mozilla"; | |||||
| public static final String REQUEST_METHOD_GET = "GET"; | |||||
| /** | |||||
| * 公共字段 | |||||
| */ | |||||
| public static final String CREATE_TIME = "createTime"; | |||||
| public static final String UPDATE_TIME = "updateTime"; | |||||
| public static final String UPDATE_USER_ID = "updateUserId"; | |||||
| public static final String CREATE_USER_ID = "createUserId"; | |||||
| public static final String DELETED = "deleted"; | |||||
| public static final String UTF8 = "utf-8"; | |||||
| /** | |||||
| * 公共字段 | |||||
| */ | |||||
| public static final String CREATE_TIME = "createTime"; | |||||
| public static final String UPDATE_TIME = "updateTime"; | |||||
| public static final String UPDATE_USER_ID = "updateUserId"; | |||||
| public static final String CREATE_USER_ID = "createUserId"; | |||||
| public static final String ORIGIN_USER_ID = "originUserId"; | |||||
| public static final String DELETED = "deleted"; | |||||
| public static final String UTF8 = "utf-8"; | |||||
| public static final String JSON_REQUEST = "application/json"; | |||||
| public static final String K8S_CALLBACK_URI = "/api/k8s/callback/pod"; | |||||
| public static final String MULTIPART = "multipart/form-data"; | |||||
| /** | |||||
| * 测试环境 | |||||
| */ | |||||
| public static final String PROFILE_ACTIVE_TEST = "test"; | |||||
| /** | |||||
| * 测试环境 | |||||
| */ | |||||
| public static final String PROFILE_ACTIVE_TEST = "test"; | |||||
| private StringConstant() { | |||||
| } | |||||
| private StringConstant() { | |||||
| } | |||||
| } | } | ||||
| @@ -19,7 +19,7 @@ package org.dubhe.constant; | |||||
| /** | /** | ||||
| * @description 符号常量 | * @description 符号常量 | ||||
| * @Date 2020-5-29 | |||||
| * @date 2020-5-29 | |||||
| */ | */ | ||||
| public class SymbolConstant { | public class SymbolConstant { | ||||
| public static final String SLASH = "/"; | public static final String SLASH = "/"; | ||||
| @@ -40,6 +40,8 @@ public class SymbolConstant { | |||||
| public static final String DOUBLE_MARK= "\"\""; | public static final String DOUBLE_MARK= "\"\""; | ||||
| public static final String MARK= "\""; | public static final String MARK= "\""; | ||||
| public static final String FLAG_EQUAL = "="; | |||||
| private SymbolConstant() { | private SymbolConstant() { | ||||
| } | } | ||||
| @@ -17,15 +17,12 @@ | |||||
| package org.dubhe.constant; | package org.dubhe.constant; | ||||
| import org.springframework.stereotype.Component; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| /** | /** | ||||
| * @description 算法用途 | * @description 算法用途 | ||||
| * @date: 2020-06-23 | |||||
| * @date 2020-06-23 | |||||
| */ | */ | ||||
| @Component | |||||
| @Data | @Data | ||||
| public class UserAuxiliaryInfoConstant { | public class UserAuxiliaryInfoConstant { | ||||
| @@ -0,0 +1,52 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.domain.dto; | |||||
| import lombok.AllArgsConstructor; | |||||
| import lombok.Builder; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.Set; | |||||
| /** | |||||
| * @description 公共权限信息DTO | |||||
| * @date 2020-09-24 | |||||
| */ | |||||
| @AllArgsConstructor | |||||
| @NoArgsConstructor | |||||
| @Builder | |||||
| @Data | |||||
| public class CommonPermissionDataDTO implements Serializable { | |||||
| /** | |||||
| * 资源拥有者ID | |||||
| */ | |||||
| private Long id; | |||||
| /** | |||||
| * 公共类型 | |||||
| */ | |||||
| private Boolean type; | |||||
| /** | |||||
| * 资源所属用户ids | |||||
| */ | |||||
| private Set<Long> resourceUserIds; | |||||
| } | |||||
| @@ -16,15 +16,14 @@ | |||||
| */ | */ | ||||
| package org.dubhe.domain.entity; | package org.dubhe.domain.entity; | ||||
| import java.io.Serializable; | |||||
| import org.dubhe.base.MagicNumConstant; | |||||
| import com.alibaba.fastjson.annotation.JSONField; | |||||
| import cn.hutool.core.date.DateUtil; | import cn.hutool.core.date.DateUtil; | ||||
| import com.alibaba.fastjson.annotation.JSONField; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
| import org.dubhe.base.MagicNumConstant; | |||||
| import java.io.Serializable; | |||||
| /** | /** | ||||
| * @description 日志对象封装类 | * @description 日志对象封装类 | ||||
| @@ -34,31 +33,27 @@ import lombok.experimental.Accessors; | |||||
| @Accessors(chain = true) | @Accessors(chain = true) | ||||
| public class LogInfo implements Serializable { | public class LogInfo implements Serializable { | ||||
| @JSONField(ordinal = MagicNumConstant.ONE) | |||||
| private String traceId; | |||||
| private static final long serialVersionUID = 5250395474667395607L; | |||||
| @JSONField(ordinal = MagicNumConstant.ONE) | |||||
| private String traceId; | |||||
| @JSONField(ordinal = MagicNumConstant.TWO) | |||||
| private String type; | |||||
| @JSONField(ordinal = MagicNumConstant.TWO) | |||||
| private String type; | |||||
| @JSONField(ordinal = MagicNumConstant.THREE) | |||||
| private String level; | |||||
| @JSONField(ordinal = MagicNumConstant.THREE) | |||||
| private String level; | |||||
| @JSONField(ordinal = MagicNumConstant.FOUR) | |||||
| private String cName; | |||||
| @JSONField(ordinal = MagicNumConstant.FOUR) | |||||
| private String location; | |||||
| @JSONField(ordinal = MagicNumConstant.FIVE) | |||||
| private String mName; | |||||
| @JSONField(ordinal = MagicNumConstant.SIX) | |||||
| private String line; | |||||
| @JSONField(ordinal = MagicNumConstant.SEVEN) | |||||
| private String time = DateUtil.now(); | |||||
| @JSONField(ordinal = MagicNumConstant.FIVE) | |||||
| private String time = DateUtil.now(); | |||||
| @JSONField(ordinal = MagicNumConstant.EIGHT) | |||||
| private Object info; | |||||
| @JSONField(ordinal = MagicNumConstant.SIX) | |||||
| private Object info; | |||||
| public void setInfo(Object info) { | |||||
| this.info = info; | |||||
| } | |||||
| public void setInfo(Object info) { | |||||
| this.info = info; | |||||
| } | |||||
| } | } | ||||
| @@ -22,12 +22,9 @@ import lombok.Builder; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
| import org.dubhe.base.BaseEntity; | import org.dubhe.base.BaseEntity; | ||||
| import org.hibernate.validator.constraints.Length; | |||||
| import javax.validation.constraints.NotBlank; | import javax.validation.constraints.NotBlank; | ||||
| import javax.validation.constraints.NotNull; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| import java.sql.Timestamp; | |||||
| import java.util.Objects; | import java.util.Objects; | ||||
| /** | /** | ||||
| @@ -22,12 +22,7 @@ import lombok.Builder; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
| import org.dubhe.base.BaseEntity; | import org.dubhe.base.BaseEntity; | ||||
| import org.hibernate.validator.constraints.Length; | |||||
| import javax.validation.constraints.NotBlank; | |||||
| import javax.validation.constraints.NotEmpty; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| import java.sql.Timestamp; | |||||
| import java.util.Objects; | import java.util.Objects; | ||||
| import java.util.Set; | import java.util.Set; | ||||
| @@ -0,0 +1,73 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.dto; | |||||
| import lombok.Data; | |||||
| /** | |||||
| * @description 全局请求日志信息 | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| @Data | |||||
| public class GlobalRequestRecordDTO { | |||||
| /** | |||||
| * 客户主机地址 | |||||
| */ | |||||
| private String clientHost; | |||||
| /** | |||||
| * 请求地址 | |||||
| */ | |||||
| private String uri; | |||||
| /** | |||||
| * 授权信息 | |||||
| */ | |||||
| private String authorization; | |||||
| /** | |||||
| * 用户名 | |||||
| */ | |||||
| private String username; | |||||
| /** | |||||
| * form参数 | |||||
| */ | |||||
| private String params; | |||||
| /** | |||||
| * 返回值类型 | |||||
| */ | |||||
| private String contentType; | |||||
| /** | |||||
| * 返回状态 | |||||
| */ | |||||
| private Integer status; | |||||
| /** | |||||
| * 时间耗费 | |||||
| */ | |||||
| private Long timeCost; | |||||
| /** | |||||
| * 请求方式 | |||||
| */ | |||||
| private String method; | |||||
| /** | |||||
| * 请求体body参数 | |||||
| */ | |||||
| private String requestBody; | |||||
| /** | |||||
| * 返回值json数据 | |||||
| */ | |||||
| private String responseBody; | |||||
| } | |||||
| @@ -44,6 +44,14 @@ public class BaseK8sPodCallbackCreateDTO { | |||||
| @NotEmpty(message = "podName 不能为空!") | @NotEmpty(message = "podName 不能为空!") | ||||
| private String podName; | private String podName; | ||||
| @ApiModelProperty(required = true,value = "k8s pod parent type") | |||||
| @NotEmpty(message = "podParentType 不能为空!") | |||||
| private String podParentType; | |||||
| @ApiModelProperty(required = true,value = "k8s pod parent name") | |||||
| @NotEmpty(message = "podParentName 不能为空!") | |||||
| private String podParentName; | |||||
| @ApiModelProperty(value = "k8s pod phase",notes = "对应PodPhaseEnum") | @ApiModelProperty(value = "k8s pod phase",notes = "对应PodPhaseEnum") | ||||
| @NotEmpty(message = "phase 不能为空!") | @NotEmpty(message = "phase 不能为空!") | ||||
| private String phase; | private String phase; | ||||
| @@ -55,10 +63,12 @@ public class BaseK8sPodCallbackCreateDTO { | |||||
| } | } | ||||
| public BaseK8sPodCallbackCreateDTO(String namespace,String resourceName,String podName,String phase,String messages){ | |||||
| public BaseK8sPodCallbackCreateDTO(String namespace,String resourceName,String podName,String podParentType,String podParentName,String phase,String messages){ | |||||
| this.namespace = namespace; | this.namespace = namespace; | ||||
| this.resourceName = resourceName; | this.resourceName = resourceName; | ||||
| this.podName = podName; | this.podName = podName; | ||||
| this.podParentType = podParentType; | |||||
| this.podParentName = podParentName; | |||||
| this.phase = phase; | this.phase = phase; | ||||
| this.messages = messages; | this.messages = messages; | ||||
| } | } | ||||
| @@ -69,6 +79,8 @@ public class BaseK8sPodCallbackCreateDTO { | |||||
| "namespace='" + namespace + '\'' + | "namespace='" + namespace + '\'' + | ||||
| ", resourceName='" + resourceName + '\'' + | ", resourceName='" + resourceName + '\'' + | ||||
| ", podName='" + podName + '\'' + | ", podName='" + podName + '\'' + | ||||
| ", podParentType='" + podParentType + '\'' + | |||||
| ", podParentName='" + podParentName + '\'' + | |||||
| ", phase='" + phase + '\'' + | ", phase='" + phase + '\'' + | ||||
| ", messages=" + messages + | ", messages=" + messages + | ||||
| '}'; | '}'; | ||||
| @@ -23,9 +23,8 @@ import java.util.HashMap; | |||||
| import java.util.Map; | import java.util.Map; | ||||
| /** | /** | ||||
| * @desc: 业务模块 | |||||
| * | |||||
| * @date 2020.05.25 | |||||
| * @description 业务模块 | |||||
| * @date 2020-05-25 | |||||
| */ | */ | ||||
| @Getter | @Getter | ||||
| public enum BizEnum { | public enum BizEnum { | ||||
| @@ -38,6 +37,10 @@ public enum BizEnum { | |||||
| * 算法管理 | * 算法管理 | ||||
| */ | */ | ||||
| ALGORITHM("算法管理","algorithm",1), | ALGORITHM("算法管理","algorithm",1), | ||||
| /** | |||||
| * 模型管理 | |||||
| */ | |||||
| MODEL("模型管理","model",2), | |||||
| ; | ; | ||||
| /** | /** | ||||
| @@ -21,8 +21,8 @@ import java.util.HashMap; | |||||
| import java.util.Map; | import java.util.Map; | ||||
| /** | /** | ||||
| * @desc: 业务NFS路径枚举 | |||||
| * @date 2020.05.13 | |||||
| * @description 业务NFS路径枚举 | |||||
| * @date 2020-05-13 | |||||
| */ | */ | ||||
| public enum BizNfsEnum { | public enum BizNfsEnum { | ||||
| /** | /** | ||||
| @@ -33,6 +33,10 @@ public enum BizNfsEnum { | |||||
| * 算法管理 NFS 路径命名 | * 算法管理 NFS 路径命名 | ||||
| */ | */ | ||||
| ALGORITHM(BizEnum.ALGORITHM, "algorithm-manage"), | ALGORITHM(BizEnum.ALGORITHM, "algorithm-manage"), | ||||
| /** | |||||
| * 模型管理 NFS 路径命名 | |||||
| */ | |||||
| MODEL(BizEnum.MODEL, "model"), | |||||
| ; | ; | ||||
| BizNfsEnum(BizEnum bizEnum, String bizNfsPath) { | BizNfsEnum(BizEnum bizEnum, String bizNfsPath) { | ||||
| @@ -81,11 +85,11 @@ public enum BizNfsEnum { | |||||
| return bizNfsPath; | return bizNfsPath; | ||||
| } | } | ||||
| public BizEnum getBizEnum(){ | |||||
| public BizEnum getBizEnum() { | |||||
| return bizEnum; | return bizEnum; | ||||
| } | } | ||||
| public String getBizCode() { | public String getBizCode() { | ||||
| return bizEnum == null ? null :bizEnum.getBizCode(); | |||||
| return bizEnum == null ? null : bizEnum.getBizCode(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ | |||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.data.constant; | |||||
| package org.dubhe.enums; | |||||
| import lombok.Getter; | import lombok.Getter; | ||||
| @@ -26,36 +26,52 @@ import lombok.Getter; | |||||
| @Getter | @Getter | ||||
| public enum LogEnum { | public enum LogEnum { | ||||
| // 系统报错日志 | |||||
| SYS_ERR, | |||||
| // 用户请求日志 | |||||
| REST_REQ, | |||||
| // 训练模块 | |||||
| BIZ_TRAIN, | |||||
| // 系统模块 | |||||
| BIZ_SYS, | |||||
| // 模型模块 | |||||
| BIZ_MODEL, | |||||
| // 数据集模块 | |||||
| BIZ_DATASET, | |||||
| // k8s模块 | |||||
| BIZ_K8S, | |||||
| //note book | |||||
| NOTE_BOOK, | |||||
| //NFS UTILS | |||||
| NFS_UTIL; | |||||
| // 系统报错日志 | |||||
| SYS_ERR, | |||||
| // 用户请求日志 | |||||
| REST_REQ, | |||||
| //全局请求日志 | |||||
| GLOBAL_REQ, | |||||
| // 训练模块 | |||||
| BIZ_TRAIN, | |||||
| // 系统模块 | |||||
| BIZ_SYS, | |||||
| // 模型模块 | |||||
| BIZ_MODEL, | |||||
| // 数据集模块 | |||||
| BIZ_DATASET, | |||||
| // k8s模块 | |||||
| BIZ_K8S, | |||||
| //note book | |||||
| NOTE_BOOK, | |||||
| //NFS UTILS | |||||
| NFS_UTIL, | |||||
| //localFileUtil | |||||
| LOCAL_FILE_UTIL, | |||||
| //FILE UTILS | |||||
| FILE_UTIL, | |||||
| //FILE UTILS | |||||
| UPLOAD_TEMP, | |||||
| //STATE MACHINE | |||||
| STATE_MACHINE, | |||||
| //全局垃圾回收 | |||||
| GARBAGE_RECYCLE, | |||||
| //DATA_SEQUENCE | |||||
| DATA_SEQUENCE, | |||||
| //IO UTIL | |||||
| IO_UTIL; | |||||
| /** | |||||
| * 判断日志类型不能为空 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @return boolean 返回类型 | |||||
| */ | |||||
| public static boolean isLogType(LogEnum logType) { | |||||
| /** | |||||
| * 判断日志类型不能为空 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @return boolean 返回类型 | |||||
| */ | |||||
| public static boolean isLogType(LogEnum logType) { | |||||
| if (logType != null) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| if (logType != null) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,72 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.enums; | |||||
| import lombok.Getter; | |||||
| import lombok.ToString; | |||||
| /** | |||||
| * @Description 操作类型枚举 | |||||
| * @Date 2020-08-24 | |||||
| */ | |||||
| @ToString | |||||
| @Getter | |||||
| public enum OperationTypeEnum { | |||||
| /** | |||||
| * SELECT 查询类型 | |||||
| */ | |||||
| SELECT("select", "查询"), | |||||
| /** | |||||
| * UPDATE 修改类型 | |||||
| */ | |||||
| UPDATE("update", "修改"), | |||||
| /** | |||||
| * DELETE 删除类型 | |||||
| */ | |||||
| DELETE("delete", "删除"), | |||||
| /** | |||||
| * LIMIT 禁止操作类型 | |||||
| */ | |||||
| LIMIT("limit", "禁止操作"), | |||||
| /** | |||||
| * INSERT 新增类型 | |||||
| */ | |||||
| INSERT("insert", "新增类型"), | |||||
| ; | |||||
| /** | |||||
| * 操作类型值 | |||||
| */ | |||||
| private String type; | |||||
| /** | |||||
| * 操作类型备注 | |||||
| */ | |||||
| private String desc; | |||||
| OperationTypeEnum(String type, String desc) { | |||||
| this.type = type; | |||||
| this.desc = desc; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.enums; | |||||
| import lombok.Getter; | |||||
| import java.util.HashSet; | |||||
| import java.util.Set; | |||||
| /** | |||||
| * @description 资源回收枚举类 | |||||
| * @date 2020-10-10 | |||||
| */ | |||||
| @Getter | |||||
| public enum RecycleResourceEnum { | |||||
| /** | |||||
| * 数据集文件回收 | |||||
| */ | |||||
| DATASET_RECYCLE_FILE("datasetRecycleFile", "数据集文件回收"), | |||||
| /** | |||||
| * 数据集版本文件回收 | |||||
| */ | |||||
| DATASET_RECYCLE_VERSION_FILE("datasetRecycleVersionFile", "数据集版本文件回收"), | |||||
| ; | |||||
| private String className; | |||||
| private String message; | |||||
| RecycleResourceEnum(String className, String message) { | |||||
| this.className = className; | |||||
| this.message = message; | |||||
| } | |||||
| } | |||||
| @@ -19,8 +19,8 @@ package org.dubhe.enums; | |||||
| import lombok.Getter; | import lombok.Getter; | ||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| import java.util.HashSet; | |||||
| import java.util.Set; | |||||
| /** | /** | ||||
| * @description 训练任务枚举类 | * @description 训练任务枚举类 | ||||
| @@ -72,7 +72,13 @@ public enum TrainJobStatusEnum { | |||||
| this.message = message; | this.message = message; | ||||
| } | } | ||||
| public static TrainJobStatusEnum get(String msg) { | |||||
| /** | |||||
| * 根据信息获取枚举类对象 | |||||
| * | |||||
| * @param msg 信息 | |||||
| * @return 枚举类对象 | |||||
| */ | |||||
| public static TrainJobStatusEnum getByMessage(String msg) { | |||||
| for (TrainJobStatusEnum statusEnum : values()) { | for (TrainJobStatusEnum statusEnum : values()) { | ||||
| if (statusEnum.message.equalsIgnoreCase(msg)) { | if (statusEnum.message.equalsIgnoreCase(msg)) { | ||||
| return statusEnum; | return statusEnum; | ||||
| @@ -81,21 +87,63 @@ public enum TrainJobStatusEnum { | |||||
| return UNKNOWN; | return UNKNOWN; | ||||
| } | } | ||||
| /** | |||||
| * 回调状态转换 若是DELETED则转换为STOP,避免状态不统一 | |||||
| * @param phase k8s pod phase | |||||
| * @return | |||||
| */ | |||||
| public static TrainJobStatusEnum transferStatus(String phase) { | |||||
| TrainJobStatusEnum enums = getByMessage(phase); | |||||
| if (enums != DELETED) { | |||||
| return enums; | |||||
| } | |||||
| return STOP; | |||||
| } | |||||
| /** | |||||
| * 根据状态获取枚举类对象 | |||||
| * | |||||
| * @param status 状态 | |||||
| * @return 枚举类对象 | |||||
| */ | |||||
| public static TrainJobStatusEnum getByStatus(Integer status) { | |||||
| for (TrainJobStatusEnum statusEnum : values()) { | |||||
| if (statusEnum.status.equals(status)) { | |||||
| return statusEnum; | |||||
| } | |||||
| } | |||||
| return UNKNOWN; | |||||
| } | |||||
| /** | |||||
| * 结束状态枚举集合 | |||||
| */ | |||||
| public static final Set<TrainJobStatusEnum> END_TRAIN_JOB_STATUS; | |||||
| static { | |||||
| END_TRAIN_JOB_STATUS = new HashSet<>(); | |||||
| END_TRAIN_JOB_STATUS.add(SUCCEEDED); | |||||
| END_TRAIN_JOB_STATUS.add(FAILED); | |||||
| END_TRAIN_JOB_STATUS.add(STOP); | |||||
| END_TRAIN_JOB_STATUS.add(CREATE_FAILED); | |||||
| END_TRAIN_JOB_STATUS.add(DELETED); | |||||
| } | |||||
| public static boolean isEnd(String msg) { | public static boolean isEnd(String msg) { | ||||
| List<String> endList = Arrays.asList("SUCCEEDED", "FAILED", "STOP", "CREATE_FAILED"); | |||||
| return endList.stream().anyMatch(s -> s.equalsIgnoreCase(msg)); | |||||
| return END_TRAIN_JOB_STATUS.contains(getByMessage(msg)); | |||||
| } | } | ||||
| public static boolean isEnd(Integer num) { | |||||
| List<Integer> endList = Arrays.asList(2, 3, 4, 7); | |||||
| return endList.stream().anyMatch(s -> s.equals(num)); | |||||
| public static boolean isEnd(Integer status) { | |||||
| return END_TRAIN_JOB_STATUS.contains(getByStatus(status)); | |||||
| } | } | ||||
| public static boolean checkStopStatus(Integer num) { | public static boolean checkStopStatus(Integer num) { | ||||
| return SUCCEEDED.getStatus().equals(num) || | |||||
| FAILED.getStatus().equals(num) || | |||||
| STOP.getStatus().equals(num) || | |||||
| CREATE_FAILED.getStatus().equals(num) || | |||||
| DELETED.getStatus().equals(num); | |||||
| return isEnd(num); | |||||
| } | |||||
| public static boolean checkRunStatus(Integer num) { | |||||
| return PENDING.getStatus().equals(num) || | |||||
| RUNNING.getStatus().equals(num); | |||||
| } | } | ||||
| } | } | ||||
| @@ -54,6 +54,7 @@ public enum BaseErrorCode implements ErrorCode { | |||||
| SYSTEM_USER_CANNOT_DELETE(20014, "系统默认用户不可删除!"), | SYSTEM_USER_CANNOT_DELETE(20014, "系统默认用户不可删除!"), | ||||
| SYSTEM_ROLE_CANNOT_DELETE(20015, "系统默认角色不可删除!"), | SYSTEM_ROLE_CANNOT_DELETE(20015, "系统默认角色不可删除!"), | ||||
| DATASET_ADMIN_PERMISSION_ERROR(1310,"无此权限,请联系管理员"), | |||||
| ; | ; | ||||
| @@ -0,0 +1,42 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.exception; | |||||
| import lombok.Getter; | |||||
| /** | |||||
| * @description 获取序列异常 | |||||
| * @date 2020-09-23 | |||||
| */ | |||||
| @Getter | |||||
| public class DataSequenceException extends BusinessException { | |||||
| private static final long serialVersionUID = 1L; | |||||
| public DataSequenceException(String msg) { | |||||
| super(msg); | |||||
| } | |||||
| public DataSequenceException(String msg, Throwable cause) { | |||||
| super(msg,cause); | |||||
| } | |||||
| public DataSequenceException(Throwable cause) { | |||||
| super(cause); | |||||
| } | |||||
| } | |||||
| @@ -20,8 +20,7 @@ package org.dubhe.exception; | |||||
| import lombok.Getter; | import lombok.Getter; | ||||
| /** | /** | ||||
| * @description: Notebook 业务处理异常 | |||||
| * | |||||
| * @description Notebook 业务处理异常 | |||||
| * @date 2020.04.27 | * @date 2020.04.27 | ||||
| */ | */ | ||||
| @Getter | @Getter | ||||
| @@ -17,8 +17,8 @@ | |||||
| package org.dubhe.exception.handler; | package org.dubhe.exception.handler; | ||||
| import java.util.Objects; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import org.apache.ibatis.exceptions.IbatisException; | |||||
| import org.apache.shiro.ShiroException; | import org.apache.shiro.ShiroException; | ||||
| import org.apache.shiro.authc.AuthenticationException; | import org.apache.shiro.authc.AuthenticationException; | ||||
| import org.apache.shiro.authc.IncorrectCredentialsException; | import org.apache.shiro.authc.IncorrectCredentialsException; | ||||
| @@ -27,11 +27,7 @@ import org.apache.shiro.authc.UnknownAccountException; | |||||
| import org.dubhe.base.DataResponseBody; | import org.dubhe.base.DataResponseBody; | ||||
| import org.dubhe.base.ResponseCode; | import org.dubhe.base.ResponseCode; | ||||
| import org.dubhe.enums.LogEnum; | import org.dubhe.enums.LogEnum; | ||||
| import org.dubhe.exception.BusinessException; | |||||
| import org.dubhe.exception.CaptchaException; | |||||
| import org.dubhe.exception.LoginException; | |||||
| import org.dubhe.exception.NotebookBizException; | |||||
| import org.dubhe.exception.UnauthorizedException; | |||||
| import org.dubhe.exception.*; | |||||
| import org.dubhe.utils.LogUtil; | import org.dubhe.utils.LogUtil; | ||||
| import org.springframework.http.HttpStatus; | import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||
| @@ -41,7 +37,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; | |||||
| import org.springframework.web.bind.annotation.ExceptionHandler; | import org.springframework.web.bind.annotation.ExceptionHandler; | ||||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import java.util.Objects; | |||||
| /** | /** | ||||
| * @description 处理异常 | * @description 处理异常 | ||||
| @@ -51,140 +47,144 @@ import lombok.extern.slf4j.Slf4j; | |||||
| @RestControllerAdvice | @RestControllerAdvice | ||||
| public class GlobalExceptionHandler { | public class GlobalExceptionHandler { | ||||
| /** | |||||
| * 处理所有不可知的异常 | |||||
| */ | |||||
| @ExceptionHandler(Throwable.class) | |||||
| public ResponseEntity<DataResponseBody> handleException(Throwable e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, | |||||
| new DataResponseBody(ResponseCode.ERROR, e.getMessage())); | |||||
| } | |||||
| /** | |||||
| * UnauthorizedException | |||||
| */ | |||||
| @ExceptionHandler(UnauthorizedException.class) | |||||
| public ResponseEntity<DataResponseBody> badCredentialsException(UnauthorizedException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); | |||||
| return buildResponseEntity(HttpStatus.UNAUTHORIZED, new DataResponseBody(ResponseCode.ERROR, message)); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = BusinessException.class) | |||||
| public ResponseEntity<DataResponseBody> badRequestException(BusinessException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = AuthenticationException.class) | |||||
| public ResponseEntity<DataResponseBody> badRequestException(AuthenticationException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.UNAUTHORIZED, "无权访问")); | |||||
| } | |||||
| /** | |||||
| * shiro 异常捕捉 | |||||
| */ | |||||
| @ExceptionHandler(value = ShiroException.class) | |||||
| public ResponseEntity<DataResponseBody> accountException(ShiroException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| ResponseEntity<DataResponseBody> responseEntity; | |||||
| if (e instanceof IncorrectCredentialsException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "密码不正确")); | |||||
| } else if (e instanceof UnknownAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "此账户不存在")); | |||||
| } | |||||
| else if (e instanceof LockedAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "未知的账号")); | |||||
| } | |||||
| else if (e instanceof UnknownAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "账户已被禁用")); | |||||
| } | |||||
| else { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, | |||||
| new DataResponseBody(ResponseCode.UNAUTHORIZED, "无权访问")); | |||||
| } | |||||
| return responseEntity; | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = LoginException.class) | |||||
| public ResponseEntity<DataResponseBody> loginException(LoginException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.UNAUTHORIZED, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = CaptchaException.class) | |||||
| public ResponseEntity<DataResponseBody> captchaException(CaptchaException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = NotebookBizException.class) | |||||
| public ResponseEntity<DataResponseBody> captchaException(NotebookBizException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理所有接口数据验证异常 | |||||
| */ | |||||
| @ExceptionHandler(MethodArgumentNotValidException.class) | |||||
| public ResponseEntity<DataResponseBody> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\."); | |||||
| String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); | |||||
| String msg = "不能为空"; | |||||
| if (msg.equals(message)) { | |||||
| message = str[1] + ":" + message; | |||||
| } | |||||
| return buildResponseEntity(HttpStatus.BAD_REQUEST, new DataResponseBody(ResponseCode.ERROR, message)); | |||||
| } | |||||
| @ExceptionHandler(BindException.class) | |||||
| public ResponseEntity<DataResponseBody> bindException(BindException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, e); | |||||
| ObjectError error = e.getAllErrors().get(0); | |||||
| return buildResponseEntity(HttpStatus.BAD_REQUEST, | |||||
| new DataResponseBody(ResponseCode.ERROR, error.getDefaultMessage())); | |||||
| } | |||||
| /** | |||||
| * 统一返回 | |||||
| * | |||||
| * @param httpStatus | |||||
| * @param responseBody | |||||
| * @return | |||||
| */ | |||||
| private ResponseEntity<DataResponseBody> buildResponseEntity(HttpStatus httpStatus, DataResponseBody responseBody) { | |||||
| return new ResponseEntity<>(responseBody, httpStatus); | |||||
| } | |||||
| /** | |||||
| * 处理所有不可知的异常 | |||||
| */ | |||||
| @ExceptionHandler(Throwable.class) | |||||
| public ResponseEntity<DataResponseBody> handleException(Throwable e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, | |||||
| new DataResponseBody(ResponseCode.ERROR, e.getMessage())); | |||||
| } | |||||
| /** | |||||
| * UnauthorizedException | |||||
| */ | |||||
| @ExceptionHandler(UnauthorizedException.class) | |||||
| public ResponseEntity<DataResponseBody> badCredentialsException(UnauthorizedException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); | |||||
| return buildResponseEntity(HttpStatus.UNAUTHORIZED, new DataResponseBody(ResponseCode.ERROR, message)); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = BusinessException.class) | |||||
| public ResponseEntity<DataResponseBody> badRequestException(BusinessException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = IbatisException.class) | |||||
| public ResponseEntity<DataResponseBody> persistenceException(IbatisException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, e.getMessage())); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = AuthenticationException.class) | |||||
| public ResponseEntity<DataResponseBody> badRequestException(AuthenticationException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.UNAUTHORIZED, "无权访问")); | |||||
| } | |||||
| /** | |||||
| * shiro 异常捕捉 | |||||
| */ | |||||
| @ExceptionHandler(value = ShiroException.class) | |||||
| public ResponseEntity<DataResponseBody> accountException(ShiroException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| ResponseEntity<DataResponseBody> responseEntity; | |||||
| if (e instanceof IncorrectCredentialsException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "密码不正确")); | |||||
| } else if (e instanceof UnknownAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "此账户不存在")); | |||||
| } else if (e instanceof LockedAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "未知的账号")); | |||||
| } else if (e instanceof UnknownAccountException) { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, new DataResponseBody(ResponseCode.ERROR, "账户已被禁用")); | |||||
| } else { | |||||
| responseEntity = buildResponseEntity(HttpStatus.OK, | |||||
| new DataResponseBody(ResponseCode.UNAUTHORIZED, "无权访问")); | |||||
| } | |||||
| return responseEntity; | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = LoginException.class) | |||||
| public ResponseEntity<DataResponseBody> loginException(LoginException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.UNAUTHORIZED, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = CaptchaException.class) | |||||
| public ResponseEntity<DataResponseBody> captchaException(CaptchaException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理自定义异常 | |||||
| */ | |||||
| @ExceptionHandler(value = NotebookBizException.class) | |||||
| public ResponseEntity<DataResponseBody> captchaException(NotebookBizException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| return buildResponseEntity(HttpStatus.OK, e.getResponseBody()); | |||||
| } | |||||
| /** | |||||
| * 处理所有接口数据验证异常 | |||||
| */ | |||||
| @ExceptionHandler(MethodArgumentNotValidException.class) | |||||
| public ResponseEntity<DataResponseBody> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\."); | |||||
| String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); | |||||
| String msg = "不能为空"; | |||||
| if (msg.equals(message)) { | |||||
| message = str[1] + ":" + message; | |||||
| } | |||||
| return buildResponseEntity(HttpStatus.BAD_REQUEST, new DataResponseBody(ResponseCode.ERROR, message)); | |||||
| } | |||||
| @ExceptionHandler(BindException.class) | |||||
| public ResponseEntity<DataResponseBody> bindException(BindException e) { | |||||
| // 打印堆栈信息 | |||||
| LogUtil.error(LogEnum.SYS_ERR, "引起异常的堆栈信息:{}", e); | |||||
| ObjectError error = e.getAllErrors().get(0); | |||||
| return buildResponseEntity(HttpStatus.BAD_REQUEST, | |||||
| new DataResponseBody(ResponseCode.ERROR, error.getDefaultMessage())); | |||||
| } | |||||
| /** | |||||
| * 统一返回 | |||||
| * | |||||
| * @param httpStatus | |||||
| * @param responseBody | |||||
| * @return | |||||
| */ | |||||
| private ResponseEntity<DataResponseBody> buildResponseEntity(HttpStatus httpStatus, DataResponseBody responseBody) { | |||||
| return new ResponseEntity<>(responseBody, httpStatus); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,74 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.filter; | |||||
| import ch.qos.logback.classic.Level; | |||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | |||||
| import ch.qos.logback.core.filter.AbstractMatcherFilter; | |||||
| import ch.qos.logback.core.spi.FilterReply; | |||||
| import cn.hutool.core.util.StrUtil; | |||||
| import org.slf4j.Marker; | |||||
| /** | |||||
| * @description 自定义日志过滤器 | |||||
| * @date 2020-07-21 | |||||
| */ | |||||
| public class BaseLogFilter extends AbstractMatcherFilter<ILoggingEvent> { | |||||
| Level level; | |||||
| /** | |||||
| * 重写decide方法 | |||||
| * | |||||
| * @param iLoggingEvent event to decide upon. | |||||
| * @return FilterReply | |||||
| */ | |||||
| @Override | |||||
| public FilterReply decide(ILoggingEvent iLoggingEvent) { | |||||
| if (!isStarted()) { | |||||
| return FilterReply.NEUTRAL; | |||||
| } | |||||
| final String msg = iLoggingEvent.getMessage(); | |||||
| //自定义级别 | |||||
| if (checkLevel(iLoggingEvent) && msg != null && msg.startsWith(StrUtil.DELIM_START) && msg.endsWith(StrUtil.DELIM_END)) { | |||||
| final Marker marker = iLoggingEvent.getMarker(); | |||||
| if (marker != null && this.getName() != null && this.getName().contains(marker.getName())) { | |||||
| return onMatch; | |||||
| } | |||||
| } | |||||
| return onMismatch; | |||||
| } | |||||
| protected boolean checkLevel(ILoggingEvent iLoggingEvent) { | |||||
| return this.level != null | |||||
| && iLoggingEvent.getLevel() != null | |||||
| && iLoggingEvent.getLevel().toInt() == this.level.toInt(); | |||||
| } | |||||
| public void setLevel(Level level) { | |||||
| this.level = level; | |||||
| } | |||||
| @Override | |||||
| public void start() { | |||||
| if (this.level != null) { | |||||
| super.start(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.filter; | |||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | |||||
| import ch.qos.logback.core.spi.FilterReply; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.slf4j.MarkerFactory; | |||||
| /** | |||||
| * @description 自定义日志过滤器 | |||||
| * @date 2020-07-21 | |||||
| */ | |||||
| public class ConsoleLogFilter extends BaseLogFilter { | |||||
| @Override | |||||
| public FilterReply decide(ILoggingEvent iLoggingEvent) { | |||||
| if (!isStarted()) { | |||||
| return FilterReply.NEUTRAL; | |||||
| } | |||||
| return checkLevel(iLoggingEvent) ? onMatch : onMismatch; | |||||
| } | |||||
| protected boolean checkLevel(ILoggingEvent iLoggingEvent) { | |||||
| return this.level != null | |||||
| && iLoggingEvent.getLevel() != null | |||||
| && iLoggingEvent.getLevel().toInt() >= this.level.toInt() | |||||
| && !MarkerFactory.getMarker(LogUtil.K8S_CALLBACK_LEVEL).equals(iLoggingEvent.getMarker()) | |||||
| && !MarkerFactory.getMarker(LogUtil.SCHEDULE_LEVEL).equals(iLoggingEvent.getMarker()) | |||||
| && !"log4jdbc.log4j2".equals(iLoggingEvent.getLoggerName()); | |||||
| } | |||||
| } | |||||
| @@ -1,60 +0,0 @@ | |||||
| /** | |||||
| * Copyright 2019-2020 Zheng Jie | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| */ | |||||
| package org.dubhe.filter; | |||||
| import ch.qos.logback.classic.Level; | |||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | |||||
| import ch.qos.logback.core.filter.AbstractMatcherFilter; | |||||
| import ch.qos.logback.core.spi.FilterReply; | |||||
| /** | |||||
| * @description 自定义日志过滤器 | |||||
| * @date 2020-07-21 | |||||
| */ | |||||
| public class FileLogFilter extends AbstractMatcherFilter<ILoggingEvent> { | |||||
| Level level; | |||||
| /** | |||||
| * 重写decide方法 | |||||
| * | |||||
| * @param iLoggingEvent event to decide upon. | |||||
| * @return FilterReply | |||||
| */ | |||||
| @Override | |||||
| public FilterReply decide(ILoggingEvent iLoggingEvent) { | |||||
| if (!isStarted()) { | |||||
| return FilterReply.NEUTRAL; | |||||
| } | |||||
| if (iLoggingEvent.getLevel().equals(level) && iLoggingEvent.getMessage() != null | |||||
| && iLoggingEvent.getMessage().startsWith("{") && iLoggingEvent.getMessage().endsWith("}")) { | |||||
| return onMatch; | |||||
| } | |||||
| return onMismatch; | |||||
| } | |||||
| public void setLevel(Level level) { | |||||
| this.level = level; | |||||
| } | |||||
| @Override | |||||
| public void start() { | |||||
| if (this.level != null) { | |||||
| super.start(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.filter; | |||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | |||||
| /** | |||||
| * @description 全局请求 日志过滤器 | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| public class GlobalRequestLogFilter extends BaseLogFilter { | |||||
| @Override | |||||
| public boolean checkLevel(ILoggingEvent iLoggingEvent) { | |||||
| return this.level != null; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,113 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.interceptor; | |||||
| import org.apache.ibatis.executor.statement.StatementHandler; | |||||
| import org.apache.ibatis.mapping.BoundSql; | |||||
| import org.apache.ibatis.mapping.MappedStatement; | |||||
| import org.apache.ibatis.plugin.*; | |||||
| import org.apache.ibatis.reflection.DefaultReflectorFactory; | |||||
| import org.apache.ibatis.reflection.MetaObject; | |||||
| import org.apache.ibatis.reflection.SystemMetaObject; | |||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.base.DataContext; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.enums.OperationTypeEnum; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.dubhe.utils.SqlUtil; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.lang.reflect.Field; | |||||
| import java.sql.Connection; | |||||
| import java.util.Arrays; | |||||
| import java.util.Objects; | |||||
| import java.util.Properties; | |||||
| /** | |||||
| * @description mybatis拦截器 | |||||
| * @date 2020-06-10 | |||||
| */ | |||||
| @Component | |||||
| @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) | |||||
| public class MySqlInterceptor implements Interceptor { | |||||
| @Override | |||||
| public Object intercept(Invocation invocation) throws Throwable { | |||||
| StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); | |||||
| MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, | |||||
| SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); | |||||
| /* | |||||
| * 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler, | |||||
| * 然后就到BaseStatementHandler的成员变量mappedStatement | |||||
| */ | |||||
| MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); | |||||
| //id为执行的mapper方法的全路径名,如com.uv.dao.UserDao.selectPageVo | |||||
| String id = mappedStatement.getId(); | |||||
| //sql语句类型 select、delete、insert、update | |||||
| String sqlCommandType = mappedStatement.getSqlCommandType().toString(); | |||||
| BoundSql boundSql = statementHandler.getBoundSql(); | |||||
| //获取到原始sql语句 | |||||
| String sql = boundSql.getSql(); | |||||
| String mSql = sql; | |||||
| //注解逻辑判断 添加注解了才拦截 | |||||
| Class<?> classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."))); | |||||
| String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length()); | |||||
| UserDTO currentUserDto = JwtUtils.getCurrentUserDto(); | |||||
| //获取类注解 获取需要忽略拦截的方法名称 | |||||
| DataPermission dataAnnotation = classType.getAnnotation(DataPermission.class); | |||||
| if (!Objects.isNull(dataAnnotation)) { | |||||
| String[] ignores = dataAnnotation.ignoresMethod(); | |||||
| //校验拦截忽略方法名 忽略新增方法 忽略回调/定时方法 | |||||
| if ((!Objects.isNull(ignores) && Arrays.asList(ignores).contains(mName)) | |||||
| || OperationTypeEnum.INSERT.getType().equals(sqlCommandType.toLowerCase()) | |||||
| || Objects.isNull(currentUserDto) | |||||
| || (!Objects.isNull(DataContext.get()) && DataContext.get().getType()) | |||||
| ) { | |||||
| return invocation.proceed(); | |||||
| } else { | |||||
| //拦截所有sql操作类型 | |||||
| mSql = SqlUtil.buildTargetSql(sql, SqlUtil.getResourceIds()); | |||||
| } | |||||
| } | |||||
| //通过反射修改sql语句 | |||||
| Field field = boundSql.getClass().getDeclaredField("sql"); | |||||
| field.setAccessible(true); | |||||
| field.set(boundSql, mSql); | |||||
| return invocation.proceed(); | |||||
| } | |||||
| @Override | |||||
| public Object plugin(Object target) { | |||||
| if (target instanceof StatementHandler) { | |||||
| return Plugin.wrap(target, this); | |||||
| } else { | |||||
| return target; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void setProperties(Properties properties) { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,457 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.interceptor; | |||||
| import com.baomidou.mybatisplus.annotation.DbType; | |||||
| import com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler; | |||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | |||||
| import com.baomidou.mybatisplus.core.metadata.OrderItem; | |||||
| import com.baomidou.mybatisplus.core.parser.ISqlParser; | |||||
| import com.baomidou.mybatisplus.core.parser.SqlInfo; | |||||
| import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; | |||||
| import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; | |||||
| import com.baomidou.mybatisplus.core.toolkit.PluginUtils; | |||||
| import com.baomidou.mybatisplus.core.toolkit.StringUtils; | |||||
| import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; | |||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.DialectFactory; | |||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel; | |||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect; | |||||
| import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils; | |||||
| import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils; | |||||
| import net.sf.jsqlparser.JSQLParserException; | |||||
| import net.sf.jsqlparser.parser.CCJSqlParserUtil; | |||||
| import net.sf.jsqlparser.schema.Column; | |||||
| import net.sf.jsqlparser.statement.select.*; | |||||
| import org.apache.ibatis.executor.statement.StatementHandler; | |||||
| import org.apache.ibatis.logging.Log; | |||||
| import org.apache.ibatis.logging.LogFactory; | |||||
| import org.apache.ibatis.mapping.*; | |||||
| import org.apache.ibatis.plugin.*; | |||||
| import org.apache.ibatis.reflection.MetaObject; | |||||
| import org.apache.ibatis.reflection.SystemMetaObject; | |||||
| import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; | |||||
| import org.apache.ibatis.session.Configuration; | |||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.base.DataContext; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.enums.OperationTypeEnum; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.dubhe.utils.SqlUtil; | |||||
| import java.sql.Connection; | |||||
| import java.sql.PreparedStatement; | |||||
| import java.sql.ResultSet; | |||||
| import java.util.*; | |||||
| import java.util.stream.Collectors; | |||||
| /** | |||||
| * @description MybatisPlus 分页拦截器 | |||||
| * @date 2020-10-09 | |||||
| */ | |||||
| @Intercepts({@Signature( | |||||
| type = StatementHandler.class, | |||||
| method = "prepare", | |||||
| args = {Connection.class, Integer.class} | |||||
| )}) | |||||
| public class PaginationInterceptor extends AbstractSqlParserHandler implements Interceptor { | |||||
| protected static final Log logger = LogFactory.getLog(PaginationInterceptor.class); | |||||
| /** | |||||
| * COUNT SQL 解析 | |||||
| */ | |||||
| protected ISqlParser countSqlParser; | |||||
| /** | |||||
| * 溢出总页数,设置第一页 | |||||
| */ | |||||
| protected boolean overflow = false; | |||||
| /** | |||||
| * 单页限制 500 条,小于 0 如 -1 不受限制 | |||||
| */ | |||||
| protected long limit = 500L; | |||||
| /** | |||||
| * 数据类型 | |||||
| */ | |||||
| private DbType dbType; | |||||
| /** | |||||
| * 方言 | |||||
| */ | |||||
| private IDialect dialect; | |||||
| /** | |||||
| * 方言类型 | |||||
| */ | |||||
| @Deprecated | |||||
| protected String dialectType; | |||||
| /** | |||||
| * 方言实现类 | |||||
| */ | |||||
| @Deprecated | |||||
| protected String dialectClazz; | |||||
| public PaginationInterceptor() { | |||||
| } | |||||
| /** | |||||
| * 构建分页sql | |||||
| * | |||||
| * @param originalSql 原生sql | |||||
| * @param page 分页参数 | |||||
| * @return 构建后 sql | |||||
| */ | |||||
| public static String concatOrderBy(String originalSql, IPage<?> page) { | |||||
| if (CollectionUtils.isNotEmpty(page.orders())) { | |||||
| try { | |||||
| List<OrderItem> orderList = page.orders(); | |||||
| Select selectStatement = (Select) CCJSqlParserUtil.parse(originalSql); | |||||
| List orderByElements; | |||||
| List orderByElementsReturn; | |||||
| if (selectStatement.getSelectBody() instanceof PlainSelect) { | |||||
| PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody(); | |||||
| orderByElements = plainSelect.getOrderByElements(); | |||||
| orderByElementsReturn = addOrderByElements(orderList, orderByElements); | |||||
| plainSelect.setOrderByElements(orderByElementsReturn); | |||||
| return plainSelect.toString(); | |||||
| } | |||||
| if (selectStatement.getSelectBody() instanceof SetOperationList) { | |||||
| SetOperationList setOperationList = (SetOperationList) selectStatement.getSelectBody(); | |||||
| orderByElements = setOperationList.getOrderByElements(); | |||||
| orderByElementsReturn = addOrderByElements(orderList, orderByElements); | |||||
| setOperationList.setOrderByElements(orderByElementsReturn); | |||||
| return setOperationList.toString(); | |||||
| } | |||||
| if (selectStatement.getSelectBody() instanceof WithItem) { | |||||
| return originalSql; | |||||
| } | |||||
| return originalSql; | |||||
| } catch (JSQLParserException var7) { | |||||
| logger.error("failed to concat orderBy from IPage, exception=", var7); | |||||
| } | |||||
| } | |||||
| return originalSql; | |||||
| } | |||||
| /** | |||||
| * 添加分页排序规则 | |||||
| * | |||||
| * @param orderList 分页规则 | |||||
| * @param orderByElements 分页排序元素 | |||||
| * @return 分页规则 | |||||
| */ | |||||
| private static List<OrderByElement> addOrderByElements(List<OrderItem> orderList, List<OrderByElement> orderByElements) { | |||||
| orderByElements = CollectionUtils.isEmpty(orderByElements) ? new ArrayList(orderList.size()) : orderByElements; | |||||
| List<OrderByElement> orderByElementList = (List) orderList.stream().filter((item) -> { | |||||
| return StringUtils.isNotBlank(item.getColumn()); | |||||
| }).map((item) -> { | |||||
| OrderByElement element = new OrderByElement(); | |||||
| element.setExpression(new Column(item.getColumn())); | |||||
| element.setAsc(item.isAsc()); | |||||
| element.setAscDescPresent(true); | |||||
| return element; | |||||
| }).collect(Collectors.toList()); | |||||
| ((List) orderByElements).addAll(orderByElementList); | |||||
| return (List) orderByElements; | |||||
| } | |||||
| /** | |||||
| * 执行sql查询逻辑 | |||||
| * | |||||
| * @param invocation mybatis 调用类 | |||||
| * @return | |||||
| * @throws Throwable | |||||
| */ | |||||
| @Override | |||||
| public Object intercept(Invocation invocation) throws Throwable { | |||||
| StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget()); | |||||
| MetaObject metaObject = SystemMetaObject.forObject(statementHandler); | |||||
| this.sqlParser(metaObject); | |||||
| MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); | |||||
| if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()) { | |||||
| BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql"); | |||||
| Object paramObj = boundSql.getParameterObject(); | |||||
| IPage<?> page = null; | |||||
| if (paramObj instanceof IPage) { | |||||
| page = (IPage) paramObj; | |||||
| } else if (paramObj instanceof Map) { | |||||
| Iterator var8 = ((Map) paramObj).values().iterator(); | |||||
| while (var8.hasNext()) { | |||||
| Object arg = var8.next(); | |||||
| if (arg instanceof IPage) { | |||||
| page = (IPage) arg; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (null != page && page.getSize() >= 0L) { | |||||
| if (this.limit > 0L && this.limit <= page.getSize()) { | |||||
| this.handlerLimit(page); | |||||
| } | |||||
| String originalSql = boundSql.getSql(); | |||||
| //注解逻辑判断 添加注解了才拦截 | |||||
| Class<?> classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."))); | |||||
| String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length()); | |||||
| UserDTO currentUserDto = JwtUtils.getCurrentUserDto(); | |||||
| String sqlCommandType = mappedStatement.getSqlCommandType().toString(); | |||||
| //获取类注解 获取需要忽略拦截的方法名称 | |||||
| DataPermission dataAnnotation = classType.getAnnotation(DataPermission.class); | |||||
| if (!Objects.isNull(dataAnnotation)) { | |||||
| String[] ignores = dataAnnotation.ignoresMethod(); | |||||
| //校验拦截忽略方法名 忽略新增方法 忽略回调/定时方法 | |||||
| if (!((!Objects.isNull(ignores) && Arrays.asList(ignores).contains(mName)) | |||||
| || OperationTypeEnum.INSERT.getType().equals(sqlCommandType.toLowerCase()) | |||||
| || Objects.isNull(currentUserDto) | |||||
| || (!Objects.isNull(DataContext.get()) && DataContext.get().getType())) | |||||
| ) { | |||||
| originalSql = SqlUtil.buildTargetSql(originalSql, SqlUtil.getResourceIds()); | |||||
| } | |||||
| } | |||||
| Connection connection = (Connection) invocation.getArgs()[0]; | |||||
| if (page.isSearchCount() && !page.isHitCount()) { | |||||
| SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql); | |||||
| this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection); | |||||
| if (page.getTotal() <= 0L) { | |||||
| return null; | |||||
| } | |||||
| } | |||||
| DbType dbType = Optional.ofNullable(this.dbType).orElse(JdbcUtils.getDbType(connection.getMetaData().getURL())); | |||||
| IDialect dialect = Optional.ofNullable(this.dialect).orElse(DialectFactory.getDialect(dbType)); | |||||
| String buildSql = concatOrderBy(originalSql, page); | |||||
| DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize()); | |||||
| Configuration configuration = mappedStatement.getConfiguration(); | |||||
| List<ParameterMapping> mappings = new ArrayList(boundSql.getParameterMappings()); | |||||
| Map<String, Object> additionalParameters = (Map) metaObject.getValue("delegate.boundSql.additionalParameters"); | |||||
| model.consumers(mappings, configuration, additionalParameters); | |||||
| metaObject.setValue("delegate.boundSql.sql", model.getDialectSql()); | |||||
| metaObject.setValue("delegate.boundSql.parameterMappings", mappings); | |||||
| return invocation.proceed(); | |||||
| } else { | |||||
| return invocation.proceed(); | |||||
| } | |||||
| } else { | |||||
| return invocation.proceed(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 处理分页数量 | |||||
| * | |||||
| * @param page 分页参数 | |||||
| */ | |||||
| protected void handlerLimit(IPage<?> page) { | |||||
| page.setSize(this.limit); | |||||
| } | |||||
| /** | |||||
| * 查询总数量 | |||||
| * | |||||
| * @param sql sql语句 | |||||
| * @param mappedStatement 映射语句包装类 | |||||
| * @param boundSql sql包装类 | |||||
| * @param page 分页参数 | |||||
| * @param connection JDBC连接包装类 | |||||
| */ | |||||
| protected void queryTotal(String sql, MappedStatement mappedStatement, BoundSql boundSql, IPage<?> page, Connection connection) { | |||||
| try { | |||||
| PreparedStatement statement = connection.prepareStatement(sql); | |||||
| Throwable var7 = null; | |||||
| try { | |||||
| DefaultParameterHandler parameterHandler = new MybatisDefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), boundSql); | |||||
| parameterHandler.setParameters(statement); | |||||
| long total = 0L; | |||||
| ResultSet resultSet = statement.executeQuery(); | |||||
| Throwable var12 = null; | |||||
| try { | |||||
| if (resultSet.next()) { | |||||
| total = resultSet.getLong(1); | |||||
| } | |||||
| } catch (Throwable var37) { | |||||
| var12 = var37; | |||||
| throw var37; | |||||
| } finally { | |||||
| if (resultSet != null) { | |||||
| if (var12 != null) { | |||||
| try { | |||||
| resultSet.close(); | |||||
| } catch (Throwable var36) { | |||||
| var12.addSuppressed(var36); | |||||
| } | |||||
| } else { | |||||
| resultSet.close(); | |||||
| } | |||||
| } | |||||
| } | |||||
| page.setTotal(total); | |||||
| if (this.overflow && page.getCurrent() > page.getPages()) { | |||||
| this.handlerOverflow(page); | |||||
| } | |||||
| } catch (Throwable var39) { | |||||
| var7 = var39; | |||||
| throw var39; | |||||
| } finally { | |||||
| if (statement != null) { | |||||
| if (var7 != null) { | |||||
| try { | |||||
| statement.close(); | |||||
| } catch (Throwable var35) { | |||||
| var7.addSuppressed(var35); | |||||
| } | |||||
| } else { | |||||
| statement.close(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } catch (Exception var41) { | |||||
| throw ExceptionUtils.mpe("Error: Method queryTotal execution error of sql : \n %s \n", var41, new Object[]{sql}); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 设置默认当前页 | |||||
| * | |||||
| * @param page 分页参数 | |||||
| */ | |||||
| protected void handlerOverflow(IPage<?> page) { | |||||
| page.setCurrent(1L); | |||||
| } | |||||
| /** | |||||
| * MybatisPlus拦截器实现自定义插件 | |||||
| * | |||||
| * @param target 拦截目标对象 | |||||
| * @return | |||||
| */ | |||||
| @Override | |||||
| public Object plugin(Object target) { | |||||
| return target instanceof StatementHandler ? Plugin.wrap(target, this) : target; | |||||
| } | |||||
| /** | |||||
| * MybatisPlus拦截器实现自定义属性设置 | |||||
| * | |||||
| * @param prop 属性参数 | |||||
| */ | |||||
| @Override | |||||
| public void setProperties(Properties prop) { | |||||
| String dialectType = prop.getProperty("dialectType"); | |||||
| String dialectClazz = prop.getProperty("dialectClazz"); | |||||
| if (StringUtils.isNotBlank(dialectType)) { | |||||
| this.setDialectType(dialectType); | |||||
| } | |||||
| if (StringUtils.isNotBlank(dialectClazz)) { | |||||
| this.setDialectClazz(dialectClazz); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 设置数据源类型 | |||||
| * | |||||
| * @param dialectType 数据源类型 | |||||
| */ | |||||
| @Deprecated | |||||
| public void setDialectType(String dialectType) { | |||||
| this.setDbType(DbType.getDbType(dialectType)); | |||||
| } | |||||
| /** | |||||
| * 设置方言实现类配置 | |||||
| * | |||||
| * @param dialectClazz 方言实现类 | |||||
| */ | |||||
| @Deprecated | |||||
| public void setDialectClazz(String dialectClazz) { | |||||
| this.setDialect(DialectFactory.getDialect(dialectClazz)); | |||||
| } | |||||
| /** | |||||
| * 设置获取总数的sql解析器 | |||||
| * | |||||
| * @param countSqlParser 总数的sql解析器 | |||||
| * @return 自定义MybatisPlus拦截器 | |||||
| */ | |||||
| public PaginationInterceptor setCountSqlParser(final ISqlParser countSqlParser) { | |||||
| this.countSqlParser = countSqlParser; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * 溢出总页数,设置第一页 | |||||
| * | |||||
| * @param overflow 溢出总页数 | |||||
| * @return 自定义MybatisPlus拦截器 | |||||
| */ | |||||
| public PaginationInterceptor setOverflow(final boolean overflow) { | |||||
| this.overflow = overflow; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * 设置分页规则 | |||||
| * | |||||
| * @param limit 分页数量 | |||||
| * @return 自定义MybatisPlus拦截器 | |||||
| */ | |||||
| public PaginationInterceptor setLimit(final long limit) { | |||||
| this.limit = limit; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * 设置数据类型 | |||||
| * | |||||
| * @param dbType 数据类型 | |||||
| * @return 自定义MybatisPlus拦截器 | |||||
| */ | |||||
| public PaginationInterceptor setDbType(final DbType dbType) { | |||||
| this.dbType = dbType; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * 设置方言 | |||||
| * | |||||
| * @param dialect 方言 | |||||
| * @return 自定义MybatisPlus拦截器 | |||||
| */ | |||||
| public PaginationInterceptor setDialect(final IDialect dialect) { | |||||
| this.dialect = dialect; | |||||
| return this; | |||||
| } | |||||
| } | |||||
| @@ -18,16 +18,25 @@ | |||||
| package org.dubhe.utils; | package org.dubhe.utils; | ||||
| import java.sql.Timestamp; | import java.sql.Timestamp; | ||||
| import java.text.DateFormat; | |||||
| import java.text.SimpleDateFormat; | |||||
| import java.time.Instant; | import java.time.Instant; | ||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||
| import java.time.ZoneId; | import java.time.ZoneId; | ||||
| import java.util.Date; | |||||
| /** | /** | ||||
| * @description 日期工具类 | * @description 日期工具类 | ||||
| * @date 2020-6-10 | |||||
| * @date 2020-06-10 | |||||
| */ | */ | ||||
| public class DateUtil { | public class DateUtil { | ||||
| private DateUtil(){ | |||||
| } | |||||
| /** | /** | ||||
| * 获取当前时间戳 | * 获取当前时间戳 | ||||
| * | * | ||||
| @@ -77,4 +86,13 @@ public class DateUtil { | |||||
| return (milli-l1); | return (milli-l1); | ||||
| } | } | ||||
| /** | |||||
| * @return 当前字符串时间yyyy-MM-dd HH:mm:ss SSS | |||||
| */ | |||||
| public static String getCurrentTimeStr(){ | |||||
| Date date = new Date(); | |||||
| DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); | |||||
| return dateFormat.format(date); | |||||
| } | |||||
| } | } | ||||
| @@ -19,10 +19,12 @@ package org.dubhe.utils; | |||||
| import cn.hutool.core.codec.Base64; | import cn.hutool.core.codec.Base64; | ||||
| import cn.hutool.core.io.IoUtil; | import cn.hutool.core.io.IoUtil; | ||||
| import cn.hutool.core.util.CharsetUtil; | |||||
| import cn.hutool.core.util.IdUtil; | import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.poi.excel.BigExcelWriter; | import cn.hutool.poi.excel.BigExcelWriter; | ||||
| import cn.hutool.poi.excel.ExcelUtil; | import cn.hutool.poi.excel.ExcelUtil; | ||||
| import org.apache.poi.util.IOUtils; | import org.apache.poi.util.IOUtils; | ||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.exception.BusinessException; | import org.dubhe.exception.BusinessException; | ||||
| import org.springframework.web.multipart.MultipartFile; | import org.springframework.web.multipart.MultipartFile; | ||||
| @@ -352,4 +354,56 @@ public class FileUtil extends cn.hutool.core.io.FileUtil { | |||||
| return getMd5(getByte(file)); | return getMd5(getByte(file)); | ||||
| } | } | ||||
| /** | |||||
| * 生成文件 | |||||
| * @param filePath 文件绝对路径 | |||||
| * @param content 文件内容 | |||||
| * @param append 文件是否是追加 | |||||
| * @return | |||||
| */ | |||||
| public static boolean generateFile(String filePath,String content,boolean append){ | |||||
| File file = new File(filePath); | |||||
| FileOutputStream outputStream = null; | |||||
| try { | |||||
| if (!file.exists()){ | |||||
| file.createNewFile(); | |||||
| } | |||||
| outputStream = new FileOutputStream(file,append); | |||||
| outputStream.write(content.getBytes(CharsetUtil.defaultCharset())); | |||||
| outputStream.flush(); | |||||
| }catch (IOException e) { | |||||
| LogUtil.error(LogEnum.FILE_UTIL,e); | |||||
| return false; | |||||
| }finally { | |||||
| if (outputStream != null){ | |||||
| try { | |||||
| outputStream.close(); | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.FILE_UTIL,e); | |||||
| } | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * 压缩文件目录 | |||||
| * | |||||
| * @param zipDir 待压缩文件夹路径 | |||||
| * @param zipFile 压缩完成zip文件绝对路径 | |||||
| * @return | |||||
| */ | |||||
| public static boolean zipPath(String zipDir,String zipFile) { | |||||
| if (zipDir == null) { | |||||
| return false; | |||||
| } | |||||
| File zip = new File(zipFile); | |||||
| cn.hutool.core.util.ZipUtil.zip(zip, CharsetUtil.defaultCharset(), true, | |||||
| (f) -> !f.isDirectory(), | |||||
| new File(zipDir).listFiles()); | |||||
| return true; | |||||
| } | |||||
| } | } | ||||
| @@ -20,6 +20,7 @@ import org.apache.commons.io.IOUtils; | |||||
| import org.dubhe.enums.LogEnum; | import org.dubhe.enums.LogEnum; | ||||
| import javax.net.ssl.HttpsURLConnection; | import javax.net.ssl.HttpsURLConnection; | ||||
| import javax.net.ssl.SSLContext; | import javax.net.ssl.SSLContext; | ||||
| import javax.net.ssl.SSLSocketFactory; | import javax.net.ssl.SSLSocketFactory; | ||||
| @@ -31,13 +32,13 @@ import java.io.InputStreamReader; | |||||
| import java.net.URL; | import java.net.URL; | ||||
| import java.security.cert.CertificateException; | import java.security.cert.CertificateException; | ||||
| import java.security.cert.X509Certificate; | import java.security.cert.X509Certificate; | ||||
| import org.apache.commons.codec.binary.Base64; | |||||
| import static org.dubhe.constant.StringConstant.UTF8; | import static org.dubhe.constant.StringConstant.UTF8; | ||||
| import static org.dubhe.constant.SymbolConstant.BLANK; | import static org.dubhe.constant.SymbolConstant.BLANK; | ||||
| /** | /** | ||||
| * @description: httpClient工具类,不校验SSL证书 | |||||
| * @date: 2020-5-21 | |||||
| * @description httpClient工具类,不校验SSL证书 | |||||
| * @date 2020-05-21 | |||||
| */ | */ | ||||
| public class HttpClientUtils { | public class HttpClientUtils { | ||||
| @@ -45,7 +46,7 @@ public class HttpClientUtils { | |||||
| InputStream inputStream = null; | InputStream inputStream = null; | ||||
| BufferedReader bufferedReader = null; | BufferedReader bufferedReader = null; | ||||
| InputStreamReader inputStreamReader = null; | InputStreamReader inputStreamReader = null; | ||||
| StringBuilder stringBuider = new StringBuilder(); | |||||
| StringBuilder stringBuilder = new StringBuilder(); | |||||
| String result = BLANK; | String result = BLANK; | ||||
| HttpsURLConnection con = null; | HttpsURLConnection con = null; | ||||
| try { | try { | ||||
| @@ -59,10 +60,10 @@ public class HttpClientUtils { | |||||
| String str = null; | String str = null; | ||||
| while ((str = bufferedReader.readLine()) != null) { | while ((str = bufferedReader.readLine()) != null) { | ||||
| stringBuider.append(str); | |||||
| stringBuilder.append(str); | |||||
| } | } | ||||
| result = stringBuider.toString(); | |||||
| result = stringBuilder.toString(); | |||||
| LogUtil.info(LogEnum.BIZ_SYS,"Request path:{}, SUCCESS, result:{}", path, result); | LogUtil.info(LogEnum.BIZ_SYS,"Request path:{}, SUCCESS, result:{}", path, result); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| @@ -74,17 +75,54 @@ public class HttpClientUtils { | |||||
| return result; | return result; | ||||
| } | } | ||||
| public static String sendHttpsDelete(String path,String username,String password) { | |||||
| InputStream inputStream = null; | |||||
| BufferedReader bufferedReader = null; | |||||
| InputStreamReader inputStreamReader = null; | |||||
| StringBuilder stringBuilder = new StringBuilder(); | |||||
| String result = BLANK; | |||||
| HttpsURLConnection con = null; | |||||
| try { | |||||
| con = getConnection(path); | |||||
| String input =username+ ":" +password; | |||||
| String encoding=Base64.encodeBase64String(input.getBytes()); | |||||
| con.setRequestProperty(JwtUtils.AUTH_HEADER, "Basic " + encoding); | |||||
| con.setRequestMethod("DELETE"); | |||||
| con.connect(); | |||||
| /**将返回的输入流转换成字符串**/ | |||||
| inputStream = con.getInputStream(); | |||||
| inputStreamReader = new InputStreamReader(inputStream, UTF8); | |||||
| bufferedReader = new BufferedReader(inputStreamReader); | |||||
| private static void closeResource(BufferedReader bufferedReader,InputStreamReader inputStreamReader,InputStream inputStream,HttpsURLConnection con) { | |||||
| String str = null; | |||||
| while ((str = bufferedReader.readLine()) != null) { | |||||
| stringBuilder.append(str); | |||||
| } | |||||
| IOUtils.closeQuietly(bufferedReader); | |||||
| result = stringBuilder.toString(); | |||||
| LogUtil.info(LogEnum.BIZ_SYS,"Request path:{}, SUCCESS, result:{}", path, result); | |||||
| if (inputStreamReader != null) { | |||||
| IOUtils.closeQuietly(inputStreamReader); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.BIZ_SYS,"Request path:{}, ERROR, exception:{}", path, e); | |||||
| return result; | |||||
| } finally { | |||||
| closeResource(bufferedReader,inputStreamReader,inputStream,con); | |||||
| } | } | ||||
| return result; | |||||
| } | |||||
| private static void closeResource(BufferedReader bufferedReader,InputStreamReader inputStreamReader,InputStream inputStream,HttpsURLConnection con) { | |||||
| if (inputStream != null) { | if (inputStream != null) { | ||||
| IOUtils.closeQuietly(inputStream); | IOUtils.closeQuietly(inputStream); | ||||
| } | } | ||||
| if (inputStreamReader != null) { | |||||
| IOUtils.closeQuietly(inputStreamReader); | |||||
| } | |||||
| if (bufferedReader != null) { | |||||
| IOUtils.closeQuietly(bufferedReader); | |||||
| } | |||||
| if (con != null) { | if (con != null) { | ||||
| con.disconnect(); | con.disconnect(); | ||||
| } | } | ||||
| @@ -20,8 +20,8 @@ package org.dubhe.utils; | |||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| /** | /** | ||||
| * @description: HttpUtil | |||||
| * @date 2020.04.30 | |||||
| * @description HttpUtil | |||||
| * @date 2020-04-30 | |||||
| */ | */ | ||||
| @Slf4j | @Slf4j | ||||
| public class HttpUtils { | public class HttpUtils { | ||||
| @@ -0,0 +1,46 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.utils; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import java.io.Closeable; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * @description IO流操作工具类 | |||||
| * @date 2020-10-14 | |||||
| */ | |||||
| public class IOUtil { | |||||
| /** | |||||
| * 循环的依次关闭流 | |||||
| * | |||||
| * @param closeableList 要被关闭的流集合 | |||||
| */ | |||||
| public static void close(Closeable... closeableList) { | |||||
| for (Closeable closeable : closeableList) { | |||||
| try { | |||||
| if (closeable != null) { | |||||
| closeable.close(); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.IO_UTIL, "关闭流异常,异常信息:{}", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -139,7 +139,7 @@ public class JwtUtils { | |||||
| public static boolean isTokenExpired(String token) { | public static boolean isTokenExpired(String token) { | ||||
| Date now = Calendar.getInstance().getTime(); | Date now = Calendar.getInstance().getTime(); | ||||
| DecodedJWT jwt = JWT.decode(token); | DecodedJWT jwt = JWT.decode(token); | ||||
| return jwt.getExpiresAt().before(now); | |||||
| return jwt.getExpiresAt() == null || jwt.getExpiresAt().before(now); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -67,12 +67,12 @@ public class K8sNameTool { | |||||
| } | } | ||||
| /** | /** | ||||
| * 生成 Notebook的NameSpace | |||||
| * 生成 Notebook的Namespace | |||||
| * | * | ||||
| * @param userId | * @param userId | ||||
| * @return namespace | * @return namespace | ||||
| */ | */ | ||||
| public String generateNameSpace(long userId) { | |||||
| public String generateNamespace(long userId) { | |||||
| return this.k8sNameConfig.getNamespace() + SEPARATOR + userId; | return this.k8sNameConfig.getNamespace() + SEPARATOR + userId; | ||||
| } | } | ||||
| @@ -96,7 +96,7 @@ public class K8sNameTool { | |||||
| * @param namespace | * @param namespace | ||||
| * @return Long | * @return Long | ||||
| */ | */ | ||||
| public Long getUserIdFromNameSpace(String namespace) { | |||||
| public Long getUserIdFromNamespace(String namespace) { | |||||
| if (StringUtils.isEmpty(namespace) || !namespace.contains(this.k8sNameConfig.getNamespace() + SEPARATOR)) { | if (StringUtils.isEmpty(namespace) || !namespace.contains(this.k8sNameConfig.getNamespace() + SEPARATOR)) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -0,0 +1,307 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.utils; | |||||
| import cn.hutool.core.util.StrUtil; | |||||
| import lombok.Getter; | |||||
| import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; | |||||
| import org.apache.commons.compress.archivers.zip.ZipFile; | |||||
| import org.apache.commons.io.IOUtils; | |||||
| import org.dubhe.base.MagicNumConstant; | |||||
| import org.dubhe.config.NfsConfig; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.beans.factory.annotation.Value; | |||||
| import org.springframework.stereotype.Component; | |||||
| import org.springframework.util.FileCopyUtils; | |||||
| import java.io.*; | |||||
| import java.util.Enumeration; | |||||
| import java.util.zip.ZipEntry; | |||||
| /** | |||||
| * @description 本地文件操作工具类 | |||||
| * @date 2020-08-19 | |||||
| */ | |||||
| @Component | |||||
| @Getter | |||||
| public class LocalFileUtil { | |||||
| @Autowired | |||||
| private NfsConfig nfsConfig; | |||||
| private static final String FILE_SEPARATOR = File.separator; | |||||
| private static final String ZIP = ".zip"; | |||||
| private static final String CHARACTER_GBK = "GBK"; | |||||
| private static final String OS_NAME = "os.name"; | |||||
| private static final String WINDOWS = "Windows"; | |||||
| @Value("${k8s.nfs-root-path}") | |||||
| private String nfsRootPath; | |||||
| @Value("${k8s.nfs-root-windows-path}") | |||||
| private String nfsRootWindowsPath; | |||||
| /** | |||||
| * windows 与 linux 的路径兼容 | |||||
| * | |||||
| * @param path linux下的路径 | |||||
| * @return path 兼容windows后的路径 | |||||
| */ | |||||
| private String compatiblePath(String path) { | |||||
| if (path == null) { | |||||
| return null; | |||||
| } | |||||
| if (System.getProperties().getProperty(OS_NAME).contains(WINDOWS)) { | |||||
| path = path.replace(nfsRootPath, StrUtil.SLASH); | |||||
| path = path.replace(StrUtil.SLASH, FILE_SEPARATOR); | |||||
| path = nfsRootWindowsPath + path; | |||||
| } | |||||
| return path; | |||||
| } | |||||
| /** | |||||
| * 本地解压zip包并删除压缩文件 | |||||
| * | |||||
| * @param sourcePath zip源文件 例如:/abc/z.zip | |||||
| * @param targetPath 解压后的目标文件夹 例如:/abc/ | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean unzipLocalPath(String sourcePath, String targetPath) { | |||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | |||||
| return false; | |||||
| } | |||||
| if (!sourcePath.toLowerCase().endsWith(ZIP)) { | |||||
| return false; | |||||
| } | |||||
| //绝对路径 | |||||
| String sourceAbsolutePath = nfsConfig.getRootDir() + sourcePath; | |||||
| String targetPathAbsolutePath = nfsConfig.getRootDir() + targetPath; | |||||
| ZipFile zipFile = null; | |||||
| InputStream in = null; | |||||
| OutputStream out = null; | |||||
| File sourceFile = new File(compatiblePath(sourceAbsolutePath)); | |||||
| File targetFileDir = new File(compatiblePath(targetPathAbsolutePath)); | |||||
| if (!targetFileDir.exists()) { | |||||
| boolean targetMkdir = targetFileDir.mkdirs(); | |||||
| if (!targetMkdir) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}failed to create target folder before decompression", sourceAbsolutePath); | |||||
| } | |||||
| } | |||||
| try { | |||||
| zipFile = new ZipFile(sourceFile); | |||||
| //判断压缩文件编码方式,并重新获取文件对象 | |||||
| try { | |||||
| zipFile.close(); | |||||
| zipFile = new ZipFile(sourceFile, CHARACTER_GBK); | |||||
| } catch (Exception e) { | |||||
| zipFile.close(); | |||||
| zipFile = new ZipFile(sourceFile); | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}the encoding mode of decompressed compressed file is changed to UTF-8:{}", sourceAbsolutePath, e); | |||||
| } | |||||
| ZipEntry entry; | |||||
| Enumeration enumeration = zipFile.getEntries(); | |||||
| while (enumeration.hasMoreElements()) { | |||||
| entry = (ZipEntry) enumeration.nextElement(); | |||||
| String entryName = entry.getName(); | |||||
| File fileDir; | |||||
| if (entry.isDirectory()) { | |||||
| fileDir = new File(targetPathAbsolutePath + entry.getName()); | |||||
| if (!fileDir.exists()) { | |||||
| boolean fileMkdir = fileDir.mkdirs(); | |||||
| if (!fileMkdir) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "failed to create folder {} while decompressing {}", fileDir, sourceAbsolutePath); | |||||
| } | |||||
| } | |||||
| } else { | |||||
| //若文件夹未创建则创建文件夹 | |||||
| if (entryName.contains(FILE_SEPARATOR)) { | |||||
| String zipDirName = entryName.substring(MagicNumConstant.ZERO, entryName.lastIndexOf(FILE_SEPARATOR)); | |||||
| fileDir = new File(targetPathAbsolutePath + zipDirName); | |||||
| if (!fileDir.exists()) { | |||||
| boolean fileMkdir = fileDir.mkdirs(); | |||||
| if (!fileMkdir) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "failed to create folder {} while decompressing {}", fileDir, sourceAbsolutePath); | |||||
| } | |||||
| } | |||||
| } | |||||
| in = zipFile.getInputStream((ZipArchiveEntry) entry); | |||||
| out = new FileOutputStream(new File(targetPathAbsolutePath, entryName)); | |||||
| IOUtils.copyLarge(in, out); | |||||
| in.close(); | |||||
| out.close(); | |||||
| } | |||||
| } | |||||
| boolean deleteZipFile = sourceFile.delete(); | |||||
| if (!deleteZipFile) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}compressed file deletion failed after decompression", sourceAbsolutePath); | |||||
| } | |||||
| return true; | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}decompression failed: {}", sourceAbsolutePath, e); | |||||
| return false; | |||||
| } finally { | |||||
| //关闭未关闭的io流 | |||||
| closeIoFlow(sourceAbsolutePath, zipFile, in, out); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 关闭未关闭的io流 | |||||
| * | |||||
| * @param sourceAbsolutePath 源路径 | |||||
| * @param zipFile 压缩文件对象 | |||||
| * @param in 输入流 | |||||
| * @param out 输出流 | |||||
| */ | |||||
| private void closeIoFlow(String sourceAbsolutePath, ZipFile zipFile, InputStream in, OutputStream out) { | |||||
| if (in != null) { | |||||
| try { | |||||
| in.close(); | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}input stream shutdown failed: {}", sourceAbsolutePath, e); | |||||
| } | |||||
| } | |||||
| if (out != null) { | |||||
| try { | |||||
| out.close(); | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}output stream shutdown failed: {}", sourceAbsolutePath, e); | |||||
| } | |||||
| } | |||||
| if (zipFile != null) { | |||||
| try { | |||||
| zipFile.close(); | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "{}input stream shutdown failed: {}", sourceAbsolutePath, e); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * NFS 复制目录到指定目录下 多个文件 包含目录与文件并存情况 | |||||
| * | |||||
| * 通过本地文件复制方式 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件目录 例如:/abc/def | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean copyPath(String sourcePath, String targetPath) { | |||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | |||||
| return false; | |||||
| } | |||||
| sourcePath = formatPath(sourcePath); | |||||
| targetPath = formatPath(targetPath); | |||||
| try { | |||||
| return copyLocalPath(nfsConfig.getRootDir() + sourcePath, nfsConfig.getRootDir() + targetPath); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, " failed to Copy file original path: {} ,target path: {} ,copyPath: {}", sourcePath, targetPath, e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 复制文件到指定目录下 单个文件 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件 例如:/abc/def/cc.txt | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| private boolean copyLocalFile(String sourcePath, String targetPath) { | |||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | |||||
| return false; | |||||
| } | |||||
| sourcePath = formatPath(sourcePath); | |||||
| targetPath = formatPath(targetPath); | |||||
| try (InputStream input = new FileInputStream(sourcePath); | |||||
| FileOutputStream output = new FileOutputStream(targetPath)) { | |||||
| FileCopyUtils.copy(input, output); | |||||
| return true; | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, " failed to copy file original path: {} ,target path: {} ,copyLocalFile:{} ", sourcePath, targetPath, e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 复制文件 到指定目录下 多个文件 包含目录与文件并存情况 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件目录 例如:/abc/def | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| private boolean copyLocalPath(String sourcePath, String targetPath) { | |||||
| if (!StringUtils.isEmpty(sourcePath) && !StringUtils.isEmpty(targetPath)) { | |||||
| sourcePath = formatPath(sourcePath); | |||||
| if (sourcePath.endsWith(FILE_SEPARATOR)) { | |||||
| sourcePath = sourcePath.substring(MagicNumConstant.ZERO, sourcePath.lastIndexOf(FILE_SEPARATOR)); | |||||
| } | |||||
| targetPath = formatPath(targetPath); | |||||
| File sourceFile = new File(sourcePath); | |||||
| if (sourceFile.exists()) { | |||||
| File[] files = sourceFile.listFiles(); | |||||
| if (files != null && files.length != 0) { | |||||
| for (File file : files) { | |||||
| try { | |||||
| if (file.isDirectory()) { | |||||
| File fileDir = new File(targetPath + FILE_SEPARATOR + file.getName()); | |||||
| if (!fileDir.exists()) { | |||||
| fileDir.mkdirs(); | |||||
| } | |||||
| copyLocalPath(sourcePath + FILE_SEPARATOR + file.getName(), targetPath + FILE_SEPARATOR + file.getName()); | |||||
| } | |||||
| if (file.isFile()) { | |||||
| File fileTargetPath = new File(targetPath); | |||||
| if (!fileTargetPath.exists()) { | |||||
| fileTargetPath.mkdirs(); | |||||
| } | |||||
| copyLocalFile(file.getAbsolutePath(), targetPath + FILE_SEPARATOR + file.getName()); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.LOCAL_FILE_UTIL, "failed to copy folder original path: {} , target path : {} ,copyLocalPath: {}", sourcePath, targetPath, e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * 替换路径中多余的 "/" | |||||
| * | |||||
| * @param path | |||||
| * @return String | |||||
| */ | |||||
| public String formatPath(String path) { | |||||
| if (!StringUtils.isEmpty(path)) { | |||||
| return path.replaceAll("///*", FILE_SEPARATOR); | |||||
| } | |||||
| return path; | |||||
| } | |||||
| } | |||||
| @@ -23,10 +23,10 @@ import lombok.extern.slf4j.Slf4j; | |||||
| import org.apache.commons.lang3.exception.ExceptionUtils; | import org.apache.commons.lang3.exception.ExceptionUtils; | ||||
| import org.dubhe.aspect.LogAspect; | import org.dubhe.aspect.LogAspect; | ||||
| import org.dubhe.base.MagicNumConstant; | import org.dubhe.base.MagicNumConstant; | ||||
| import org.dubhe.constant.SymbolConstant; | |||||
| import org.dubhe.domain.entity.LogInfo; | import org.dubhe.domain.entity.LogInfo; | ||||
| import org.dubhe.enums.LogEnum; | import org.dubhe.enums.LogEnum; | ||||
| import org.slf4j.MDC; | import org.slf4j.MDC; | ||||
| import org.slf4j.MarkerFactory; | |||||
| import org.slf4j.helpers.MessageFormatter; | import org.slf4j.helpers.MessageFormatter; | ||||
| import java.util.Arrays; | import java.util.Arrays; | ||||
| @@ -38,218 +38,283 @@ import java.util.UUID; | |||||
| */ | */ | ||||
| @Slf4j | @Slf4j | ||||
| public class LogUtil { | public class LogUtil { | ||||
| /** | |||||
| * info级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| public static void info(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.INFO, object); | |||||
| } | |||||
| /** | |||||
| * debug级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| public static void debug(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.DEBUG, object); | |||||
| } | |||||
| /** | |||||
| * error级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| public static void error(LogEnum logType, Object... object) { | |||||
| errorObjectHandle(object); | |||||
| logHandle(logType, Level.ERROR, object); | |||||
| } | |||||
| /** | |||||
| * warn级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| public static void warn(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.WARN, object); | |||||
| } | |||||
| /** | |||||
| * trace级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| public static void trace(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.TRACE, object); | |||||
| } | |||||
| /** | |||||
| * 日志处理 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param level 日志级别 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| private static void logHandle(LogEnum logType, Level level, Object[] object) { | |||||
| LogInfo logInfo = generateLogInfo(logType, level, object); | |||||
| String logInfoJsonStr = logJsonStringLengthLimit(logInfo); | |||||
| switch (Level.toLevel(logInfo.getLevel()).levelInt) { | |||||
| case Level.TRACE_INT: | |||||
| log.trace(logInfoJsonStr); | |||||
| break; | |||||
| case Level.DEBUG_INT: | |||||
| log.debug(logInfoJsonStr); | |||||
| break; | |||||
| case Level.INFO_INT: | |||||
| log.info(logInfoJsonStr); | |||||
| break; | |||||
| case Level.WARN_INT: | |||||
| log.warn(logInfoJsonStr); | |||||
| break; | |||||
| case Level.ERROR_INT: | |||||
| log.error(logInfoJsonStr); | |||||
| break; | |||||
| default: | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 日志信息组装的内部方法 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param level 日志级别 | |||||
| * @param object 打印的日志参数 | |||||
| * @return LogInfo日志对象信息 | |||||
| */ | |||||
| private static LogInfo generateLogInfo(LogEnum logType, Level level, Object[] object) { | |||||
| LogInfo logInfo = new LogInfo(); | |||||
| // 日志类型检测 | |||||
| if (!LogEnum.isLogType(logType)) { | |||||
| level = Level.ERROR; | |||||
| object = new Object[MagicNumConstant.ONE]; | |||||
| object[MagicNumConstant.ZERO] = String.valueOf("logType【").concat(String.valueOf(logType)) | |||||
| .concat("】is error !"); | |||||
| logType = LogEnum.SYS_ERR; | |||||
| } | |||||
| // 获取trace_id | |||||
| if (StringUtils.isEmpty(MDC.get(LogAspect.TRACE_ID))) { | |||||
| MDC.put(LogAspect.TRACE_ID, UUID.randomUUID().toString()); | |||||
| } | |||||
| // 设置logInfo的level,type,traceId属性 | |||||
| logInfo.setLevel(level.levelStr).setType(logType.toString()).setTraceId(MDC.get(LogAspect.TRACE_ID)); | |||||
| // 设置logInfo的堆栈信息 | |||||
| setLogStackInfo(logInfo); | |||||
| // 设置logInfo的info信息 | |||||
| setLogInfo(logInfo, object); | |||||
| // 截取loginfo的长度并转换成json字符串 | |||||
| return logInfo; | |||||
| } | |||||
| /** | |||||
| * 设置loginfo的堆栈信息 | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| */ | |||||
| private static void setLogStackInfo(LogInfo logInfo) { | |||||
| StackTraceElement[] elements = Thread.currentThread().getStackTrace(); | |||||
| if (elements.length >= MagicNumConstant.SIX) { | |||||
| logInfo.setCName(elements[MagicNumConstant.FIVE].getClassName()) | |||||
| .setMName(elements[MagicNumConstant.FIVE].getMethodName()) | |||||
| .setLine(String.valueOf(elements[MagicNumConstant.FIVE].getLineNumber())); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 限制log日志的长度并转换成json | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| * @return String 日志对象Json字符串 | |||||
| */ | |||||
| private static String logJsonStringLengthLimit(LogInfo logInfo) { | |||||
| try { | |||||
| String jsonString = JSON.toJSONString(logInfo.getInfo()); | |||||
| if (jsonString.length() > MagicNumConstant.TEN_THOUSAND) { | |||||
| jsonString = jsonString.substring(MagicNumConstant.ZERO, MagicNumConstant.TEN_THOUSAND); | |||||
| } | |||||
| logInfo.setInfo(jsonString); | |||||
| jsonString = JSON.toJSONString(logInfo); | |||||
| jsonString = jsonString.replace(SymbolConstant.BACKSLASH_MARK, SymbolConstant.MARK) | |||||
| .replace(SymbolConstant.DOUBLE_MARK, SymbolConstant.MARK) | |||||
| .replace(SymbolConstant.BRACKETS, SymbolConstant.BLANK); | |||||
| return jsonString; | |||||
| } catch (Exception e) { | |||||
| logInfo.setLevel(Level.ERROR.levelStr).setType(LogEnum.SYS_ERR.toString()) | |||||
| .setInfo("cannot serialize exception: " + ExceptionUtils.getStackTrace(e)); | |||||
| return JSON.toJSONString(logInfo); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 设置日志对象的info信息 | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| private static void setLogInfo(LogInfo logInfo, Object[] object) { | |||||
| for (Object obj : object) { | |||||
| if (obj instanceof Exception) { | |||||
| log.error((ExceptionUtils.getStackTrace((Throwable) obj))); | |||||
| } | |||||
| } | |||||
| if (object.length > MagicNumConstant.ONE) { | |||||
| logInfo.setInfo(MessageFormatter.arrayFormat(object[MagicNumConstant.ZERO].toString(), | |||||
| Arrays.copyOfRange(object, MagicNumConstant.ONE, object.length)).getMessage()); | |||||
| } else if (object.length == MagicNumConstant.ONE && object[MagicNumConstant.ZERO] instanceof Exception) { | |||||
| logInfo.setInfo((ExceptionUtils.getStackTrace((Exception) object[MagicNumConstant.ZERO]))); | |||||
| } else if (object.length == MagicNumConstant.ONE) { | |||||
| logInfo.setInfo( | |||||
| object[MagicNumConstant.ZERO] == null ? SymbolConstant.BLANK : object[MagicNumConstant.ZERO]); | |||||
| } else { | |||||
| logInfo.setInfo(SymbolConstant.BLANK); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 处理Exception的情况 | |||||
| * | |||||
| * @param object 打印的日志参数 | |||||
| */ | |||||
| private static void errorObjectHandle(Object[] object) { | |||||
| if (object.length >= MagicNumConstant.TWO) { | |||||
| object[MagicNumConstant.ZERO] = String.valueOf(object[MagicNumConstant.ZERO]) | |||||
| .concat(SymbolConstant.BRACKETS); | |||||
| } | |||||
| if (object.length == MagicNumConstant.TWO && object[MagicNumConstant.ONE] instanceof Exception) { | |||||
| log.error((ExceptionUtils.getStackTrace((Throwable) object[MagicNumConstant.ONE]))); | |||||
| object[MagicNumConstant.ONE] = ExceptionUtils.getStackTrace((Exception) object[MagicNumConstant.ONE]); | |||||
| } else if (object.length >= MagicNumConstant.THREE) { | |||||
| for (int i = 0; i < object.length; i++) { | |||||
| if (object[i] instanceof Exception) { | |||||
| log.error((ExceptionUtils.getStackTrace((Throwable) object[i]))); | |||||
| object[i] = ExceptionUtils.getStackTrace((Exception) object[i]); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| private static final String TRACE_TYPE = "TRACE_TYPE"; | |||||
| public static final String SCHEDULE_LEVEL = "SCHEDULE"; | |||||
| public static final String K8S_CALLBACK_LEVEL = "K8S_CALLBACK"; | |||||
| private static final String GLOBAL_REQUEST_LEVEL = "GLOBAL_REQUEST"; | |||||
| private static final String TRACE_LEVEL = "TRACE"; | |||||
| private static final String DEBUG_LEVEL = "DEBUG"; | |||||
| private static final String INFO_LEVEL = "INFO"; | |||||
| private static final String WARN_LEVEL = "WARN"; | |||||
| private static final String ERROR_LEVEL = "ERROR"; | |||||
| public static void startScheduleTrace() { | |||||
| MDC.put(TRACE_TYPE, SCHEDULE_LEVEL); | |||||
| } | |||||
| public static void startK8sCallbackTrace() { | |||||
| MDC.put(TRACE_TYPE, K8S_CALLBACK_LEVEL); | |||||
| } | |||||
| public static void cleanTrace() { | |||||
| MDC.clear(); | |||||
| } | |||||
| /** | |||||
| * info级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| public static void info(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.INFO, object); | |||||
| } | |||||
| /** | |||||
| * debug级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| public static void debug(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.DEBUG, object); | |||||
| } | |||||
| /** | |||||
| * error级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| public static void error(LogEnum logType, Object... object) { | |||||
| errorObjectHandle(object); | |||||
| logHandle(logType, Level.ERROR, object); | |||||
| } | |||||
| /** | |||||
| * warn级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| public static void warn(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.WARN, object); | |||||
| } | |||||
| /** | |||||
| * trace级别的日志 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| public static void trace(LogEnum logType, Object... object) { | |||||
| logHandle(logType, Level.TRACE, object); | |||||
| } | |||||
| /** | |||||
| * 日志处理 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param level 日志级别 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| private static void logHandle(LogEnum logType, Level level, Object[] object) { | |||||
| LogInfo logInfo = generateLogInfo(logType, level, object); | |||||
| switch (logInfo.getLevel()) { | |||||
| case TRACE_LEVEL: | |||||
| log.trace(MarkerFactory.getMarker(TRACE_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case DEBUG_LEVEL: | |||||
| log.debug(MarkerFactory.getMarker(DEBUG_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case GLOBAL_REQUEST_LEVEL: | |||||
| logInfo.setLevel(null); | |||||
| logInfo.setType(null); | |||||
| logInfo.setLocation(null); | |||||
| log.info(MarkerFactory.getMarker(GLOBAL_REQUEST_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case SCHEDULE_LEVEL: | |||||
| log.info(MarkerFactory.getMarker(SCHEDULE_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case K8S_CALLBACK_LEVEL: | |||||
| log.info(MarkerFactory.getMarker(K8S_CALLBACK_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case INFO_LEVEL: | |||||
| log.info(MarkerFactory.getMarker(INFO_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case WARN_LEVEL: | |||||
| log.warn(MarkerFactory.getMarker(WARN_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| case ERROR_LEVEL: | |||||
| log.error(MarkerFactory.getMarker(ERROR_LEVEL), logJsonStringLengthLimit(logInfo)); | |||||
| break; | |||||
| default: | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 日志信息组装的内部方法 | |||||
| * | |||||
| * @param logType 日志类型 | |||||
| * @param level 日志级别 | |||||
| * @param object 打印的日志参数 | |||||
| * @return LogInfo | |||||
| */ | |||||
| private static LogInfo generateLogInfo(LogEnum logType, Level level, Object[] object) { | |||||
| LogInfo logInfo = new LogInfo(); | |||||
| // 日志类型检测 | |||||
| if (!LogEnum.isLogType(logType)) { | |||||
| level = Level.ERROR; | |||||
| object = new Object[MagicNumConstant.ONE]; | |||||
| object[MagicNumConstant.ZERO] = "日志类型【".concat(String.valueOf(logType)).concat("】不正确!"); | |||||
| logType = LogEnum.SYS_ERR; | |||||
| } | |||||
| // 获取trace_id | |||||
| if (StringUtils.isEmpty(MDC.get(LogAspect.TRACE_ID))) { | |||||
| MDC.put(LogAspect.TRACE_ID, UUID.randomUUID().toString()); | |||||
| } | |||||
| // 设置logInfo的level,type,traceId属性 | |||||
| logInfo.setLevel(level.levelStr) | |||||
| .setType(logType.toString()) | |||||
| .setTraceId(MDC.get(LogAspect.TRACE_ID)); | |||||
| //自定义日志级别 | |||||
| //LogEnum、 MDC中的 TRACE_TYPE 做日志分流标识 | |||||
| if (Level.INFO.toInt() == level.toInt()) { | |||||
| if (LogEnum.GLOBAL_REQ.equals(logType)) { | |||||
| //info全局请求 | |||||
| logInfo.setLevel(GLOBAL_REQUEST_LEVEL); | |||||
| } else if (LogEnum.BIZ_K8S.equals(logType)) { | |||||
| logInfo.setLevel(K8S_CALLBACK_LEVEL); | |||||
| } else { | |||||
| //schedule定时等 链路记录 | |||||
| String traceType = MDC.get(TRACE_TYPE); | |||||
| if (StringUtils.isNotBlank(traceType)) { | |||||
| logInfo.setLevel(traceType); | |||||
| } | |||||
| } | |||||
| } | |||||
| // 设置logInfo的堆栈信息 | |||||
| setLogStackInfo(logInfo); | |||||
| // 设置logInfo的info信息 | |||||
| setLogInfo(logInfo, object); | |||||
| // 截取logInfo的长度并转换成json字符串 | |||||
| return logInfo; | |||||
| } | |||||
| /** | |||||
| * 设置loginfo的堆栈信息 | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| * @return void | |||||
| */ | |||||
| private static void setLogStackInfo(LogInfo logInfo) { | |||||
| StackTraceElement[] elements = Thread.currentThread().getStackTrace(); | |||||
| if (elements.length >= MagicNumConstant.SIX) { | |||||
| StackTraceElement element = elements[MagicNumConstant.FIVE]; | |||||
| logInfo.setLocation(String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber())); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 限制log日志的长度并转换成json | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| * @return String | |||||
| */ | |||||
| private static String logJsonStringLengthLimit(LogInfo logInfo) { | |||||
| try { | |||||
| String jsonString = JSON.toJSONString(logInfo); | |||||
| if (StringUtils.isBlank(jsonString)) { | |||||
| return ""; | |||||
| } | |||||
| if (jsonString.length() > MagicNumConstant.TEN_THOUSAND) { | |||||
| String trunk = logInfo.getInfo().toString().substring(MagicNumConstant.ZERO, MagicNumConstant.NINE_THOUSAND); | |||||
| logInfo.setInfo(trunk); | |||||
| jsonString = JSON.toJSONString(logInfo); | |||||
| } | |||||
| return jsonString; | |||||
| } catch (Exception e) { | |||||
| logInfo.setLevel(Level.ERROR.levelStr).setType(LogEnum.SYS_ERR.toString()) | |||||
| .setInfo("cannot serialize exception: " + ExceptionUtils.getStackTrace(e)); | |||||
| return JSON.toJSONString(logInfo); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 设置日志对象的info信息 | |||||
| * | |||||
| * @param logInfo 日志对象 | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| private static void setLogInfo(LogInfo logInfo, Object[] object) { | |||||
| if (object.length > MagicNumConstant.ONE) { | |||||
| logInfo.setInfo(MessageFormatter.arrayFormat(object[MagicNumConstant.ZERO].toString(), | |||||
| Arrays.copyOfRange(object, MagicNumConstant.ONE, object.length)).getMessage()); | |||||
| } else if (object.length == MagicNumConstant.ONE && object[MagicNumConstant.ZERO] instanceof Exception) { | |||||
| logInfo.setInfo((ExceptionUtils.getStackTrace((Exception) object[MagicNumConstant.ZERO]))); | |||||
| log.error((ExceptionUtils.getStackTrace((Exception) object[MagicNumConstant.ZERO]))); | |||||
| } else if (object.length == MagicNumConstant.ONE) { | |||||
| logInfo.setInfo(object[MagicNumConstant.ZERO] == null ? "" : object[MagicNumConstant.ZERO]); | |||||
| } else { | |||||
| logInfo.setInfo(""); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 处理Exception的情况 | |||||
| * | |||||
| * @param object 打印的日志参数 | |||||
| * @return void | |||||
| */ | |||||
| private static void errorObjectHandle(Object[] object) { | |||||
| if (object.length == MagicNumConstant.TWO && object[MagicNumConstant.ONE] instanceof Exception) { | |||||
| log.error(String.valueOf(object[MagicNumConstant.ZERO]), (Exception) object[MagicNumConstant.ONE]); | |||||
| object[MagicNumConstant.ONE] = ExceptionUtils.getStackTrace((Exception) object[MagicNumConstant.ONE]); | |||||
| } else if (object.length >= MagicNumConstant.THREE) { | |||||
| log.error(String.valueOf(object[MagicNumConstant.ZERO]), | |||||
| Arrays.copyOfRange(object, MagicNumConstant.ONE, object.length)); | |||||
| for (int i = 0; i < object.length; i++) { | |||||
| if (object[i] instanceof Exception) { | |||||
| object[i] = ExceptionUtils.getStackTrace((Exception) object[i]); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -20,8 +20,8 @@ package org.dubhe.utils; | |||||
| import org.dubhe.base.MagicNumConstant; | import org.dubhe.base.MagicNumConstant; | ||||
| /** | /** | ||||
| * @description: 计算工具类 | |||||
| * @create: 2020/6/4 14:53 | |||||
| * @description 计算工具类 | |||||
| * @date 2020-06-04 | |||||
| */ | */ | ||||
| public class MathUtils { | public class MathUtils { | ||||
| @@ -68,9 +68,9 @@ public class MinioUtil { | |||||
| try { | try { | ||||
| client = new MinioClient(url, accessKey, secretKey); | client = new MinioClient(url, accessKey, secretKey); | ||||
| } catch (InvalidEndpointException e) { | } catch (InvalidEndpointException e) { | ||||
| LogUtil.warn(LogEnum.BIZ_DATASET, "MinIO endpoint invalid. e:", e); | |||||
| LogUtil.warn(LogEnum.BIZ_DATASET, "MinIO endpoint invalid. e, {}", e); | |||||
| } catch (InvalidPortException e) { | } catch (InvalidPortException e) { | ||||
| LogUtil.warn(LogEnum.BIZ_DATASET, "MinIO endpoint port invalid. e:", e); | |||||
| LogUtil.warn(LogEnum.BIZ_DATASET, "MinIO endpoint port invalid. e, {}", e); | |||||
| } | } | ||||
| } | } | ||||
| @@ -94,7 +94,7 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 读取文件 | * 读取文件 | ||||
| * | * | ||||
| * @param bucket | |||||
| * @param bucket 桶 | |||||
| * @param fullFilePath 文件存储的全路径,包括文件名,非'/'开头. e.g. dataset/12/annotation/test.txt | * @param fullFilePath 文件存储的全路径,包括文件名,非'/'开头. e.g. dataset/12/annotation/test.txt | ||||
| * @return String | * @return String | ||||
| */ | */ | ||||
| @@ -107,7 +107,7 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 文件删除 | * 文件删除 | ||||
| * | * | ||||
| * @param bucket | |||||
| * @param bucket 桶 | |||||
| * @param fullFilePath 文件存储的全路径,包括文件名,非'/'开头. e.g. dataset/12/annotation/test.txt | * @param fullFilePath 文件存储的全路径,包括文件名,非'/'开头. e.g. dataset/12/annotation/test.txt | ||||
| */ | */ | ||||
| public void del(String bucket, String fullFilePath) throws Exception { | public void del(String bucket, String fullFilePath) throws Exception { | ||||
| @@ -125,8 +125,8 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 批量删除文件 | * 批量删除文件 | ||||
| * | * | ||||
| * @param bucket | |||||
| * @param objectNames | |||||
| * @param bucket 桶 | |||||
| * @param objectNames 对象名称 | |||||
| */ | */ | ||||
| public void delFiles(String bucket,List<String> objectNames) throws Exception{ | public void delFiles(String bucket,List<String> objectNames) throws Exception{ | ||||
| Iterable<Result<DeleteError>> results = client.removeObjects(bucket, objectNames); | Iterable<Result<DeleteError>> results = client.removeObjects(bucket, objectNames); | ||||
| @@ -138,6 +138,10 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 获取对象名称 | * 获取对象名称 | ||||
| * | * | ||||
| * @param bucketName 桶名称 | |||||
| * @param prefix 前缀 | |||||
| * @return | |||||
| * @throws Exception | |||||
| */ | */ | ||||
| public List<String> getObjects(String bucketName, String prefix)throws Exception{ | public List<String> getObjects(String bucketName, String prefix)throws Exception{ | ||||
| List<String> fileNames = new ArrayList<>(); | List<String> fileNames = new ArrayList<>(); | ||||
| @@ -152,6 +156,10 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 获取文件流 | * 获取文件流 | ||||
| * | * | ||||
| * @param bucket 桶 | |||||
| * @param objectName 对象名称 | |||||
| * @return | |||||
| * @throws Exception | |||||
| */ | */ | ||||
| public InputStream getObjectInputStream(String bucket,String objectName)throws Exception{ | public InputStream getObjectInputStream(String bucket,String objectName)throws Exception{ | ||||
| return client.getObject(bucket, objectName); | return client.getObject(bucket, objectName); | ||||
| @@ -160,9 +168,9 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 文件夹复制 | * 文件夹复制 | ||||
| * | * | ||||
| * @param bucket | |||||
| * @param bucket 桶 | |||||
| * @param sourceFiles 源文件 | * @param sourceFiles 源文件 | ||||
| * @param targetDir 目标文件夹 | |||||
| * @param targetDir 目标文件夹 | |||||
| */ | */ | ||||
| public void copyDir(String bucket, List<String> sourceFiles, String targetDir) { | public void copyDir(String bucket, List<String> sourceFiles, String targetDir) { | ||||
| sourceFiles.forEach(sourceFile -> { | sourceFiles.forEach(sourceFile -> { | ||||
| @@ -211,10 +219,10 @@ public class MinioUtil { | |||||
| /** | /** | ||||
| * 生成文件下载请求参数方法 | * 生成文件下载请求参数方法 | ||||
| * | * | ||||
| * @param bucketName | |||||
| * @param prefix | |||||
| * @param objects | |||||
| * @return MinioDownloadDto | |||||
| * @param bucketName 桶名称 | |||||
| * @param prefix 前缀 | |||||
| * @param objects 对象名称 | |||||
| * @return MinioDownloadDto 下载请求参数 | |||||
| */ | */ | ||||
| public MinioDownloadDto getDownloadParam(String bucketName, String prefix, List<String> objects, String zipName) { | public MinioDownloadDto getDownloadParam(String bucketName, String prefix, List<String> objects, String zipName) { | ||||
| String paramTemplate = "{\"id\":%d,\"jsonrpc\":\"%s\",\"params\":{\"username\":\"%s\",\"password\":\"%s\"},\"method\":\"%s\"}"; | String paramTemplate = "{\"id\":%d,\"jsonrpc\":\"%s\",\"params\":{\"username\":\"%s\",\"password\":\"%s\"},\"method\":\"%s\"}"; | ||||
| @@ -17,6 +17,7 @@ | |||||
| package org.dubhe.utils; | package org.dubhe.utils; | ||||
| import cn.hutool.core.util.StrUtil; | |||||
| import com.emc.ecs.nfsclient.nfs.io.Nfs3File; | import com.emc.ecs.nfsclient.nfs.io.Nfs3File; | ||||
| import com.emc.ecs.nfsclient.nfs.io.NfsFileInputStream; | import com.emc.ecs.nfsclient.nfs.io.NfsFileInputStream; | ||||
| import com.emc.ecs.nfsclient.nfs.io.NfsFileOutputStream; | import com.emc.ecs.nfsclient.nfs.io.NfsFileOutputStream; | ||||
| @@ -31,7 +32,6 @@ import org.dubhe.exception.NfsBizException; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.CollectionUtils; | import org.springframework.util.CollectionUtils; | ||||
| import org.springframework.util.FileCopyUtils; | |||||
| import java.io.*; | import java.io.*; | ||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| @@ -130,14 +130,14 @@ public class NfsUtil { | |||||
| } | } | ||||
| /** | /** | ||||
| * 校验文件或文件夹是否存在 | |||||
| * 校验文件或文件夹是否不存在 | |||||
| * | * | ||||
| * @param path 文件路径 | * @param path 文件路径 | ||||
| * @return boolean | * @return boolean | ||||
| */ | */ | ||||
| public boolean fileOrDirIsEmpty(String path) { | public boolean fileOrDirIsEmpty(String path) { | ||||
| if (!StringUtils.isEmpty(path)) { | if (!StringUtils.isEmpty(path)) { | ||||
| path = formatPath(path); | |||||
| path = formatPath(path.startsWith(nfsConfig.getRootDir()) ? path.replaceFirst(nfsConfig.getRootDir(), StrUtil.SLASH) : path); | |||||
| Nfs3File nfs3File = getNfs3File(path); | Nfs3File nfs3File = getNfs3File(path); | ||||
| try { | try { | ||||
| if (nfs3File.exists()) { | if (nfs3File.exists()) { | ||||
| @@ -398,30 +398,6 @@ public class NfsUtil { | |||||
| } | } | ||||
| /** | |||||
| * NFS 复制目录到指定目录下 多个文件 包含目录与文件并存情况 | |||||
| * | |||||
| * 通过本地文件复制方式 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件目录 例如:/abc/def | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean copyPath(String sourcePath, String targetPath) { | |||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | |||||
| return false; | |||||
| } | |||||
| sourcePath = formatPath(sourcePath); | |||||
| targetPath = formatPath(targetPath); | |||||
| try { | |||||
| return copyLocalPath(nfsConfig.getRootDir() + sourcePath, nfsConfig.getRootDir() + targetPath); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.NFS_UTIL, " copyPath 复制失败: ", e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * NFS 复制目录到指定目录下 多个文件 包含目录与文件并存情况 | * NFS 复制目录到指定目录下 多个文件 包含目录与文件并存情况 | ||||
| * | * | ||||
| @@ -468,126 +444,15 @@ public class NfsUtil { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 复制文件到指定目录下 单个文件 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件 例如:/abc/def/cc.txt | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean copyLocalFile(String sourcePath, String targetPath) { | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "复制文件原路径: {} ,目标路径: {}", sourcePath, targetPath); | |||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | |||||
| return false; | |||||
| } | |||||
| sourcePath = formatPath(sourcePath); | |||||
| targetPath = formatPath(targetPath); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "过滤后文件原路径: {} ,目标路径:{}", sourcePath, targetPath); | |||||
| try (InputStream input = new FileInputStream(sourcePath); | |||||
| FileOutputStream output = new FileOutputStream(targetPath)) { | |||||
| FileCopyUtils.copy(input, output); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "复制文件成功"); | |||||
| return true; | |||||
| } catch (IOException e) { | |||||
| LogUtil.error(LogEnum.NFS_UTIL, " copyLocalFile 复制失败: ", e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 复制文件 到指定目录下 多个文件 包含目录与文件并存情况 | |||||
| * | |||||
| * @param sourcePath 需要复制的文件目录 例如:/abc/def | |||||
| * @param targetPath 需要放置的目标目录 例如:/abc/dd | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean copyLocalPath(String sourcePath, String targetPath) { | |||||
| if (!StringUtils.isEmpty(sourcePath) && !StringUtils.isEmpty(targetPath)) { | |||||
| sourcePath = formatPath(sourcePath); | |||||
| if (sourcePath.endsWith(FILE_SEPARATOR)) { | |||||
| sourcePath = sourcePath.substring(MagicNumConstant.ZERO, sourcePath.lastIndexOf(FILE_SEPARATOR)); | |||||
| } | |||||
| targetPath = formatPath(targetPath); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "复制文件夹 原路径: {} , 目标路径 : {} ", sourcePath, targetPath); | |||||
| File[] files = new File(sourcePath).listFiles(); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "需要复制的文件数量为: {}", files.length); | |||||
| if (files.length != 0) { | |||||
| for (File file : files) { | |||||
| try { | |||||
| if (file.isDirectory()) { | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "需要复制夹: {}", file.getAbsolutePath()); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "目标文件夹: {}", targetPath + FILE_SEPARATOR + file.getName()); | |||||
| File fileDir = new File(targetPath + FILE_SEPARATOR + file.getName()); | |||||
| if(!fileDir.exists()){ | |||||
| fileDir.mkdirs(); | |||||
| } | |||||
| copyLocalPath(sourcePath + FILE_SEPARATOR + file.getName(), targetPath + FILE_SEPARATOR + file.getName()); | |||||
| } | |||||
| if (file.isFile()) { | |||||
| File fileTargetPath = new File(targetPath); | |||||
| if(!fileTargetPath.exists()){ | |||||
| fileTargetPath.mkdirs(); | |||||
| } | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "需要复制文件: {}", file.getAbsolutePath()); | |||||
| LogUtil.info(LogEnum.NFS_UTIL, "需要复制文件名称: {}", file.getName()); | |||||
| copyLocalFile(file.getAbsolutePath() , targetPath + FILE_SEPARATOR + file.getName()); | |||||
| } | |||||
| }catch (Exception e){ | |||||
| LogUtil.error(LogEnum.NFS_UTIL, "复制文件夹失败: {}", e); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * 解压前清理同路径下其他文件(目前只支持路径下无文件夹,文件均为zip文件) | |||||
| * 上传路径垃圾文件清理 | |||||
| * | |||||
| * @param zipFilePath zip源文件 例如:/abc/z.zip | |||||
| * @param path 文件夹 例如:/abc/ | |||||
| * @return boolean | |||||
| */ | |||||
| public boolean cleanPath(String zipFilePath, String path) { | |||||
| if (!StringUtils.isEmpty(zipFilePath) && !StringUtils.isEmpty(path) && zipFilePath.toLowerCase().endsWith(ZIP)) { | |||||
| zipFilePath = formatPath(zipFilePath); | |||||
| path = formatPath(path); | |||||
| Nfs3File nfs3Files = getNfs3File(path); | |||||
| try { | |||||
| String zipName = zipFilePath.substring(zipFilePath.lastIndexOf(FILE_SEPARATOR) + MagicNumConstant.ONE); | |||||
| if (!StringUtils.isEmpty(zipName)) { | |||||
| List<Nfs3File> nfs3FilesList = nfs3Files.listFiles(); | |||||
| if (!CollectionUtils.isEmpty(nfs3FilesList)) { | |||||
| for (Nfs3File nfs3File : nfs3FilesList) { | |||||
| if (!zipName.equals(nfs3File.getName())) { | |||||
| nfs3File.delete(); | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.NFS_UTIL, "路径{}清理失败,错误原因为:{} ", path, e); | |||||
| return false; | |||||
| } finally { | |||||
| nfsPool.revertNfs(nfs3Files.getNfs()); | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | /** | ||||
| * zip解压并删除压缩文件 | * zip解压并删除压缩文件 | ||||
| * | |||||
| * 当压缩包文件较多时,可能会因为RPC超时而解压失败 | |||||
| * 该方法已废弃,请使用org.dubhe.utils.LocalFileUtil类的unzipLocalPath方法来替代 | |||||
| * @param sourcePath zip源文件 例如:/abc/z.zip | * @param sourcePath zip源文件 例如:/abc/z.zip | ||||
| * @param targetPath 解压后的目标文件夹 例如:/abc/ | * @param targetPath 解压后的目标文件夹 例如:/abc/ | ||||
| * @return boolean | * @return boolean | ||||
| */ | */ | ||||
| @Deprecated | |||||
| public boolean unzip(String sourcePath, String targetPath) { | public boolean unzip(String sourcePath, String targetPath) { | ||||
| if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | if (StringUtils.isEmpty(sourcePath) || StringUtils.isEmpty(targetPath)) { | ||||
| return false; | return false; | ||||
| @@ -894,11 +759,15 @@ public class NfsUtil { | |||||
| * @param path | * @param path | ||||
| * @return String | * @return String | ||||
| */ | */ | ||||
| private String formatPath(String path) { | |||||
| public String formatPath(String path) { | |||||
| if (!StringUtils.isEmpty(path)) { | if (!StringUtils.isEmpty(path)) { | ||||
| return path.replaceAll("///*", FILE_SEPARATOR); | return path.replaceAll("///*", FILE_SEPARATOR); | ||||
| } | } | ||||
| return path; | return path; | ||||
| } | } | ||||
| public String getAbsolutePath(String relativePath) { | |||||
| return nfsConfig.getRootDir() + nfsConfig.getBucket() + relativePath; | |||||
| } | |||||
| } | } | ||||
| @@ -26,6 +26,9 @@ import org.springframework.data.redis.core.Cursor; | |||||
| import org.springframework.data.redis.core.RedisConnectionUtils; | import org.springframework.data.redis.core.RedisConnectionUtils; | ||||
| import org.springframework.data.redis.core.RedisTemplate; | import org.springframework.data.redis.core.RedisTemplate; | ||||
| import org.springframework.data.redis.core.ScanOptions; | import org.springframework.data.redis.core.ScanOptions; | ||||
| import org.springframework.data.redis.core.script.DefaultRedisScript; | |||||
| import org.springframework.data.redis.core.script.RedisScript; | |||||
| import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; | |||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.CollectionUtils; | import org.springframework.util.CollectionUtils; | ||||
| @@ -33,7 +36,7 @@ import java.util.*; | |||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
| /** | /** | ||||
| * @description redis工具类 | |||||
| * @description redis工具类 | |||||
| * @date 2020-03-13 | * @date 2020-03-13 | ||||
| */ | */ | ||||
| @Component | @Component | ||||
| @@ -62,7 +65,7 @@ public class RedisUtils { | |||||
| redisTemplate.expire(key, time, TimeUnit.SECONDS); | redisTemplate.expire(key, time, TimeUnit.SECONDS); | ||||
| } | } | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils expire key {} time {} error:{}",key,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils expire key {} time {} error:{}", key, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | return true; | ||||
| @@ -82,7 +85,7 @@ public class RedisUtils { | |||||
| * 查找匹配key | * 查找匹配key | ||||
| * | * | ||||
| * @param pattern key | * @param pattern key | ||||
| * @return / | |||||
| * @return List<String> 匹配的key集合 | |||||
| */ | */ | ||||
| public List<String> scan(String pattern) { | public List<String> scan(String pattern) { | ||||
| ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); | ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); | ||||
| @@ -94,9 +97,9 @@ public class RedisUtils { | |||||
| result.add(new String(cursor.next())); | result.add(new String(cursor.next())); | ||||
| } | } | ||||
| try { | try { | ||||
| RedisConnectionUtils.releaseConnection(rc, factory,true); | |||||
| RedisConnectionUtils.releaseConnection(rc, factory, true); | |||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils scan pattern {} error:{}",pattern,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils scan pattern {} error:{}", pattern, e.getMessage(), e); | |||||
| } | } | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -107,7 +110,7 @@ public class RedisUtils { | |||||
| * @param patternKey key | * @param patternKey key | ||||
| * @param page 页码 | * @param page 页码 | ||||
| * @param size 每页数目 | * @param size 每页数目 | ||||
| * @return / | |||||
| * @return 匹配到的key集合 | |||||
| */ | */ | ||||
| public List<String> findKeysForPage(String patternKey, int page, int size) { | public List<String> findKeysForPage(String patternKey, int page, int size) { | ||||
| ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); | ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); | ||||
| @@ -132,9 +135,9 @@ public class RedisUtils { | |||||
| cursor.next(); | cursor.next(); | ||||
| } | } | ||||
| try { | try { | ||||
| RedisConnectionUtils.releaseConnection(rc, factory,true); | |||||
| RedisConnectionUtils.releaseConnection(rc, factory, true); | |||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils findKeysForPage patternKey {} page {} size {} error:{}",patternKey,page,size,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils findKeysForPage patternKey {} page {} size {} error:{}", patternKey, page, size, e.getMessage(), e); | |||||
| } | } | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -149,7 +152,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.hasKey(key); | return redisTemplate.hasKey(key); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils hasKey key {} error:{}",key,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils hasKey key {} error:{}", key, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -175,7 +178,7 @@ public class RedisUtils { | |||||
| * 普通缓存获取 | * 普通缓存获取 | ||||
| * | * | ||||
| * @param key 键 | * @param key 键 | ||||
| * @return 值 | |||||
| * @return key对应的value值 | |||||
| */ | */ | ||||
| public Object get(String key) { | public Object get(String key) { | ||||
| @@ -185,8 +188,8 @@ public class RedisUtils { | |||||
| /** | /** | ||||
| * 批量获取 | * 批量获取 | ||||
| * | * | ||||
| * @param keys | |||||
| * @return | |||||
| * @param keys key集合 | |||||
| * @return key集合对应的value集合 | |||||
| */ | */ | ||||
| public List<Object> multiGet(List<String> keys) { | public List<Object> multiGet(List<String> keys) { | ||||
| Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys)); | Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys)); | ||||
| @@ -205,7 +208,7 @@ public class RedisUtils { | |||||
| redisTemplate.opsForValue().set(key, value); | redisTemplate.opsForValue().set(key, value); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils set key {} value {} error:{}",key,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils set key {} value {} error:{}", key, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -227,7 +230,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils set key {} value {} time {} error:{}",key,value,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils set key {} value {} time {} error:{}", key, value, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -250,11 +253,56 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils set key {} value {} time {} timeUnit {} error:{}",key,value,time,timeUnit,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils set key {} value {} time {} timeUnit {} error:{}", key, value, time, timeUnit, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| //===============================Lock================================= | |||||
| /** | |||||
| * 加锁 | |||||
| * @param key 键 | |||||
| * @param requestId 请求id用以释放锁 | |||||
| * @param expireTime 超时时间(秒) | |||||
| * @return | |||||
| */ | |||||
| public boolean getDistributedLock(String key, String requestId, long expireTime) { | |||||
| String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end else return 0 end"; | |||||
| Object result = executeRedisScript(script, key, requestId, expireTime); | |||||
| return result != null && result.equals(MagicNumConstant.ONE_LONG); | |||||
| } | |||||
| /** | |||||
| * 释放锁 | |||||
| * @param key 键 | |||||
| * @param requestId 请求id用以释放锁 | |||||
| * @return | |||||
| */ | |||||
| public boolean releaseDistributedLock(String key, String requestId) { | |||||
| String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; | |||||
| Object result = executeRedisScript(script, key, requestId); | |||||
| return result != null && result.equals(MagicNumConstant.ONE_LONG); | |||||
| } | |||||
| /** | |||||
| * | |||||
| * @param script 脚本字符串 | |||||
| * @param key 键 | |||||
| * @param args 脚本其他参数 | |||||
| * @return | |||||
| */ | |||||
| public Object executeRedisScript(String script, String key, Object... args) { | |||||
| try { | |||||
| RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); | |||||
| redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); | |||||
| return redisTemplate.execute(redisScript, Collections.singletonList(key), args); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.SYS_ERR, "executeRedisScript script {} key {} expireTime {} args {} error:{}", script, key, args, e); | |||||
| return MagicNumConstant.ZERO_LONG; | |||||
| } | |||||
| } | |||||
| // ================================Map================================= | // ================================Map================================= | ||||
| /** | /** | ||||
| @@ -291,7 +339,7 @@ public class RedisUtils { | |||||
| redisTemplate.opsForHash().putAll(key, map); | redisTemplate.opsForHash().putAll(key, map); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils hmset key {} map {} error:{}",key,map,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils hmset key {} map {} error:{}", key, map, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -312,7 +360,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils hmset key {} map {} time {} error:{}",key,map,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils hmset key {} map {} time {} error:{}", key, map, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -330,7 +378,7 @@ public class RedisUtils { | |||||
| redisTemplate.opsForHash().put(key, item, value); | redisTemplate.opsForHash().put(key, item, value); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils hset key {} item {} value {} error:{}",key,item,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils hset key {} item {} value {} error:{}", key, item, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -352,7 +400,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils hset key {} item {} value {} time {} error:{}",key,item,value,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils hset key {} item {} value {} time {} error:{}", key, item, value, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -414,7 +462,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForSet().members(key); | return redisTemplate.opsForSet().members(key); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils sGet key {} error:{}",key,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils sGet key {} error:{}", key, e.getMessage(), e); | |||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| @@ -430,7 +478,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForSet().isMember(key, value); | return redisTemplate.opsForSet().isMember(key, value); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils sHasKey key {} value {} error:{}",key,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils sHasKey key {} value {} error:{}", key, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -446,7 +494,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForSet().add(key, values); | return redisTemplate.opsForSet().add(key, values); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils sSet key {} values {} error:{}",key,values,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils sSet key {} values {} error:{}", key, values, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -467,7 +515,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return count; | return count; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils sSetAndTime key {} time {} values {} error:{}",key,time,values,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils sSetAndTime key {} time {} values {} error:{}", key, time, values, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -482,7 +530,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForSet().size(key); | return redisTemplate.opsForSet().size(key); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils sGetSetSize key {} error:{}",key,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils sGetSetSize key {} error:{}", key, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -499,7 +547,7 @@ public class RedisUtils { | |||||
| Long count = redisTemplate.opsForSet().remove(key, values); | Long count = redisTemplate.opsForSet().remove(key, values); | ||||
| return count; | return count; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils setRemove key {} values {} error:{}",key,values,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils setRemove key {} values {} error:{}", key, values, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -507,22 +555,22 @@ public class RedisUtils { | |||||
| // ===============================sorted set================================= | // ===============================sorted set================================= | ||||
| /** | /** | ||||
| *将zSet数据放入缓存 | |||||
| * 将zSet数据放入缓存 | |||||
| * | * | ||||
| * @param key | * @param key | ||||
| * @param time | * @param time | ||||
| * @param values | * @param values | ||||
| * @return Boolean | * @return Boolean | ||||
| */ | */ | ||||
| public Boolean zSet(String key, long time, Object value){ | |||||
| public Boolean zSet(String key, long time, Object value) { | |||||
| try { | try { | ||||
| Boolean success = redisTemplate.opsForZSet().add(key, value,System.currentTimeMillis()); | |||||
| Boolean success = redisTemplate.opsForZSet().add(key, value, System.currentTimeMillis()); | |||||
| if (success) { | if (success) { | ||||
| expire(key, time); | expire(key, time); | ||||
| } | } | ||||
| return success; | return success; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils zSet key {} time {} value {} error:{}",key,time,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils zSet key {} time {} value {} error:{}", key, time, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -533,17 +581,16 @@ public class RedisUtils { | |||||
| * @param key | * @param key | ||||
| * @return Set<Object> | * @return Set<Object> | ||||
| */ | */ | ||||
| public Set<Object> zGet(String key){ | |||||
| public Set<Object> zGet(String key) { | |||||
| try { | try { | ||||
| return redisTemplate.opsForZSet().reverseRange(key,Long.MIN_VALUE, Long.MAX_VALUE); | |||||
| return redisTemplate.opsForZSet().reverseRange(key, Long.MIN_VALUE, Long.MAX_VALUE); | |||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils zGet key {} error:{}",key,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils zGet key {} error:{}", key, e.getMessage(), e); | |||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| // ===============================list================================= | // ===============================list================================= | ||||
| /** | /** | ||||
| @@ -558,7 +605,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForList().range(key, start, end); | return redisTemplate.opsForList().range(key, start, end); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lGetIndex key {} start {} end {} error:{}",key,start,end,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lGetIndex key {} start {} end {} error:{}", key, start, end, e.getMessage(), e); | |||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| @@ -573,7 +620,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForList().size(key); | return redisTemplate.opsForList().size(key); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lGetListSize key {} error:{}",key,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lGetListSize key {} error:{}", key, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -589,7 +636,7 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForList().index(key, index); | return redisTemplate.opsForList().index(key, index); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lGetIndex key {} index {} error:{}",key,index,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lGetIndex key {} index {} error:{}", key, index, e.getMessage(), e); | |||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| @@ -606,7 +653,7 @@ public class RedisUtils { | |||||
| redisTemplate.opsForList().rightPush(key, value); | redisTemplate.opsForList().rightPush(key, value); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lSet key {} value {} error:{}",key,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lSet key {} value {} error:{}", key, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -617,7 +664,7 @@ public class RedisUtils { | |||||
| * @param key 键 | * @param key 键 | ||||
| * @param value 值 | * @param value 值 | ||||
| * @param time 时间(秒) | * @param time 时间(秒) | ||||
| * @return | |||||
| * @return 是否存储成功 | |||||
| */ | */ | ||||
| public boolean lSet(String key, Object value, long time) { | public boolean lSet(String key, Object value, long time) { | ||||
| try { | try { | ||||
| @@ -627,7 +674,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lSet key {} value {} time {} error:{}",key,value,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lSet key {} value {} time {} error:{}", key, value, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -637,14 +684,14 @@ public class RedisUtils { | |||||
| * | * | ||||
| * @param key 键 | * @param key 键 | ||||
| * @param value 值 | * @param value 值 | ||||
| * @return | |||||
| * @return 是否存储成功 | |||||
| */ | */ | ||||
| public boolean lSet(String key, List<Object> value) { | public boolean lSet(String key, List<Object> value) { | ||||
| try { | try { | ||||
| redisTemplate.opsForList().rightPushAll(key, value); | redisTemplate.opsForList().rightPushAll(key, value); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lSet key {} value {} error:{}",key,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lSet key {} value {} error:{}", key, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -655,7 +702,7 @@ public class RedisUtils { | |||||
| * @param key 键 | * @param key 键 | ||||
| * @param value 值 | * @param value 值 | ||||
| * @param time 时间(秒) | * @param time 时间(秒) | ||||
| * @return | |||||
| * @return 是否存储成功 | |||||
| */ | */ | ||||
| public boolean lSet(String key, List<Object> value, long time) { | public boolean lSet(String key, List<Object> value, long time) { | ||||
| try { | try { | ||||
| @@ -665,7 +712,7 @@ public class RedisUtils { | |||||
| } | } | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lSet key {} value {} time {} error:{}",key,value,time,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lSet key {} value {} time {} error:{}", key, value, time, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -676,14 +723,14 @@ public class RedisUtils { | |||||
| * @param key 键 | * @param key 键 | ||||
| * @param index 索引 | * @param index 索引 | ||||
| * @param value 值 | * @param value 值 | ||||
| * @return / | |||||
| * @return 更新数据标识 | |||||
| */ | */ | ||||
| public boolean lUpdateIndex(String key, long index, Object value) { | public boolean lUpdateIndex(String key, long index, Object value) { | ||||
| try { | try { | ||||
| redisTemplate.opsForList().set(key, index, value); | redisTemplate.opsForList().set(key, index, value); | ||||
| return true; | return true; | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lUpdateIndex key {} index {} value {} error:{}",key,index,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lUpdateIndex key {} index {} value {} error:{}", key, index, value, e.getMessage(), e); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -700,8 +747,23 @@ public class RedisUtils { | |||||
| try { | try { | ||||
| return redisTemplate.opsForList().remove(key, count, value); | return redisTemplate.opsForList().remove(key, count, value); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.SYS_ERR,"RedisUtils lRemove key {} count {} value {} error:{}",key,count,value,e.getMessage(),e); | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lRemove key {} count {} value {} error:{}", key, count, value, e.getMessage(), e); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 队列从左弹出数据 | |||||
| * | |||||
| * @param key | |||||
| * @return key对应的value值 | |||||
| */ | |||||
| public Object lpop(String key) { | |||||
| try { | |||||
| return redisTemplate.opsForList().leftPop(key); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.SYS_ERR, "RedisUtils lRemove key {} error:{}", key, e.getMessage(), e); | |||||
| return null; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -18,7 +18,8 @@ | |||||
| package org.dubhe.utils; | package org.dubhe.utils; | ||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||
| import java.util.*; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | /** | ||||
| * @description 反射工具类 | * @description 反射工具类 | ||||
| @@ -23,8 +23,8 @@ import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
| /** | /** | ||||
| * @description: 正则匹配工具类 | |||||
| * @create: 2020/4/23 13:51 | |||||
| * @description 正则匹配工具类 | |||||
| * @date 2020-04-23 | |||||
| */ | */ | ||||
| @Slf4j | @Slf4j | ||||
| public class RegexUtil { | public class RegexUtil { | ||||
| @@ -17,11 +17,18 @@ | |||||
| package org.dubhe.utils; | package org.dubhe.utils; | ||||
| import org.dubhe.base.BaseService; | |||||
| import org.dubhe.base.DataContext; | |||||
| import java.util.HashSet; | |||||
| import java.util.Objects; | |||||
| import java.util.Set; | |||||
| /** | /** | ||||
| * @description sql语句转换的工具类 | * @description sql语句转换的工具类 | ||||
| * @date 2020-07-06 | * @date 2020-07-06 | ||||
| */ | */ | ||||
| public class SqlUtil { | public class SqlUtil { | ||||
| /** | /** | ||||
| @@ -46,4 +53,46 @@ public class SqlUtil { | |||||
| return ""; | return ""; | ||||
| } | } | ||||
| /** | |||||
| * 获取资源拥有着ID | |||||
| * | |||||
| * @return 资源拥有者id集合 | |||||
| */ | |||||
| public static Set<Long> getResourceIds() { | |||||
| if (!Objects.isNull(DataContext.get())) { | |||||
| return DataContext.get().getResourceUserIds(); | |||||
| } | |||||
| Set<Long> ids = new HashSet<>(); | |||||
| Long id = JwtUtils.getCurrentUserDto().getId(); | |||||
| ids.add(id); | |||||
| return ids; | |||||
| } | |||||
| /** | |||||
| * 构建目标sql语句 | |||||
| * | |||||
| * @param originSql 原生sql | |||||
| * @param resourceUserIds 所属资源用户ids | |||||
| * @return 目标sql | |||||
| */ | |||||
| public static String buildTargetSql(String originSql, Set<Long> resourceUserIds) { | |||||
| if (BaseService.isAdmin()) { | |||||
| return originSql; | |||||
| } | |||||
| String sqlWhereBefore = org.dubhe.utils.StringUtils.substringBefore(originSql.toLowerCase(), "where"); | |||||
| String sqlWhereAfter = org.dubhe.utils.StringUtils.substringAfter(originSql.toLowerCase(), "where"); | |||||
| StringBuffer buffer = new StringBuffer(); | |||||
| //操作的sql拼接 | |||||
| String targetSql = buffer.append(sqlWhereBefore).append(" where ").append(" origin_user_id in (") | |||||
| .append(org.dubhe.utils.StringUtils.join(resourceUserIds, ",")).append(") and ").append(sqlWhereAfter).toString(); | |||||
| return targetSql; | |||||
| } | |||||
| } | } | ||||
| @@ -273,4 +273,41 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { | |||||
| matcher.appendTail(sb); | matcher.appendTail(sb); | ||||
| return sb.toString(); | return sb.toString(); | ||||
| } | } | ||||
| /** | |||||
| * 字符串截取前 | |||||
| * @param str | |||||
| * @return | |||||
| */ | |||||
| public static String substringBefore(String str, String separator){ | |||||
| if (!isEmpty(str) && separator != null) { | |||||
| if (separator.isEmpty()) { | |||||
| return ""; | |||||
| } else { | |||||
| int pos = str.indexOf(separator); | |||||
| return pos == -1 ? str : str.substring(0, pos); | |||||
| } | |||||
| } else { | |||||
| return str; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 字符串截取后 | |||||
| * @param str | |||||
| * @return | |||||
| */ | |||||
| public static String substringAfter(String str, String separator){ | |||||
| if (isEmpty(str)) { | |||||
| return str; | |||||
| } else if (separator == null) { | |||||
| return ""; | |||||
| } else { | |||||
| int pos = str.indexOf(separator); | |||||
| return pos == -1 ? "" : str.substring(pos + separator.length()); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -17,42 +17,33 @@ | |||||
| package org.dubhe.utils; | package org.dubhe.utils; | ||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import java.text.ParseException; | |||||
| import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||
| import java.util.Calendar; | import java.util.Calendar; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import static org.dubhe.base.MagicNumConstant.EIGHT; | |||||
| /** | /** | ||||
| * @description: UTC时间转换CST时间工具类 | |||||
| * @create: 2020/5/20 12:10 | |||||
| * @description 时间格式转换工具类 | |||||
| * @date 2020-05-20 | |||||
| */ | */ | ||||
| @Slf4j | |||||
| public class TimeTransferUtil { | public class TimeTransferUtil { | ||||
| private static final String UTC_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.sss'Z'"; | |||||
| /** | /** | ||||
| * @param utcTime | |||||
| * @return cstTime | |||||
| * Date转换为UTC时间 | |||||
| * | |||||
| * @param date | |||||
| * @return utcTime | |||||
| */ | */ | ||||
| public static String cstTransfer(String utcTime){ | |||||
| Date utcDate = null; | |||||
| /**2020-05-20T03:13:22Z 对应的时间格式 yyyy-MM-dd'T'HH:mm:ss'Z'**/ | |||||
| SimpleDateFormat utcSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); | |||||
| try { | |||||
| utcDate = utcSimpleDateFormat.parse(utcTime); | |||||
| } catch (ParseException e) { | |||||
| log.info(e.getMessage()); | |||||
| return null; | |||||
| } | |||||
| /**System.out.println("UTC时间:"+date);**/ | |||||
| public static String dateTransferToUtc(Date date){ | |||||
| Calendar calendar = Calendar.getInstance(); | Calendar calendar = Calendar.getInstance(); | ||||
| calendar.setTime(utcDate); | |||||
| calendar.set(Calendar.HOUR,calendar.get(Calendar.HOUR)+8); | |||||
| SimpleDateFormat cstSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |||||
| Date cstDate = calendar.getTime(); | |||||
| String cstTime = cstSimpleDateFormat.format(calendar.getTime()); | |||||
| return cstTime; | |||||
| calendar.setTime(date); | |||||
| /**UTC时间与CST时间相差8小时**/ | |||||
| calendar.set(Calendar.HOUR,calendar.get(Calendar.HOUR) - EIGHT); | |||||
| SimpleDateFormat utcSimpleDateFormat = new SimpleDateFormat(UTC_FORMAT); | |||||
| Date utcDate = calendar.getTime(); | |||||
| return utcSimpleDateFormat.format(utcDate); | |||||
| } | } | ||||
| } | } | ||||
| @@ -25,9 +25,8 @@ import java.util.concurrent.ConcurrentHashMap; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||
| /** | /** | ||||
| * @description: 唯一码生成器 (依赖时间轴) | |||||
| * | |||||
| * @date 2020.05.08 | |||||
| * @description 唯一码生成器 (依赖时间轴) | |||||
| * @date 2020-05-08 | |||||
| */ | */ | ||||
| public class UniqueKeyGenerator { | public class UniqueKeyGenerator { | ||||
| @@ -20,7 +20,6 @@ package org.dubhe.utils; | |||||
| import cn.hutool.core.util.ObjectUtil; | import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import lombok.val; | |||||
| import org.dubhe.annotation.Query; | import org.dubhe.annotation.Query; | ||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||
| @@ -31,7 +30,7 @@ import java.util.List; | |||||
| /** | /** | ||||
| * @description 构建Wrapper | * @description 构建Wrapper | ||||
| * @date 2020-03-15 13:52:30 | |||||
| * @date 2020-03-15 | |||||
| */ | */ | ||||
| @Slf4j | @Slf4j | ||||
| public class WrapperHelp { | public class WrapperHelp { | ||||
| @@ -23,8 +23,8 @@ import org.junit.Test; | |||||
| import static org.dubhe.utils.HttpUtils.isSuccess; | import static org.dubhe.utils.HttpUtils.isSuccess; | ||||
| /** | /** | ||||
| * @description: HttpUtil | |||||
| * @date 2020.04.30 | |||||
| * @description HttpUtil | |||||
| * @date 2020-04-30 | |||||
| */ | */ | ||||
| public class HttpUtilsTest { | public class HttpUtilsTest { | ||||
| @@ -0,0 +1,71 @@ | |||||
| #!/bin/bash | |||||
| PROG_NAME=$0 | |||||
| ACTION=$1 | |||||
| ENV=$2 | |||||
| APP_HOME=$3 | |||||
| APP_NAME=dubhe-${ENV} | |||||
| APP_HOME=$APP_HOME/${APP_NAME} # 从package.tgz中解压出来的jar包放到这个目录下 | |||||
| JAR_NAME=${APP_HOME}/dubhe-admin/target/dubhe-admin-1.0-exec.jar # jar包的名字 | |||||
| JAVA_OUT=/dev/null | |||||
| # 创建出相关目录 | |||||
| mkdir -p ${APP_HOME} | |||||
| mkdir -p ${APP_HOME}/logs | |||||
| usage() { | |||||
| echo "Usage: $PROG_NAME {start|stop|restart} {dev|test|prod}" | |||||
| exit 2 | |||||
| } | |||||
| start_application() { | |||||
| echo "starting java process" | |||||
| echo "nohup java -jar ${JAR_NAME} > ${JAVA_OUT} --spring.profiles.active=${ENV} 2>&1 &" | |||||
| nohup java -jar ${JAR_NAME} > ${JAVA_OUT} --spring.profiles.active=${ENV} 2>&1 & | |||||
| echo "started java process" | |||||
| } | |||||
| stop_application() { | |||||
| checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'deploy.sh'| awk '{print$2}'` | |||||
| if [ -z $checkjavapid ];then | |||||
| echo -e "\rno java process "$checkjavapid | |||||
| return | |||||
| fi | |||||
| echo "stop java process" | |||||
| times=60 | |||||
| for e in $(seq 60) | |||||
| do | |||||
| sleep 1 | |||||
| COSTTIME=$(($times - $e )) | |||||
| checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'deploy.sh'| awk '{print$2}'` | |||||
| if [ "$checkjavapid" != "" ];then | |||||
| echo "kill "$checkjavapid | |||||
| kill -9 $checkjavapid | |||||
| echo -e "\r -- stopping java lasts `expr $COSTTIME` seconds." | |||||
| else | |||||
| echo -e "\rjava process has exited" | |||||
| break; | |||||
| fi | |||||
| done | |||||
| echo "" | |||||
| } | |||||
| case "$ACTION" in | |||||
| start) | |||||
| start_application | |||||
| ;; | |||||
| stop) | |||||
| stop_application | |||||
| ;; | |||||
| restart) | |||||
| stop_application | |||||
| start_application | |||||
| ;; | |||||
| *) | |||||
| usage | |||||
| ;; | |||||
| esac | |||||
| @@ -75,6 +75,7 @@ | |||||
| <configuration> | <configuration> | ||||
| <skip>false</skip> | <skip>false</skip> | ||||
| <fork>true</fork> | <fork>true</fork> | ||||
| <classifier>exec</classifier> | |||||
| </configuration> | </configuration> | ||||
| </plugin> | </plugin> | ||||
| <!-- 跳过单元测试 --> | <!-- 跳过单元测试 --> | ||||
| @@ -14,7 +14,7 @@ | |||||
| * limitations under the License. | * limitations under the License. | ||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.task; | |||||
| package org.dubhe.async; | |||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||
| import org.dubhe.base.ResponseCode; | import org.dubhe.base.ResponseCode; | ||||
| @@ -25,6 +25,7 @@ import org.dubhe.enums.ImageStateEnum; | |||||
| import org.dubhe.enums.LogEnum; | import org.dubhe.enums.LogEnum; | ||||
| import org.dubhe.exception.BusinessException; | import org.dubhe.exception.BusinessException; | ||||
| import org.dubhe.harbor.api.HarborApi; | import org.dubhe.harbor.api.HarborApi; | ||||
| import org.dubhe.utils.IOUtil; | |||||
| import org.dubhe.utils.LogUtil; | import org.dubhe.utils.LogUtil; | ||||
| import org.dubhe.utils.StringUtils; | import org.dubhe.utils.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| @@ -62,46 +63,21 @@ public class HarborImagePushAsync { | |||||
| String imageResource = trainHarborConfig.getAddress() + StrUtil.SLASH + trainHarborConfig.getModelName() | String imageResource = trainHarborConfig.getAddress() + StrUtil.SLASH + trainHarborConfig.getModelName() | ||||
| + StrUtil.SLASH + imageNameandTag; | + StrUtil.SLASH + imageNameandTag; | ||||
| String cmdStr = "docker login --username=" + trainHarborConfig.getUsername() + " " + trainHarborConfig.getAddress() + " --password=" + trainHarborConfig.getPassword() + " ; docker " + | String cmdStr = "docker login --username=" + trainHarborConfig.getUsername() + " " + trainHarborConfig.getAddress() + " --password=" + trainHarborConfig.getPassword() + " ; docker " + | ||||
| "load < " + imagePath + " |awk '{print $3}' |xargs -I str docker tag str " + imageResource + " ; docker push " + imageResource; | |||||
| "load < " + imagePath + " |awk '{print $3}' |xargs -I str docker tag str " + imageResource + " ; docker push " + imageResource + "; docker rmi " + imageResource; | |||||
| String[] cmd = {"/bin/bash", "-c", cmdStr}; | String[] cmd = {"/bin/bash", "-c", cmdStr}; | ||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "镜像上传执行脚本参数:{}", cmd); | LogUtil.info(LogEnum.BIZ_TRAIN, "镜像上传执行脚本参数:{}", cmd); | ||||
| Process process = Runtime.getRuntime().exec(cmd); | Process process = Runtime.getRuntime().exec(cmd); | ||||
| //读取标准输出流 | |||||
| BufferedReader brOut = new BufferedReader(new InputStreamReader(process.getInputStream())); | |||||
| //读取标准错误流 | |||||
| BufferedReader brErr = new BufferedReader(new InputStreamReader(process.getErrorStream())); | |||||
| String line; | |||||
| String outMessage = ""; | |||||
| String errMessage = ""; | |||||
| while ((line = brOut.readLine()) != null) { | |||||
| outMessage += line; | |||||
| } | |||||
| if (StringUtils.isNotEmpty(outMessage)) { | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "shell上传镜像输出信息:" + outMessage); | |||||
| } | |||||
| while ((line = brErr.readLine()) != null) { | |||||
| errMessage += line; | |||||
| } | |||||
| if (StringUtils.isNotEmpty(errMessage)) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "shell上传镜像异常信息:" + errMessage); | |||||
| } | |||||
| Integer status = process.waitFor(); | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "上传镜像状态:{}", status); | |||||
| if (status == null) { | |||||
| if (harborApi.isExistImage(ptImage.getImageUrl())) { | |||||
| updateImageStatus(ptImage, ImageStateEnum.SUCCESS.getCode()); | |||||
| } else { | |||||
| updateImageStatus(ptImage, ImageStateEnum.FAIL.getCode()); | |||||
| } | |||||
| } else if (status == 0) { | |||||
| if (checkImagePushIsOk(ptImage, process)) { | |||||
| updateImageStatus(ptImage, ImageStateEnum.SUCCESS.getCode()); | updateImageStatus(ptImage, ImageStateEnum.SUCCESS.getCode()); | ||||
| } else { | } else { | ||||
| updateImageStatus(ptImage, ImageStateEnum.FAIL.getCode()); | updateImageStatus(ptImage, ImageStateEnum.FAIL.getCode()); | ||||
| } | } | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "上传镜像异常:{}", e); | LogUtil.error(LogEnum.BIZ_TRAIN, "上传镜像异常:{}", e); | ||||
| updateImageStatus(ptImage, ImageStateEnum.FAIL.getCode()); | |||||
| throw new BusinessException("上传镜像异常!"); | throw new BusinessException("上传镜像异常!"); | ||||
| } | } | ||||
| } | } | ||||
| @@ -117,4 +93,52 @@ public class HarborImagePushAsync { | |||||
| ptImageMapper.updateById(ptImage); | ptImageMapper.updateById(ptImage); | ||||
| return ResponseCode.SUCCESS; | return ResponseCode.SUCCESS; | ||||
| } | } | ||||
| /** | |||||
| * 校验镜像是否上传成功 | |||||
| * | |||||
| * @param ptImage 镜像信息 | |||||
| * @param process process对象 | |||||
| * @return 是否上传成功 | |||||
| */ | |||||
| public boolean checkImagePushIsOk(PtImage ptImage, Process process) { | |||||
| //读取标准输出流 | |||||
| BufferedReader brOut = new BufferedReader(new InputStreamReader(process.getInputStream())); | |||||
| //读取标准错误流 | |||||
| BufferedReader brErr = new BufferedReader(new InputStreamReader(process.getErrorStream())); | |||||
| String line; | |||||
| StringBuilder outMessage = new StringBuilder(); | |||||
| StringBuilder errMessage = new StringBuilder(); | |||||
| boolean isPushOk = true; | |||||
| try { | |||||
| while ((line = brOut.readLine()) != null) { | |||||
| outMessage.append(line); | |||||
| } | |||||
| if (StringUtils.isNotEmpty(outMessage)) { | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "shell上传镜像输出信息:{}", outMessage.toString()); | |||||
| } | |||||
| while ((line = brErr.readLine()) != null) { | |||||
| errMessage.append(line); | |||||
| } | |||||
| if (StringUtils.isNotEmpty(errMessage)) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "shell上传镜像异常信息:{}", errMessage.toString()); | |||||
| } | |||||
| Integer status = process.waitFor(); | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "上传镜像状态:{}", status); | |||||
| if (status == null) { | |||||
| if (!harborApi.isExistImage(ptImage.getImageUrl())) { | |||||
| isPushOk = false; | |||||
| } | |||||
| } else if (status != 0) { | |||||
| isPushOk = false; | |||||
| } | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "上传镜像异常:{}", e); | |||||
| return false; | |||||
| } finally { | |||||
| IOUtil.close(brErr, brOut); | |||||
| } | |||||
| return isPushOk; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,144 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.async; | |||||
| import org.dubhe.config.TrainJobConfig; | |||||
| import org.dubhe.dao.PtTrainJobMapper; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.domain.entity.PtTrainJob; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.enums.TrainJobStatusEnum; | |||||
| import org.dubhe.enums.TrainTypeEnum; | |||||
| import org.dubhe.k8s.api.DistributeTrainApi; | |||||
| import org.dubhe.k8s.api.PodApi; | |||||
| import org.dubhe.k8s.api.TrainJobApi; | |||||
| import org.dubhe.k8s.domain.resource.BizPod; | |||||
| import org.dubhe.utils.*; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.scheduling.annotation.Async; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.time.LocalDateTime; | |||||
| import java.time.ZoneOffset; | |||||
| import java.time.format.DateTimeFormatter; | |||||
| import java.util.List; | |||||
| import java.util.function.Consumer; | |||||
| /** | |||||
| * @description 停止训练任务异步处理 | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| @Component | |||||
| public class StopTrainJobAsync { | |||||
| @Autowired | |||||
| private K8sNameTool k8sNameTool; | |||||
| @Autowired | |||||
| private PodApi podApi; | |||||
| @Autowired | |||||
| private TrainJobApi trainJobApi; | |||||
| @Autowired | |||||
| private TrainJobConfig trainJobConfig; | |||||
| @Autowired | |||||
| private DistributeTrainApi distributeTrainApi; | |||||
| @Autowired | |||||
| private PtTrainJobMapper ptTrainJobMapper; | |||||
| /** | |||||
| * 停止任务 | |||||
| * | |||||
| * @param currentUser 用户 | |||||
| * @param jobList 任务集合 | |||||
| */ | |||||
| @Async("trainExecutor") | |||||
| public void stopJobs(UserDTO currentUser, List<PtTrainJob> jobList) { | |||||
| String namespace = k8sNameTool.generateNamespace(currentUser.getId()); | |||||
| jobList.forEach(job -> { | |||||
| BizPod bizPod = podApi.getWithResourceName(namespace, job.getJobName()); | |||||
| if (!bizPod.isSuccess()) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "User {} stops training Job return code:{},message:{}", currentUser.getUsername(), Integer.valueOf(bizPod.getCode()), bizPod.getMessage()); | |||||
| } | |||||
| boolean bool = TrainTypeEnum.isDistributeTrain(job.getTrainType()) ? | |||||
| distributeTrainApi.deleteByResourceName(namespace, job.getJobName()).isSuccess() : | |||||
| trainJobApi.delete(namespace, job.getJobName()); | |||||
| if (!bool) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "User {} stops training Job and K8S fails in the stop process, namespace为{}, resourceName为{}", | |||||
| currentUser.getUsername(), namespace, job.getJobName()); | |||||
| } | |||||
| //更新训练状态 | |||||
| job.setRuntime(calculateRuntime(bizPod)) | |||||
| .setTrainStatus(TrainJobStatusEnum.STOP.getStatus()); | |||||
| ptTrainJobMapper.updateById(job); | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 计算job训练时长 | |||||
| * | |||||
| * @param bizPod pod信息 | |||||
| * @return String 训练时长 | |||||
| */ | |||||
| private String calculateRuntime(BizPod bizPod) { | |||||
| return calculateRuntime(bizPod, (x) -> { | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * 计算job训练时长 | |||||
| * | |||||
| * @param bizPod | |||||
| * @param consumer pod已经完成状态的回调函数 | |||||
| * @return res 返回训练时长 | |||||
| */ | |||||
| private String calculateRuntime(BizPod bizPod, Consumer<String> consumer) { | |||||
| Long completedTime; | |||||
| if (StringUtils.isBlank(bizPod.getStartTime())) { | |||||
| return TrainUtil.INIT_RUNTIME; | |||||
| } | |||||
| Long startTime = transformTime(bizPod.getStartTime()); | |||||
| boolean hasCompleted = StringUtils.isNotBlank(bizPod.getCompletedTime()); | |||||
| completedTime = hasCompleted ? transformTime(bizPod.getCompletedTime()) : LocalDateTime.now().toEpochSecond(ZoneOffset.of(trainJobConfig.getPlusEight())); | |||||
| Long time = completedTime - startTime; | |||||
| String res = DubheDateUtil.convert2Str(time); | |||||
| if (hasCompleted) { | |||||
| consumer.accept(res); | |||||
| } | |||||
| return res; | |||||
| } | |||||
| /** | |||||
| * 时间转换 | |||||
| * | |||||
| * @param time 时间 | |||||
| * @return Long 时间戳 | |||||
| */ | |||||
| private Long transformTime(String time) { | |||||
| LocalDateTime localDateTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_OFFSET_DATE_TIME); | |||||
| //没有根据时区做处理, 默认当前为东八区 | |||||
| localDateTime = localDateTime.plusHours(Long.valueOf(trainJobConfig.getEight())); | |||||
| return localDateTime.toEpochSecond(ZoneOffset.of(trainJobConfig.getPlusEight())); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,115 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.async; | |||||
| import org.dubhe.config.NfsConfig; | |||||
| import org.dubhe.dao.PtTrainAlgorithmMapper; | |||||
| import org.dubhe.domain.dto.PtTrainAlgorithmCreateDTO; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.domain.entity.PtTrainAlgorithm; | |||||
| import org.dubhe.enums.AlgorithmStatusEnum; | |||||
| import org.dubhe.enums.BizNfsEnum; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.exception.BusinessException; | |||||
| import org.dubhe.service.NoteBookService; | |||||
| import org.dubhe.utils.K8sNameTool; | |||||
| import org.dubhe.utils.LocalFileUtil; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.scheduling.annotation.Async; | |||||
| import org.springframework.stereotype.Component; | |||||
| /** | |||||
| * @description 异步上传算法 | |||||
| * @date 2020-08-10 | |||||
| */ | |||||
| @Component | |||||
| public class TrainAlgorithmUploadAsync { | |||||
| @Autowired | |||||
| private LocalFileUtil localFileUtil; | |||||
| @Autowired | |||||
| private NfsConfig nfsConfig; | |||||
| @Autowired | |||||
| private K8sNameTool k8sNameTool; | |||||
| @Autowired | |||||
| private NoteBookService noteBookService; | |||||
| @Autowired | |||||
| private PtTrainAlgorithmMapper trainAlgorithmMapper; | |||||
| /** | |||||
| * 异步任务创建算法 | |||||
| * | |||||
| * @param user 当前登录用户信息 | |||||
| * @param ptTrainAlgorithm 算法信息 | |||||
| * @param trainAlgorithmCreateDTO 创建算法条件 | |||||
| */ | |||||
| @Async("trainExecutor") | |||||
| public void createTrainAlgorithm(UserDTO user, PtTrainAlgorithm ptTrainAlgorithm, PtTrainAlgorithmCreateDTO trainAlgorithmCreateDTO) { | |||||
| String path = nfsConfig.getBucket() + trainAlgorithmCreateDTO.getCodeDir(); | |||||
| //校验创建算法来源(true:由fork创建算法,false:其它创建算法方式),若为true则拷贝预置算法文件至新路径 | |||||
| if (trainAlgorithmCreateDTO.getFork()) { | |||||
| //生成算法相对路径 | |||||
| String algorithmPath = k8sNameTool.getNfsPath(BizNfsEnum.ALGORITHM, user.getId()); | |||||
| //拷贝预置算法文件夹 | |||||
| boolean copyResult = localFileUtil.copyPath(path, nfsConfig.getBucket() + algorithmPath); | |||||
| if (!copyResult) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "The user {} copied the preset algorithm path {} successfully", user.getUsername(), path); | |||||
| updateTrainAlgorithm(ptTrainAlgorithm, trainAlgorithmCreateDTO, false); | |||||
| throw new BusinessException("内部错误"); | |||||
| } | |||||
| ptTrainAlgorithm.setCodeDir(algorithmPath); | |||||
| //修改算法上传状态 | |||||
| updateTrainAlgorithm(ptTrainAlgorithm, trainAlgorithmCreateDTO, true); | |||||
| } else { | |||||
| updateTrainAlgorithm(ptTrainAlgorithm, trainAlgorithmCreateDTO, true); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 更新上传算法状态 | |||||
| * | |||||
| * @param ptTrainAlgorithm 算法信息 | |||||
| * @param trainAlgorithmCreateDTO 创建算法的条件 | |||||
| * @param flag 创建算法是否成功(true:成功,false:失败) | |||||
| */ | |||||
| public void updateTrainAlgorithm(PtTrainAlgorithm ptTrainAlgorithm, PtTrainAlgorithmCreateDTO trainAlgorithmCreateDTO, boolean flag) { | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "async update algorithmPath by algorithmId:{} and update noteBook by noteBookId:{}", ptTrainAlgorithm.getId(), trainAlgorithmCreateDTO.getNoteBookId()); | |||||
| if (flag) { | |||||
| ptTrainAlgorithm.setAlgorithmStatus(AlgorithmStatusEnum.SUCCESS.getCode()); | |||||
| //更新fork算法新路径 | |||||
| trainAlgorithmMapper.updateById(ptTrainAlgorithm); | |||||
| //保存算法根据notbookId更新算法id | |||||
| if (trainAlgorithmCreateDTO.getNoteBookId() != null) { | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "Save algorithm Update algorithm ID :{} according to notBookId:{}", trainAlgorithmCreateDTO.getNoteBookId(), ptTrainAlgorithm.getId()); | |||||
| noteBookService.updateTrainIdByNoteBookId(trainAlgorithmCreateDTO.getNoteBookId(), ptTrainAlgorithm.getId()); | |||||
| } | |||||
| } else { | |||||
| ptTrainAlgorithm.setAlgorithmStatus(AlgorithmStatusEnum.FAIL.getCode()); | |||||
| trainAlgorithmMapper.updateById(ptTrainAlgorithm); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,417 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.async; | |||||
| import cn.hutool.core.util.StrUtil; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import org.dubhe.base.MagicNumConstant; | |||||
| import org.dubhe.constant.SymbolConstant; | |||||
| import org.dubhe.config.TrainJobConfig; | |||||
| import org.dubhe.dao.PtTrainJobMapper; | |||||
| import org.dubhe.domain.dto.BaseTrainJobDTO; | |||||
| import org.dubhe.domain.dto.UserDTO; | |||||
| import org.dubhe.domain.entity.PtTrainJob; | |||||
| import org.dubhe.domain.vo.PtImageAndAlgorithmVO; | |||||
| import org.dubhe.enums.BizEnum; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.enums.ResourcesPoolTypeEnum; | |||||
| import org.dubhe.enums.TrainJobStatusEnum; | |||||
| import org.dubhe.exception.BusinessException; | |||||
| import org.dubhe.k8s.api.DistributeTrainApi; | |||||
| import org.dubhe.k8s.api.NamespaceApi; | |||||
| import org.dubhe.k8s.api.TrainJobApi; | |||||
| import org.dubhe.k8s.domain.bo.DistributeTrainBO; | |||||
| import org.dubhe.k8s.domain.bo.PtJupyterJobBO; | |||||
| import org.dubhe.k8s.domain.resource.BizDistributeTrain; | |||||
| import org.dubhe.k8s.domain.resource.BizNamespace; | |||||
| import org.dubhe.k8s.domain.vo.PtJupyterJobVO; | |||||
| import org.dubhe.utils.*; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @description 提交训练任务 | |||||
| * @date 2020-07-17 | |||||
| */ | |||||
| @Component | |||||
| public class TrainJobAsync { | |||||
| @Autowired | |||||
| private K8sNameTool k8sNameTool; | |||||
| @Autowired | |||||
| private NamespaceApi namespaceApi; | |||||
| @Autowired | |||||
| private TrainJobConfig trainJobConfig; | |||||
| @Autowired | |||||
| private NfsUtil nfsUtil; | |||||
| @Autowired | |||||
| private LocalFileUtil localFileUtil; | |||||
| @Autowired | |||||
| private PtTrainJobMapper ptTrainJobMapper; | |||||
| @Autowired | |||||
| private TrainJobApi trainJobApi; | |||||
| @Autowired | |||||
| private DistributeTrainApi distributeTrainApi; | |||||
| /** | |||||
| * 提交分布式训练 | |||||
| * | |||||
| * @param baseTrainJobDTO 训练任务信息 | |||||
| * @param currentUser 用户 | |||||
| * @param ptImageAndAlgorithmVO 镜像和算法信息 | |||||
| * @param ptTrainJob 训练任务实体信息 | |||||
| */ | |||||
| public void doDistributedJob(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, PtImageAndAlgorithmVO ptImageAndAlgorithmVO, PtTrainJob ptTrainJob) { | |||||
| try { | |||||
| //判断是否存在相应的namespace,如果没有则创建 | |||||
| String namespace = getNamespace(currentUser); | |||||
| // 构建DistributeTrainBO | |||||
| DistributeTrainBO bo = buildDistributeTrainBO(baseTrainJobDTO, currentUser, ptImageAndAlgorithmVO, ptTrainJob, namespace); | |||||
| if (null == bo) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "user{}create TrainJob,Encapsulating ptjupyterjobbo object is empty,the received parameters namespace:{}", currentUser.getId(), namespace); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, "", false); | |||||
| return; | |||||
| } | |||||
| // 调度K8s | |||||
| BizDistributeTrain bizDistributeTrain = distributeTrainApi.create(bo); | |||||
| if (bizDistributeTrain.isSuccess()) { | |||||
| // 调度成功 | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, bizDistributeTrain.getName(), true); | |||||
| } else { | |||||
| // 调度失败 | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "distributeTrainApi.create FAILED! {}", bizDistributeTrain); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, bizDistributeTrain.getName(), false); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "doDistributedJob ERROR!{} ", e); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, "", false); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 构造分布式训练DistributeTrainBO | |||||
| * | |||||
| * @param baseTrainJobDTO 训练任务信息 | |||||
| * @param currentUser 用户 | |||||
| * @param ptImageAndAlgorithmVO 镜像和算法信息 | |||||
| * @param ptTrainJob 训练任务实体信息 | |||||
| * @param namespace 命名空间 | |||||
| * @return DistributeTrainBO | |||||
| */ | |||||
| private DistributeTrainBO buildDistributeTrainBO(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, PtImageAndAlgorithmVO ptImageAndAlgorithmVO, PtTrainJob ptTrainJob, String namespace) { | |||||
| //绝对路径 | |||||
| String basePath = nfsUtil.getNfsConfig().getBucket() + trainJobConfig.getManage() + StrUtil.SLASH | |||||
| + currentUser.getId() + StrUtil.SLASH + baseTrainJobDTO.getJobName(); | |||||
| //相对路径 | |||||
| String relativePath = StrUtil.SLASH + trainJobConfig.getManage() + StrUtil.SLASH | |||||
| + currentUser.getId() + StrUtil.SLASH + baseTrainJobDTO.getJobName(); | |||||
| String[] codeDirArray = ptImageAndAlgorithmVO.getCodeDir().split(StrUtil.SLASH); | |||||
| String workspaceDir = codeDirArray[codeDirArray.length - 1]; | |||||
| // 算法路径待拷贝的地址 | |||||
| String sourcePath = nfsUtil.getNfsConfig().getBucket() + ptImageAndAlgorithmVO.getCodeDir().substring(1); | |||||
| String trainDir = basePath.substring(1) + StrUtil.SLASH + workspaceDir; | |||||
| if (!localFileUtil.copyPath(sourcePath, trainDir)) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "buildDistributeTrainBO copyPath failed ! sourcePath:{},basePath:{},trainDir:{}", sourcePath, basePath, trainDir); | |||||
| return null; | |||||
| } | |||||
| // 参数前缀 | |||||
| String paramPrefix = trainJobConfig.getPythonFormat(); | |||||
| // 初始化固定头命令,获取分布式节点IP | |||||
| StringBuilder sb = new StringBuilder("export NODE_IPS=`cat /home/hostfile.json |jq -r \".[]|.ip\"|paste -d \",\" -s` "); | |||||
| // 切换到算法路径下 | |||||
| sb.append(" && cd ").append(trainJobConfig.getDockerTrainPath()).append(StrUtil.SLASH).append(workspaceDir).append(" && "); | |||||
| // 拼接用户自定义python启动命令 | |||||
| sb.append(ptImageAndAlgorithmVO.getRunCommand()); | |||||
| // 拼接python固定参数 节点IP | |||||
| sb.append(paramPrefix).append(trainJobConfig.getNodeIps()).append("=\"$NODE_IPS\" "); | |||||
| // 拼接python固定参数 节点数量 | |||||
| sb.append(paramPrefix).append(trainJobConfig.getNodeNum()).append(SymbolConstant.FLAG_EQUAL).append(ptTrainJob.getResourcesPoolNode()).append(StrUtil.SPACE); | |||||
| if (ptImageAndAlgorithmVO.getIsTrainOut()) { | |||||
| // 拼接 out | |||||
| nfsUtil.createDir(basePath + StrUtil.SLASH + trainJobConfig.getOutPath()); | |||||
| baseTrainJobDTO.setOutPath(relativePath + StrUtil.SLASH + trainJobConfig.getOutPath()); | |||||
| sb.append(paramPrefix).append(trainJobConfig.getDockerOutPath()); | |||||
| } | |||||
| if (ptImageAndAlgorithmVO.getIsTrainLog()) { | |||||
| // 拼接 输出日志 | |||||
| nfsUtil.createDir(basePath + StrUtil.SLASH + trainJobConfig.getLogPath()); | |||||
| baseTrainJobDTO.setLogPath(relativePath + StrUtil.SLASH + trainJobConfig.getLogPath()); | |||||
| sb.append(paramPrefix).append(trainJobConfig.getDockerLogPath()); | |||||
| } | |||||
| if (ptImageAndAlgorithmVO.getIsVisualizedLog()) { | |||||
| // 拼接 输出可视化日志 | |||||
| nfsUtil.createDir(basePath + StrUtil.SLASH + trainJobConfig.getVisualizedLogPath()); | |||||
| baseTrainJobDTO.setVisualizedLogPath(relativePath + StrUtil.SLASH + trainJobConfig.getVisualizedLogPath()); | |||||
| sb.append(paramPrefix).append(trainJobConfig.getDockerVisualizedLogPath()); | |||||
| } | |||||
| // 拼接python固定参数 数据集 | |||||
| sb.append(paramPrefix).append(trainJobConfig.getDockerDataset()); | |||||
| JSONObject runParams = baseTrainJobDTO.getRunParams(); | |||||
| if (null != runParams && !runParams.isEmpty()) { | |||||
| // 拼接用户自定义参数 | |||||
| runParams.entrySet().forEach(entry -> | |||||
| sb.append(paramPrefix).append(entry.getKey()).append(SymbolConstant.FLAG_EQUAL).append(entry.getValue()).append(StrUtil.SPACE) | |||||
| ); | |||||
| } | |||||
| // 在用户自定以参数拼接晚后拼接固定参数,防止被用户自定义参数覆盖 | |||||
| if (ResourcesPoolTypeEnum.isGpuCode(baseTrainJobDTO.getPtTrainJobSpecs().getResourcesPoolType())) { | |||||
| // 需要GPU | |||||
| sb.append(paramPrefix).append(trainJobConfig.getGpuNumPerNode()).append(SymbolConstant.FLAG_EQUAL).append(baseTrainJobDTO.getGpuNumPerNode()).append(StrUtil.SPACE); | |||||
| } | |||||
| String mainCommand = sb.toString(); | |||||
| // 拼接辅助日志打印 | |||||
| String wholeCommand = " echo 'Distribute training mission begins... " | |||||
| + mainCommand | |||||
| + " ' && " | |||||
| + mainCommand | |||||
| + " && echo 'Distribute training mission is over' "; | |||||
| DistributeTrainBO distributeTrainBO = new DistributeTrainBO() | |||||
| .setNamespace(namespace) | |||||
| .setName(baseTrainJobDTO.getJobName()) | |||||
| .setSize(ptTrainJob.getResourcesPoolNode()) | |||||
| .setImage(ptImageAndAlgorithmVO.getImageName()) | |||||
| .setMasterCmd(wholeCommand) | |||||
| .setMemNum(baseTrainJobDTO.getMenNum()) | |||||
| .setCpuNum(baseTrainJobDTO.getCpuNum()) | |||||
| .setDatasetStoragePath(k8sNameTool.getAbsoluteNfsPath(baseTrainJobDTO.getDataSourcePath())) | |||||
| .setWorkspaceStoragePath(localFileUtil.formatPath(nfsUtil.getNfsConfig().getRootDir() + basePath)) | |||||
| .setModelStoragePath(k8sNameTool.getAbsoluteNfsPath(relativePath + StrUtil.SLASH + trainJobConfig.getOutPath())) | |||||
| .setBusinessLabel(k8sNameTool.getPodLabel(BizEnum.ALGORITHM)); | |||||
| //延时启动,单位为分钟 | |||||
| if (baseTrainJobDTO.getDelayCreateTime() != null && baseTrainJobDTO.getDelayCreateTime() > 0) { | |||||
| distributeTrainBO.setDelayCreateTime(baseTrainJobDTO.getDelayCreateTime() * MagicNumConstant.SIXTY); | |||||
| } | |||||
| //定时停止,单位为分钟 | |||||
| if (baseTrainJobDTO.getDelayDeleteTime() != null && baseTrainJobDTO.getDelayDeleteTime() > 0) { | |||||
| distributeTrainBO.setDelayDeleteTime(baseTrainJobDTO.getDelayDeleteTime() * MagicNumConstant.SIXTY); | |||||
| } | |||||
| if (ResourcesPoolTypeEnum.isGpuCode(baseTrainJobDTO.getPtTrainJobSpecs().getResourcesPoolType())) { | |||||
| // 需要GPU | |||||
| distributeTrainBO.setGpuNum(baseTrainJobDTO.getGpuNumPerNode()); | |||||
| } | |||||
| // 主从一致 | |||||
| distributeTrainBO.setSlaveCmd(distributeTrainBO.getMasterCmd()); | |||||
| return distributeTrainBO; | |||||
| } | |||||
| /** | |||||
| * 提交job | |||||
| * | |||||
| * @param baseTrainJobDTO 训练任务信息 | |||||
| * @param currentUser 用户 | |||||
| * @param ptImageAndAlgorithmVO 镜像和算法信息 | |||||
| */ | |||||
| public void doJob(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, PtImageAndAlgorithmVO ptImageAndAlgorithmVO, PtTrainJob ptTrainJob) { | |||||
| PtJupyterJobBO jobBo = null; | |||||
| String k8sJobName = ""; | |||||
| try { | |||||
| //判断是否存在相应的namespace,如果没有则创建 | |||||
| String namespace = getNamespace(currentUser); | |||||
| //封装PtJupyterJobBO对象,调用创建训练任务接口 | |||||
| jobBo = pkgPtJupyterJobBo(baseTrainJobDTO, currentUser, ptImageAndAlgorithmVO, namespace); | |||||
| if (null == jobBo) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "user {} create TrainJob,Encapsulating ptjupyterjobbo object is empty,the received parameters namespace:{}", currentUser.getId(), namespace); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, k8sJobName, false); | |||||
| } | |||||
| PtJupyterJobVO ptJupyterJobResult = trainJobApi.create(jobBo); | |||||
| if (!ptJupyterJobResult.isSuccess()) { | |||||
| String message = null == ptJupyterJobResult.getMessage() ? "未知的错误" : ptJupyterJobResult.getMessage(); | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "user {} create TrainJob, K8s creation failed, the received parameters are {}, the wrong information is{}", currentUser.getUsername(), jobBo, message); | |||||
| ptTrainJob.setTrainMsg(message); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, k8sJobName, false); | |||||
| } | |||||
| k8sJobName = ptJupyterJobResult.getName(); | |||||
| //更新训练任务状态 | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, k8sJobName, true); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "user {} create TrainJob, K8s creation failed, the received parameters are {}, the wrong information is{}", currentUser.getUsername(), | |||||
| jobBo, e); | |||||
| ptTrainJob.setTrainMsg("内部错误"); | |||||
| updateTrainStatus(currentUser, ptTrainJob, baseTrainJobDTO, k8sJobName, false); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取namespace | |||||
| * | |||||
| * @param currentUser 用户 | |||||
| * @return String 命名空间 | |||||
| */ | |||||
| private String getNamespace(UserDTO currentUser) { | |||||
| String namespaceStr = k8sNameTool.generateNamespace(currentUser.getId()); | |||||
| BizNamespace bizNamespace = namespaceApi.get(namespaceStr); | |||||
| if (null == bizNamespace) { | |||||
| BizNamespace namespace = namespaceApi.create(namespaceStr, null); | |||||
| if (null == namespace || !namespace.isSuccess()) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "User {} failed to create namespace during training job..."); | |||||
| throw new BusinessException("内部错误"); | |||||
| } | |||||
| } | |||||
| return namespaceStr; | |||||
| } | |||||
| /** | |||||
| * 封装出创建job所需的BO | |||||
| * | |||||
| * @param baseTrainJobDTO 训练任务信息 | |||||
| * @param ptImageAndAlgorithmVO 镜像和算法信息 | |||||
| * @param namespace 命名空间 | |||||
| * @return PtJupyterJobBO jupyter任务BO | |||||
| */ | |||||
| private PtJupyterJobBO pkgPtJupyterJobBo(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, | |||||
| PtImageAndAlgorithmVO ptImageAndAlgorithmVO, String namespace) { | |||||
| //绝对路径 | |||||
| String commonPath = nfsUtil.getNfsConfig().getBucket() + trainJobConfig.getManage() + StrUtil.SLASH | |||||
| + currentUser.getId() + StrUtil.SLASH + baseTrainJobDTO.getJobName(); | |||||
| //相对路径 | |||||
| String relativeCommonPath = StrUtil.SLASH + trainJobConfig.getManage() + StrUtil.SLASH | |||||
| + currentUser.getId() + StrUtil.SLASH + baseTrainJobDTO.getJobName(); | |||||
| String[] codeDirArray = ptImageAndAlgorithmVO.getCodeDir().split(StrUtil.SLASH); | |||||
| String workspaceDir = codeDirArray[codeDirArray.length - 1]; | |||||
| // 算法路径待拷贝的地址 | |||||
| String sourcePath = nfsUtil.getNfsConfig().getBucket() + ptImageAndAlgorithmVO.getCodeDir().substring(1); | |||||
| String trainDir = commonPath.substring(1) + StrUtil.SLASH + workspaceDir; | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "Algorithm path copy::sourcePath:{},commonPath:{},trainDir:{}", sourcePath, commonPath, trainDir); | |||||
| boolean bool = localFileUtil.copyPath(sourcePath.substring(1), trainDir); | |||||
| if (!bool) { | |||||
| LogUtil.error(LogEnum.BIZ_TRAIN, "During the process of user {} creating training Job and encapsulating k8s creating job interface parameters, it failed to copy algorithm directory {} to the specified directory {}", currentUser.getUsername(), sourcePath.substring(1), | |||||
| trainDir); | |||||
| return null; | |||||
| } | |||||
| List<String> list = new ArrayList<>(); | |||||
| JSONObject runParams = baseTrainJobDTO.getRunParams(); | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| sb.append(ptImageAndAlgorithmVO.getRunCommand()); | |||||
| // 拼接out,log和dataset | |||||
| String pattern = trainJobConfig.getPythonFormat(); | |||||
| if (ptImageAndAlgorithmVO.getIsTrainOut()) { | |||||
| nfsUtil.createDir(commonPath + StrUtil.SLASH + trainJobConfig.getOutPath()); | |||||
| baseTrainJobDTO.setOutPath(relativeCommonPath + StrUtil.SLASH + trainJobConfig.getOutPath()); | |||||
| sb.append(pattern).append(trainJobConfig.getDockerOutPath()); | |||||
| } | |||||
| if (ptImageAndAlgorithmVO.getIsTrainLog()) { | |||||
| nfsUtil.createDir(commonPath + StrUtil.SLASH + trainJobConfig.getLogPath()); | |||||
| baseTrainJobDTO.setLogPath(relativeCommonPath + StrUtil.SLASH + trainJobConfig.getLogPath()); | |||||
| sb.append(pattern).append(trainJobConfig.getDockerLogPath()); | |||||
| } | |||||
| if (ptImageAndAlgorithmVO.getIsVisualizedLog()) { | |||||
| nfsUtil.createDir(commonPath + StrUtil.SLASH + trainJobConfig.getVisualizedLogPath()); | |||||
| baseTrainJobDTO.setVisualizedLogPath(relativeCommonPath + StrUtil.SLASH + trainJobConfig.getVisualizedLogPath()); | |||||
| sb.append(pattern).append(trainJobConfig.getDockerVisualizedLogPath()); | |||||
| } | |||||
| sb.append(pattern).append(trainJobConfig.getDockerDataset()); | |||||
| String valDataSourcePath = baseTrainJobDTO.getValDataSourcePath(); | |||||
| if (StringUtils.isNotBlank(valDataSourcePath)) { | |||||
| sb.append(pattern).append(trainJobConfig.getLoadValDatasetKey()).append(SymbolConstant.FLAG_EQUAL).append(trainJobConfig.getDockerValDatasetPath()); | |||||
| } | |||||
| //将模型加载路径拼接到 | |||||
| String modelLoadPathDir = baseTrainJobDTO.getModelLoadPathDir(); | |||||
| if (StringUtils.isNotBlank(modelLoadPathDir)) { | |||||
| //将模型路径model_load_dir路径 | |||||
| sb.append(pattern).append(trainJobConfig.getLoadKey()).append(SymbolConstant.FLAG_EQUAL).append(trainJobConfig.getDockerModelPath()); | |||||
| } | |||||
| if (null != runParams && !runParams.isEmpty()) { | |||||
| runParams.forEach((k, v) -> | |||||
| sb.append(pattern).append(k).append(SymbolConstant.FLAG_EQUAL).append(v).append(StrUtil.SPACE) | |||||
| ); | |||||
| } | |||||
| // 在用户自定以参数拼接晚后拼接固定参数,防止被用户自定义参数覆盖 | |||||
| if (ResourcesPoolTypeEnum.isGpuCode(baseTrainJobDTO.getPtTrainJobSpecs().getResourcesPoolType())) { | |||||
| // 需要GPU | |||||
| sb.append(pattern).append(trainJobConfig.getGpuNumPerNode()).append(SymbolConstant.FLAG_EQUAL).append(baseTrainJobDTO.getGpuNumPerNode()).append(StrUtil.SPACE); | |||||
| } | |||||
| String executeCmd = sb.toString(); | |||||
| list.add("-c"); | |||||
| String workPath = trainJobConfig.getDockerTrainPath() + StrUtil.SLASH + workspaceDir; | |||||
| String command = "echo 'training mission begins... " + executeCmd + "\r\n '" + | |||||
| " && cd " + workPath + | |||||
| " && " + executeCmd + | |||||
| " && echo 'the training mission is over' "; | |||||
| list.add(command); | |||||
| PtJupyterJobBO jobBo = new PtJupyterJobBO(); | |||||
| jobBo.setNamespace(namespace) | |||||
| .setName(baseTrainJobDTO.getJobName()) | |||||
| .setImage(ptImageAndAlgorithmVO.getImageName()) | |||||
| .putNfsMounts(trainJobConfig.getDockerDatasetPath(), nfsUtil.getNfsConfig().getRootDir() + nfsUtil.getNfsConfig().getBucket().substring(1) + baseTrainJobDTO.getDataSourcePath()) | |||||
| .setCmdLines(list) | |||||
| .putNfsMounts(trainJobConfig.getDockerTrainPath(), nfsUtil.getNfsConfig().getRootDir() + commonPath.substring(1)) | |||||
| .putNfsMounts(trainJobConfig.getDockerModelPath(), nfsUtil.formatPath(nfsUtil.getAbsolutePath(modelLoadPathDir))) | |||||
| .putNfsMounts(trainJobConfig.getDockerValDatasetPath(), nfsUtil.formatPath(nfsUtil.getAbsolutePath(valDataSourcePath))) | |||||
| .setBusinessLabel(k8sNameTool.getPodLabel(BizEnum.ALGORITHM)); | |||||
| //延时启动,单位为分钟 | |||||
| if (baseTrainJobDTO.getDelayCreateTime() != null && baseTrainJobDTO.getDelayCreateTime() > 0) { | |||||
| jobBo.setDelayCreateTime(baseTrainJobDTO.getDelayCreateTime() * MagicNumConstant.SIXTY); | |||||
| } | |||||
| //自动停止,单位为分钟 | |||||
| if (baseTrainJobDTO.getDelayDeleteTime() != null && baseTrainJobDTO.getDelayDeleteTime() > 0) { | |||||
| jobBo.setDelayDeleteTime(baseTrainJobDTO.getDelayDeleteTime() * MagicNumConstant.SIXTY); | |||||
| } | |||||
| jobBo.setCpuNum(baseTrainJobDTO.getCpuNum()).setMemNum(baseTrainJobDTO.getMenNum()); | |||||
| if (ResourcesPoolTypeEnum.isGpuCode(baseTrainJobDTO.getPtTrainJobSpecs().getResourcesPoolType())) { | |||||
| jobBo.setUseGpu(true).setGpuNum(baseTrainJobDTO.getGpuNumPerNode()); | |||||
| } else { | |||||
| jobBo.setUseGpu(false); | |||||
| } | |||||
| return jobBo; | |||||
| } | |||||
| /** | |||||
| * 训练任务异步处理更新训练状态 | |||||
| * | |||||
| * @param user 用户 | |||||
| * @param ptTrainJob 训练任务 | |||||
| * @param baseTrainJobDTO 训练任务信息 | |||||
| * @param k8sJobName k8s创建的job名称,或者分布式训练名称 | |||||
| * @param flag 创建训练任务是否异常(true:正常,false:失败) | |||||
| **/ | |||||
| private void updateTrainStatus(UserDTO user, PtTrainJob ptTrainJob, BaseTrainJobDTO baseTrainJobDTO, String k8sJobName, boolean flag) { | |||||
| ptTrainJob.setK8sJobName(k8sJobName) | |||||
| .setOutPath(baseTrainJobDTO.getOutPath()) | |||||
| .setLogPath(baseTrainJobDTO.getLogPath()) | |||||
| .setVisualizedLogPath(baseTrainJobDTO.getVisualizedLogPath()); | |||||
| LogUtil.info(LogEnum.BIZ_TRAIN, "user {} training tasks are processed asynchronously to update training status,receiving parameters:{}", user.getId(), ptTrainJob); | |||||
| if (flag) { | |||||
| ptTrainJobMapper.updateById(ptTrainJob); | |||||
| } else { | |||||
| ptTrainJob.setTrainStatus(TrainJobStatusEnum.CREATE_FAILED.getStatus()); | |||||
| //训练任务创建失败 | |||||
| ptTrainJobMapper.updateById(ptTrainJob); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -14,13 +14,16 @@ | |||||
| * limitations under the License. | * limitations under the License. | ||||
| * ============================================================= | * ============================================================= | ||||
| */ | */ | ||||
| package org.dubhe.task; | |||||
| package org.dubhe.async; | |||||
| import org.dubhe.aspect.LogAspect; | import org.dubhe.aspect.LogAspect; | ||||
| import org.dubhe.base.DataContext; | |||||
| import org.dubhe.domain.dto.BaseTrainJobDTO; | import org.dubhe.domain.dto.BaseTrainJobDTO; | ||||
| import org.dubhe.domain.dto.CommonPermissionDataDTO; | |||||
| import org.dubhe.domain.dto.UserDTO; | import org.dubhe.domain.dto.UserDTO; | ||||
| import org.dubhe.domain.entity.PtTrainJob; | import org.dubhe.domain.entity.PtTrainJob; | ||||
| import org.dubhe.domain.vo.PtImageAndAlgorithmVO; | import org.dubhe.domain.vo.PtImageAndAlgorithmVO; | ||||
| import org.dubhe.enums.TrainTypeEnum; | |||||
| import org.slf4j.MDC; | import org.slf4j.MDC; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| @@ -38,26 +41,32 @@ import java.util.concurrent.Executor; | |||||
| public class TransactionAsyncManager { | public class TransactionAsyncManager { | ||||
| @Autowired | @Autowired | ||||
| private TrainJobAsyncTask trainJobAsyncTask; | |||||
| @Resource(name = "trainJobAsyncExecutor") | |||||
| private Executor trainJobAsyncExecutor; | |||||
| private TrainJobAsync trainJobAsync; | |||||
| @Resource(name = "trainExecutor") | |||||
| private Executor trainExecutor; | |||||
| public void execute(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, PtImageAndAlgorithmVO ptImageAndAlgorithmVO, PtTrainJob ptTrainJob) { | public void execute(BaseTrainJobDTO baseTrainJobDTO, UserDTO currentUser, PtImageAndAlgorithmVO ptImageAndAlgorithmVO, PtTrainJob ptTrainJob) { | ||||
| String traceId = MDC.get(LogAspect.TRACE_ID); | String traceId = MDC.get(LogAspect.TRACE_ID); | ||||
| CommonPermissionDataDTO commonPermissionDataDTO = DataContext.get(); | |||||
| TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { | TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { | ||||
| @Override | @Override | ||||
| public void afterCommit() { | public void afterCommit() { | ||||
| trainJobAsyncExecutor.execute( | |||||
| () -> { | |||||
| MDC.put(LogAspect.TRACE_ID, traceId); | |||||
| trainJobAsyncTask.doJob(baseTrainJobDTO, currentUser, ptImageAndAlgorithmVO, ptTrainJob); | |||||
| MDC.remove(LogAspect.TRACE_ID); | |||||
| } | |||||
| ); | |||||
| trainExecutor.execute(() -> { | |||||
| MDC.put(LogAspect.TRACE_ID, traceId); | |||||
| DataContext.set(commonPermissionDataDTO); | |||||
| if (TrainTypeEnum.isDistributeTrain(ptTrainJob.getTrainType())) { | |||||
| // 分布式训练 | |||||
| trainJobAsync.doDistributedJob(baseTrainJobDTO, currentUser, ptImageAndAlgorithmVO, ptTrainJob); | |||||
| } else { | |||||
| // 普通训练 | |||||
| trainJobAsync.doJob(baseTrainJobDTO, currentUser, ptImageAndAlgorithmVO, ptTrainJob); | |||||
| } | |||||
| MDC.remove(LogAspect.TRACE_ID); | |||||
| DataContext.remove(); | |||||
| }); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -0,0 +1,148 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.config; | |||||
| import com.alibaba.fastjson.JSON; | |||||
| import org.dubhe.constant.StringConstant; | |||||
| import org.dubhe.constatnts.UserConstant; | |||||
| import org.dubhe.dto.GlobalRequestRecordDTO; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.JwtUtils; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import org.dubhe.utils.StringUtils; | |||||
| import org.springframework.core.annotation.Order; | |||||
| import org.springframework.stereotype.Component; | |||||
| import javax.servlet.*; | |||||
| import javax.servlet.annotation.WebFilter; | |||||
| import javax.servlet.http.HttpServletRequest; | |||||
| import javax.servlet.http.HttpServletResponse; | |||||
| import java.io.IOException; | |||||
| import static org.dubhe.constant.StringConstant.K8S_CALLBACK_URI; | |||||
| /** | |||||
| * @description 全局请求拦截器 用于日志收集 | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| @Order(1) | |||||
| @Component | |||||
| @WebFilter(filterName = "GlobalFilter", urlPatterns = "/**") | |||||
| public class GlobalFilter implements Filter { | |||||
| @Override | |||||
| public void init(FilterConfig filterConfig) throws ServletException { | |||||
| } | |||||
| @Override | |||||
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { | |||||
| long start = System.currentTimeMillis(); | |||||
| HttpServletRequest request = ((HttpServletRequest) servletRequest); | |||||
| HttpServletResponse response = ((HttpServletResponse) servletResponse); | |||||
| GlobalRequestRecordDTO dto = new GlobalRequestRecordDTO(); | |||||
| try { | |||||
| if (StringUtils.isNotBlank(request.getContentType()) && request.getContentType().contains(StringConstant.MULTIPART)) { | |||||
| chain.doFilter(request, response); | |||||
| } else { | |||||
| checkScheduleRequest(request); | |||||
| checkK8sCallback(request); | |||||
| RequestBodyWrapper requestBodyWrapper = new RequestBodyWrapper(request); | |||||
| ResponseBodyWrapper responseBodyWrapper = new ResponseBodyWrapper(response); | |||||
| dto.setRequestBody(requestBodyWrapper.getBodyString()); | |||||
| chain.doFilter(requestBodyWrapper, responseBodyWrapper); | |||||
| if (StringConstant.JSON_REQUEST.equals(responseBodyWrapper.getContentType())) { | |||||
| final String responseBody = responseBodyWrapper.getResponseBody(); | |||||
| dto.setResponseBody(responseBody); | |||||
| } else { | |||||
| responseBodyWrapper.flush(); | |||||
| } | |||||
| } | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.GLOBAL_REQ, "Global request record error : {}", e); | |||||
| throw e; | |||||
| } finally { | |||||
| buildGlobalRequestDTO(dto, request, response); | |||||
| dto.setTimeCost(System.currentTimeMillis() - start); | |||||
| LogUtil.info(LogEnum.GLOBAL_REQ, "Global request record: {}", dto); | |||||
| LogUtil.cleanTrace(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 构建全局请求对象 | |||||
| * | |||||
| * @param dto | |||||
| * @param request | |||||
| * @param response | |||||
| */ | |||||
| private void buildGlobalRequestDTO(GlobalRequestRecordDTO dto, HttpServletRequest request, HttpServletResponse response) { | |||||
| dto.setClientHost(request.getRemoteHost()); | |||||
| dto.setParams(JSON.toJSONString(request.getParameterMap())); | |||||
| dto.setMethod(request.getMethod()); | |||||
| dto.setUri(request.getRequestURI()); | |||||
| //身份认证信息 | |||||
| String token = request.getHeader(UserConstant.USER_TOKEN_KEY); | |||||
| dto.setAuthorization(token); | |||||
| if (token != null) { | |||||
| String userName = JwtUtils.getUserName(token); | |||||
| dto.setUsername(userName); | |||||
| } | |||||
| dto.setContentType(response.getContentType()); | |||||
| dto.setStatus(response.getStatus()); | |||||
| } | |||||
| /** | |||||
| * 检查是否是前端的定时请求 | |||||
| * | |||||
| * @param request 请求信息 | |||||
| * @return 是否是前端的定时请求 | |||||
| */ | |||||
| private boolean checkScheduleRequest(HttpServletRequest request) { | |||||
| if (StringConstant.REQUEST_METHOD_GET.equals(request.getMethod()) | |||||
| && StringUtils.isNotBlank(request.getParameter(LogUtil.SCHEDULE_LEVEL))) { | |||||
| LogUtil.startScheduleTrace(); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * 校验请求是否为k8s回调 | |||||
| * @param request 请求信息 | |||||
| * @return 是否为k8s回调 | |||||
| */ | |||||
| private boolean checkK8sCallback(HttpServletRequest request) { | |||||
| if (request.getRequestURI() != null && request.getRequestURI().contains(K8S_CALLBACK_URI)) { | |||||
| LogUtil.startK8sCallbackTrace(); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public void destroy() { | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,101 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.config; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import javax.servlet.ReadListener; | |||||
| import javax.servlet.ServletInputStream; | |||||
| import javax.servlet.http.HttpServletRequest; | |||||
| import javax.servlet.http.HttpServletRequestWrapper; | |||||
| import java.io.BufferedReader; | |||||
| import java.io.ByteArrayInputStream; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStreamReader; | |||||
| /** | |||||
| * @description 用于获取请求body参数的包装类 | |||||
| * @date 2020-08-20 | |||||
| */ | |||||
| public class RequestBodyWrapper extends HttpServletRequestWrapper { | |||||
| private ByteArrayInputStream byteArrayInputStream; | |||||
| private String bodyString; | |||||
| public RequestBodyWrapper(HttpServletRequest request) | |||||
| throws IOException { | |||||
| super(request); | |||||
| try (BufferedReader reader = request.getReader()) { | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| String line = null; | |||||
| while ((line = reader.readLine()) != null) { | |||||
| sb.append(line); | |||||
| } | |||||
| if (sb.length() > 0) { | |||||
| bodyString = sb.toString(); | |||||
| } else { | |||||
| bodyString = ""; | |||||
| } | |||||
| byteArrayInputStream = new ByteArrayInputStream(bodyString.getBytes()); | |||||
| } catch (Exception e) { | |||||
| LogUtil.error(LogEnum.GLOBAL_REQ, "request get reader error : {}", e); | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 获取请求体的json数据 | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public String getBodyString() { | |||||
| return bodyString; | |||||
| } | |||||
| @Override | |||||
| public BufferedReader getReader() throws IOException { | |||||
| return new BufferedReader(new InputStreamReader(getInputStream())); | |||||
| } | |||||
| @Override | |||||
| public ServletInputStream getInputStream() throws IOException { | |||||
| return new ServletInputStream() { | |||||
| @Override | |||||
| public boolean isFinished() { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public boolean isReady() { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public void setReadListener(ReadListener readListener) { | |||||
| } | |||||
| @Override | |||||
| public int read() throws IOException { | |||||
| return byteArrayInputStream.read(); | |||||
| } | |||||
| }; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,126 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.config; | |||||
| import org.dubhe.enums.LogEnum; | |||||
| import org.dubhe.utils.LogUtil; | |||||
| import java.io.ByteArrayOutputStream; | |||||
| import java.io.IOException; | |||||
| import java.io.OutputStreamWriter; | |||||
| import java.io.PrintWriter; | |||||
| import java.io.UnsupportedEncodingException; | |||||
| import javax.servlet.ServletOutputStream; | |||||
| import javax.servlet.WriteListener; | |||||
| import javax.servlet.http.HttpServletResponse; | |||||
| import javax.servlet.http.HttpServletResponseWrapper; | |||||
| /** | |||||
| * @description 用于获取response 的 json 返回值 | |||||
| * @date 2020-08-19 | |||||
| */ | |||||
| public class ResponseBodyWrapper extends HttpServletResponseWrapper { | |||||
| private ByteArrayOutputStream byteArrayOutputStream = null; | |||||
| private ServletOutputStream servletOutputStream = null; | |||||
| private PrintWriter printWriter = null; | |||||
| private HttpServletResponse response; | |||||
| public ResponseBodyWrapper(HttpServletResponse response) throws IOException { | |||||
| super(response); | |||||
| this.response = response; | |||||
| byteArrayOutputStream = new ByteArrayOutputStream(); | |||||
| printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, "UTF-8")); | |||||
| servletOutputStream = new ServletOutputStream() { | |||||
| @Override | |||||
| public void write(int b) throws IOException { | |||||
| byteArrayOutputStream.write(b); | |||||
| } | |||||
| @Override | |||||
| public boolean isReady() { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public void setWriteListener(WriteListener writeListener) { | |||||
| } | |||||
| }; | |||||
| } | |||||
| @Override | |||||
| public ServletOutputStream getOutputStream() throws IOException { | |||||
| return servletOutputStream; | |||||
| } | |||||
| @Override | |||||
| public PrintWriter getWriter() throws IOException { | |||||
| return printWriter; | |||||
| } | |||||
| @Override | |||||
| public void flushBuffer() throws IOException { | |||||
| if (servletOutputStream != null) { | |||||
| servletOutputStream.flush(); | |||||
| } | |||||
| if (printWriter != null) { | |||||
| printWriter.flush(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void reset() { | |||||
| byteArrayOutputStream.reset(); | |||||
| } | |||||
| /** | |||||
| * 获取json返回值 | |||||
| * @return | |||||
| * @throws IOException | |||||
| */ | |||||
| public String getResponseBody() throws IOException { | |||||
| //清空response的流,之后再添加进去 | |||||
| flushBuffer(); | |||||
| byte[] bytes = byteArrayOutputStream.toByteArray(); | |||||
| try { | |||||
| return new String(bytes, "UTF-8"); | |||||
| } catch (UnsupportedEncodingException e) { | |||||
| LogUtil.error(LogEnum.GLOBAL_REQ, e); | |||||
| } finally { | |||||
| response.getOutputStream().write(bytes); | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| /** | |||||
| * 清掉缓冲 | |||||
| * @throws IOException | |||||
| */ | |||||
| public void flush() throws IOException { | |||||
| //清空response的流,之后再添加进去 | |||||
| flushBuffer(); | |||||
| byte[] bytes = byteArrayOutputStream.toByteArray(); | |||||
| response.getOutputStream().write(bytes); | |||||
| } | |||||
| } | |||||
| @@ -24,7 +24,7 @@ import org.springframework.stereotype.Component; | |||||
| import java.sql.Timestamp; | import java.sql.Timestamp; | ||||
| /** | /** | ||||
| * @description: 转换时间戳类型 | |||||
| * @description 转换时间戳类型 | |||||
| * @date 2020-05-22 | * @date 2020-05-22 | ||||
| */ | */ | ||||
| @Component | @Component | ||||
| @@ -0,0 +1,47 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.dao; | |||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
| import org.apache.ibatis.annotations.Param; | |||||
| import org.apache.ibatis.annotations.Select; | |||||
| import org.dubhe.domain.dto.ModelQueryDTO; | |||||
| import org.dubhe.domain.entity.ModelQuery; | |||||
| import org.dubhe.domain.entity.ModelQueryBrance; | |||||
| /** | |||||
| * @description model mapper | |||||
| * @date 2020-10-09 | |||||
| */ | |||||
| public interface ModelQueryMapper extends BaseMapper<ModelQueryDTO> { | |||||
| /** | |||||
| * 根据modelId查询模型信息 | |||||
| * | |||||
| * @param modelId 模型id | |||||
| * @return modelQuery返回查询的模型对象 | |||||
| */ | |||||
| @Select("select name,url from pt_model_info where id=#{modelId}") | |||||
| ModelQuery findModelNameById(@Param("modelId") Integer modelId); | |||||
| /** | |||||
| * 根据模型路径查询模型版本信息 | |||||
| * | |||||
| * @param modelLoadPathDir 模型路径 | |||||
| * @return ModelQueryBrance 模型版本信息 | |||||
| */ | |||||
| @Select("select version from pt_model_branch where url=#{modelLoadPathDir}") | |||||
| ModelQueryBrance findModelVersionByUrl(@Param("modelLoadPathDir") String modelLoadPathDir); | |||||
| } | |||||
| @@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Select; | import org.apache.ibatis.annotations.Select; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.NoteBook; | import org.dubhe.domain.entity.NoteBook; | ||||
| import java.util.List; | import java.util.List; | ||||
| @@ -29,28 +30,27 @@ import java.util.List; | |||||
| * @description notebook mapper | * @description notebook mapper | ||||
| * @date 2020-04-28 | * @date 2020-04-28 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = {"insert","findByNamespaceAndResourceName","selectRunNotUrlList"}) | |||||
| public interface NoteBookMapper extends BaseMapper<NoteBook> { | public interface NoteBookMapper extends BaseMapper<NoteBook> { | ||||
| /** | /** | ||||
| * 根据名称查询 | * 根据名称查询 | ||||
| * | * | ||||
| * @param name | * @param name | ||||
| * @param userId | |||||
| * @param status | * @param status | ||||
| * @return NoteBook | * @return NoteBook | ||||
| */ | */ | ||||
| @Select("select * from notebook where notebook_name = #{name} and user_id = #{userId} and status != #{status} and deleted = 0 limit 1") | |||||
| NoteBook findByNameAndUserId(@Param("name") String name, @Param("userId") long userId, @Param("status") Integer status); | |||||
| @Select("select * from notebook where notebook_name = #{name} and status != #{status} and deleted = 0 limit 1") | |||||
| NoteBook findByNameAndStatus(@Param("name") String name, @Param("status") Integer status); | |||||
| /** | /** | ||||
| * 查询正在运行的notebook数量 | * 查询正在运行的notebook数量 | ||||
| * | * | ||||
| * @param userId | |||||
| * @param status | * @param status | ||||
| * @return int | * @return int | ||||
| */ | */ | ||||
| @Select("select count(1) from notebook where user_id = #{userId} and status = #{status} and deleted = 0") | |||||
| int selectRunNoteBookNum(@Param("userId") long userId, @Param("status") Integer status); | |||||
| @Select("select count(1) from notebook where status = #{status} and deleted = 0") | |||||
| int selectRunNoteBookNum( @Param("status") Integer status); | |||||
| /** | /** | ||||
| * 根据namespace + resourceName查询 | * 根据namespace + resourceName查询 | ||||
| @@ -19,12 +19,14 @@ package org.dubhe.dao; | |||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtImage; | import org.dubhe.domain.entity.PtImage; | ||||
| /** | /** | ||||
| * @description 镜像 Mapper 接口 | * @description 镜像 Mapper 接口 | ||||
| * @date 2020-04-27 | * @date 2020-04-27 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = {"insert"}) | |||||
| public interface PtImageMapper extends BaseMapper<PtImage> { | public interface PtImageMapper extends BaseMapper<PtImage> { | ||||
| } | } | ||||
| @@ -20,6 +20,7 @@ package org.dubhe.dao; | |||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Select; | import org.apache.ibatis.annotations.Select; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtTrainAlgorithm; | import org.dubhe.domain.entity.PtTrainAlgorithm; | ||||
| import java.util.List; | import java.util.List; | ||||
| @@ -28,6 +29,7 @@ import java.util.List; | |||||
| * @description 训练算法Mapper | * @description 训练算法Mapper | ||||
| * @date 2020-04-27 | * @date 2020-04-27 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = {"insert"}) | |||||
| public interface PtTrainAlgorithmMapper extends BaseMapper<PtTrainAlgorithm> { | public interface PtTrainAlgorithmMapper extends BaseMapper<PtTrainAlgorithm> { | ||||
| /** | /** | ||||
| @@ -17,17 +17,17 @@ | |||||
| package org.dubhe.dao; | package org.dubhe.dao; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtTrainAlgorithmUsage; | import org.dubhe.domain.entity.PtTrainAlgorithmUsage; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| /** | /** | ||||
| * | * | ||||
| * 用户辅助信息Mapper 接口 | |||||
| * </p> | |||||
| * | |||||
| * @since 2020-06-23 | |||||
| * @description 用户辅助信息Mapper 接口 | |||||
| * @date 2020-06-23 | |||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = "insert") | |||||
| public interface PtTrainAlgorithmUsageMapper extends BaseMapper<PtTrainAlgorithmUsage> { | public interface PtTrainAlgorithmUsageMapper extends BaseMapper<PtTrainAlgorithmUsage> { | ||||
| } | } | ||||
| @@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Select; | import org.apache.ibatis.annotations.Select; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtTrainJob; | import org.dubhe.domain.entity.PtTrainJob; | ||||
| import org.dubhe.domain.vo.PtTrainVO; | import org.dubhe.domain.vo.PtTrainVO; | ||||
| @@ -28,6 +29,7 @@ import org.dubhe.domain.vo.PtTrainVO; | |||||
| * @description 训练作业job Mapper 接口 | * @description 训练作业job Mapper 接口 | ||||
| * @date 2020-04-27 | * @date 2020-04-27 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = {"insert","selectCountByStatus","getPageTrain"}) | |||||
| public interface PtTrainJobMapper extends BaseMapper<PtTrainJob> { | public interface PtTrainJobMapper extends BaseMapper<PtTrainJob> { | ||||
| /** | /** | ||||
| @@ -18,12 +18,14 @@ | |||||
| package org.dubhe.dao; | package org.dubhe.dao; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtTrain; | import org.dubhe.domain.entity.PtTrain; | ||||
| /** | /** | ||||
| * @description 训练作业主 Mapper 接口 | * @description 训练作业主 Mapper 接口 | ||||
| * @date 2020-04-27 | * @date 2020-04-27 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = {"insert"}) | |||||
| public interface PtTrainMapper extends BaseMapper<PtTrain> { | public interface PtTrainMapper extends BaseMapper<PtTrain> { | ||||
| } | } | ||||
| @@ -17,6 +17,7 @@ | |||||
| package org.dubhe.dao; | package org.dubhe.dao; | ||||
| import org.dubhe.annotation.DataPermission; | |||||
| import org.dubhe.domain.entity.PtTrainParam; | import org.dubhe.domain.entity.PtTrainParam; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| @@ -24,6 +25,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||
| * @description 任务参数 Mapper 接口 | * @description 任务参数 Mapper 接口 | ||||
| * @date 2020-04-27 | * @date 2020-04-27 | ||||
| */ | */ | ||||
| @DataPermission(ignoresMethod = "insert") | |||||
| public interface PtTrainParamMapper extends BaseMapper<PtTrainParam> { | public interface PtTrainParamMapper extends BaseMapper<PtTrainParam> { | ||||
| } | } | ||||
| @@ -40,4 +40,40 @@ public class BaseTrainJobDTO implements Serializable { | |||||
| private String outPath; | private String outPath; | ||||
| private String logPath; | private String logPath; | ||||
| private String visualizedLogPath; | private String visualizedLogPath; | ||||
| private Integer delayCreateTime; | |||||
| private Integer delayDeleteTime; | |||||
| /** | |||||
| * @return 每个节点的GPU数量 | |||||
| */ | |||||
| public Integer getGpuNumPerNode(){ | |||||
| return getPtTrainJobSpecs().getSpecsInfo().getInteger("gpuNum"); | |||||
| } | |||||
| /** | |||||
| * @return cpu数量 | |||||
| */ | |||||
| public Integer getCpuNum(){ | |||||
| return getPtTrainJobSpecs().getSpecsInfo().getInteger("cpuNum"); | |||||
| } | |||||
| /** | |||||
| * @return memNum | |||||
| */ | |||||
| public Integer getMenNum(){ | |||||
| return getPtTrainJobSpecs().getSpecsInfo().getInteger("memNum"); | |||||
| } | |||||
| /** | |||||
| * "验证数据来源名称" | |||||
| */ | |||||
| private String valDataSourceName; | |||||
| /** | |||||
| * 验证数据来源路径 | |||||
| */ | |||||
| private String valDataSourcePath; | |||||
| /** | |||||
| * 模型路径 | |||||
| */ | |||||
| private String modelLoadPathDir; | |||||
| } | } | ||||
| @@ -0,0 +1,39 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.domain.dto; | |||||
| import io.swagger.annotations.ApiModel; | |||||
| import io.swagger.annotations.ApiModelProperty; | |||||
| import lombok.Data; | |||||
| /** | |||||
| * @description model 查询dto | |||||
| * @date 2020-10-09 | |||||
| */ | |||||
| @Data | |||||
| @ApiModel("模型查询") | |||||
| public class ModelQueryDTO { | |||||
| @ApiModelProperty(value = "模型类型") | |||||
| private Integer modelResource; | |||||
| @ApiModelProperty(value = "模型名称") | |||||
| private String name; | |||||
| @ApiModelProperty(value = "模型版本") | |||||
| private String version; | |||||
| @ApiModelProperty(value = "模型id") | |||||
| private Integer id; | |||||
| @ApiModelProperty(value = "模型路径") | |||||
| private String modelPath; | |||||
| } | |||||
| @@ -30,7 +30,6 @@ import java.io.Serializable; | |||||
| @Data | @Data | ||||
| public class NoteBookListQueryDTO implements Serializable { | public class NoteBookListQueryDTO implements Serializable { | ||||
| @Query(propName = "status", type = Query.Type.EQ) | |||||
| @ApiModelProperty("0运行中,1停止, 2删除, 3启动中,4停止中,5删除中,6运行异常(暂未启用)") | @ApiModelProperty("0运行中,1停止, 2删除, 3启动中,4停止中,5删除中,6运行异常(暂未启用)") | ||||
| private Integer status; | private Integer status; | ||||
| @@ -38,7 +37,7 @@ public class NoteBookListQueryDTO implements Serializable { | |||||
| @ApiModelProperty("notebook名称") | @ApiModelProperty("notebook名称") | ||||
| private String noteBookName; | private String noteBookName; | ||||
| @Query(propName = "user_id", type = Query.Type.EQ) | |||||
| @Query(propName = "origin_user_id", type = Query.Type.EQ) | |||||
| @ApiModelProperty(value = "所属用户ID", hidden = true) | @ApiModelProperty(value = "所属用户ID", hidden = true) | ||||
| private Long userId; | private Long userId; | ||||
| @@ -42,7 +42,7 @@ public class NoteBookQueryDTO implements Serializable { | |||||
| @Query(propName = "k8s_pvc_path", type = Query.Type.EQ) | @Query(propName = "k8s_pvc_path", type = Query.Type.EQ) | ||||
| private String k8sPvcPath; | private String k8sPvcPath; | ||||
| @Query(propName = "user_id", type = Query.Type.EQ) | |||||
| @Query(propName = "origin_user_id", type = Query.Type.EQ) | |||||
| private Long userId; | private Long userId; | ||||
| @Query(propName = "last_operation_timeout", type = Query.Type.LT) | @Query(propName = "last_operation_timeout", type = Query.Type.LT) | ||||
| @@ -66,4 +66,6 @@ public class PtImageDTO implements Serializable { | |||||
| @ApiModelProperty("删除(0正常,1已删除)") | @ApiModelProperty("删除(0正常,1已删除)") | ||||
| private Boolean deleted; | private Boolean deleted; | ||||
| @ApiModelProperty("资源拥有者ID") | |||||
| private Long originUserId; | |||||
| } | } | ||||
| @@ -0,0 +1,39 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.domain.dto; | |||||
| import io.swagger.annotations.ApiModelProperty; | |||||
| import lombok.Data; | |||||
| import lombok.experimental.Accessors; | |||||
| import javax.validation.constraints.NotNull; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @description 训练镜像删除DTO | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| @Data | |||||
| @Accessors(chain = true) | |||||
| public class PtImageDeleteDTO implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @ApiModelProperty(value = "id", required = true) | |||||
| @NotNull(message = "镜像id不能为空") | |||||
| private List<Long> ids; | |||||
| } | |||||
| @@ -40,4 +40,7 @@ public class PtImageQueryDTO extends PageQueryBase implements Serializable { | |||||
| @ApiModelProperty(value = "镜像状态,0为制作中,1位制作成功,2位制作失败") | @ApiModelProperty(value = "镜像状态,0为制作中,1位制作成功,2位制作失败") | ||||
| private Integer imageStatus; | private Integer imageStatus; | ||||
| @ApiModelProperty(value = "镜像名称或id") | |||||
| private String imageNameOrId; | |||||
| } | } | ||||
| @@ -0,0 +1,47 @@ | |||||
| /** | |||||
| * Copyright 2020 Zhejiang Lab. All Rights Reserved. | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ============================================================= | |||||
| */ | |||||
| package org.dubhe.domain.dto; | |||||
| import io.swagger.annotations.ApiModelProperty; | |||||
| import lombok.Data; | |||||
| import lombok.experimental.Accessors; | |||||
| import org.dubhe.utils.TrainUtil; | |||||
| import org.hibernate.validator.constraints.Length; | |||||
| import javax.validation.constraints.NotNull; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @description 训练镜像信息修改DTO | |||||
| * @date 2020-08-13 | |||||
| */ | |||||
| @Data | |||||
| @Accessors(chain = true) | |||||
| public class PtImageUpdateDTO implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @ApiModelProperty(value = "id", required = true) | |||||
| @NotNull(message = "镜像id不能为空") | |||||
| private List<Long> ids; | |||||
| @ApiModelProperty("镜像描述") | |||||
| @Length(max = TrainUtil.NUMBER_ONE_THOUSAND_AND_TWENTY_FOUR, message = "镜像描述-输入长度不能超过1024个字符") | |||||
| private String remark; | |||||
| } | |||||
| @@ -55,7 +55,7 @@ public class PtImageUploadDTO implements Serializable { | |||||
| @Pattern(regexp = TrainUtil.REGEXP_TAG, message = "镜像版本号支持字母、数字、英文横杠、英文.号和下划线") | @Pattern(regexp = TrainUtil.REGEXP_TAG, message = "镜像版本号支持字母、数字、英文横杠、英文.号和下划线") | ||||
| private String imageTag; | private String imageTag; | ||||
| @ApiModelProperty("备注") | |||||
| @Length(max = TrainUtil.NUMBER_ONE_THOUSAND_AND_TWENTY_FOUR, message = "备注-输入长度不能超过1024个字符") | |||||
| @ApiModelProperty("镜像描述") | |||||
| @Length(max = TrainUtil.NUMBER_ONE_THOUSAND_AND_TWENTY_FOUR, message = "镜像描述-输入长度不能超过1024个字符") | |||||
| private String remark; | private String remark; | ||||
| } | } | ||||
| @@ -85,4 +85,7 @@ public class PtTrainAlgorithmCreateDTO implements Serializable { | |||||
| @ApiModelProperty("noteBookId") | @ApiModelProperty("noteBookId") | ||||
| private Long noteBookId; | private Long noteBookId; | ||||
| @ApiModelProperty("资源拥有者ID") | |||||
| private Long originUserId; | |||||
| } | } | ||||