This commit is contained in:
淋雨一直走YH
2023-09-28 15:38:29 +08:00
commit 7bc57c75dc
1480 changed files with 144393 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
package mjkf.xinke.dev.core.aop;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import mjkf.xinke.auth.core.pojo.SaBaseLoginUser;
import mjkf.xinke.auth.core.util.StpLoginUserUtil;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.dev.modular.log.util.DevLogUtil;
import java.lang.reflect.Method;
/**
* 业务日志aop切面
*
*
* @date 2020/3/20 11:47
*/
@Aspect
@Order
@Component
public class DevLogAop {
/**
* 日志切入点
*
*
* @date 2020/3/23 17:10
*/
@Pointcut("@annotation(mjkf.xinke.common.annotation.CommonLog)")
private void getLogPointCut() {
}
/**
* 操作成功返回结果记录日志
*
*
* @date 2022/9/2 15:24
*/
@AfterReturning(pointcut = "getLogPointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CommonLog commonLog = method.getAnnotation(CommonLog.class);
String userName = "未知";
try {
SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
if(ObjectUtil.isNotNull(loginUser)) {
userName = loginUser.getName();
}
} catch (Exception ignored) {
}
// 异步记录日志
DevLogUtil.executeOperationLog(commonLog, userName, joinPoint, JSONUtil.toJsonStr(result));
}
/**
* 操作发生异常记录日志
*
*
* @date 2022/9/2 15:24
*/
@AfterThrowing(pointcut = "getLogPointCut()", throwing = "exception")
public void doAfterThrowing(JoinPoint joinPoint, Exception exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
CommonLog commonLog = method.getAnnotation(CommonLog.class);
String userName = "未知";
try {
SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
if(ObjectUtil.isNotNull(loginUser)) {
userName = loginUser.getName();
}
} catch (Exception ignored) {
}
//异步记录日志
DevLogUtil.executeExceptionLog(commonLog, userName, joinPoint, exception);
}
}

View File

@@ -0,0 +1,56 @@
package mjkf.xinke.dev.core.config;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import mjkf.xinke.common.pojo.CommonResult;
import javax.annotation.Resource;
/**
* 开发工具相关配置
*
*
* @date 2022/7/7 16:18
**/
@Configuration
public class DevConfigure {
@Resource
private OpenApiExtensionResolver openApiExtensionResolver;
/**
* API文档分组配置
*
*
* @date 2022/7/7 16:18
**/
@Bean(value = "devDocApi")
public Docket devDocApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("开发工具DEV")
.description("开发工具DEV")
.termsOfServiceUrl("https://www.xiaonuo.vip")
.contact(new Contact("SNOWY_TEAM","https://www.xiaonuo.vip", "xuyuxiang29@foxmail.com"))
.version("2.0.0")
.build())
.globalResponseMessage(RequestMethod.GET, CommonResult.responseList())
.globalResponseMessage(RequestMethod.POST, CommonResult.responseList())
.groupName("开发工具DEV")
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.apis(RequestHandlerSelectors.basePackage("mjkf.xinke.dev"))
.paths(PathSelectors.any())
.build().extensions(openApiExtensionResolver.buildExtensions("开发工具DEV"));
}
}

View File

@@ -0,0 +1,51 @@
package mjkf.xinke.dev.core.listener;
import cn.hutool.cron.CronUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.timer.CommonTimerTaskRunner;
import mjkf.xinke.dev.modular.job.entity.DevJob;
import mjkf.xinke.dev.modular.job.enums.DevJobStatusEnum;
import mjkf.xinke.dev.modular.job.service.DevJobService;
/**
* 定时任务监听器,系统启动时将定时任务启动
*
*
* @date 2022/8/5 16:07
**/
@Slf4j
@Configuration
public class DevJobListener implements ApplicationListener<ApplicationStartedEvent>, Ordered {
@Override
public void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent) {
SpringUtil.getBean(DevJobService.class).list(new LambdaQueryWrapper<DevJob>()
.eq(DevJob::getJobStatus, DevJobStatusEnum.RUNNING.getValue()).orderByAsc(DevJob::getSortCode))
.forEach(devJob -> CronUtil.schedule(devJob.getId(), devJob.getCronExpression(), () -> {
try {
// 运行定时任务
((CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(devJob.getActionClass()))).action();
} catch (ClassNotFoundException e) {
throw new CommonException("定时任务找不到对应的类,名称为:{}", devJob.getActionClass());
}
}));
// 设置秒级别的启用
CronUtil.setMatchSecond(true);
// 启动定时器执行器
CronUtil.start();
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,157 @@
package mjkf.xinke.dev.modular.config.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.modular.config.entity.DevConfig;
import mjkf.xinke.dev.modular.config.enums.DevConfigCategoryEnum;
import mjkf.xinke.dev.modular.config.param.*;
import mjkf.xinke.dev.modular.config.service.DevConfigService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 配置控制器
*
*
* @date 2022/4/22 10:56
**/
@Api(tags = "配置控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 1)
@RestController
@Validated
public class DevConfigController {
@Resource
private DevConfigService devConfigService;
/**
* 获取配置分页
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 1)
@ApiOperation("获取配置分页")
@GetMapping("/dev/config/page")
public CommonResult<Page<DevConfig>> page(DevConfigPageParam devConfigPageParam) {
return CommonResult.data(devConfigService.page(devConfigPageParam));
}
/**
* 获取系统基础配置
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 2)
@ApiOperation("获取系统基础配置")
@GetMapping("/dev/config/sysBaseList")
public CommonResult<List<DevConfig>> sysBaseList() {
DevConfigListParam devConfigListParam = new DevConfigListParam();
devConfigListParam.setCategory(DevConfigCategoryEnum.SYS_BASE.getValue());
return CommonResult.data(devConfigService.list(devConfigListParam));
}
/**
* 获取配置列表
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 2)
@ApiOperation("获取配置列表")
@GetMapping("/dev/config/list")
public CommonResult<List<DevConfig>> list(DevConfigListParam devConfigListParam) {
return CommonResult.data(devConfigService.list(devConfigListParam));
}
/**
* 添加配置
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 3)
@ApiOperation("添加配置")
@CommonLog("添加配置")
@PostMapping("/dev/config/add")
public CommonResult<String> add(@RequestBody @Valid DevConfigAddParam devConfigAddParam) {
devConfigService.add(devConfigAddParam);
return CommonResult.ok();
}
/**
* 编辑配置
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 4)
@ApiOperation("编辑配置")
@CommonLog("编辑配置")
@PostMapping("/dev/config/edit")
public CommonResult<String> edit(@RequestBody @Valid DevConfigEditParam devConfigEditParam) {
devConfigService.edit(devConfigEditParam);
return CommonResult.ok();
}
/**
* 删除配置
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 5)
@ApiOperation("删除配置")
@CommonLog("删除配置")
@PostMapping("/dev/config/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevConfigIdParam> devConfigIdParamList) {
devConfigService.delete(devConfigIdParamList);
return CommonResult.ok();
}
/**
* 获取配置详情
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 6)
@ApiOperation("获取配置详情")
@GetMapping("/dev/config/detail")
public CommonResult<DevConfig> detail(@Valid DevConfigIdParam devConfigIdParam) {
return CommonResult.data(devConfigService.detail(devConfigIdParam));
}
/**
* 配置批量更新
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 7)
@ApiOperation("配置批量更新")
@CommonLog("配置批量更新")
@PostMapping("/dev/config/editBatch")
public CommonResult<String> editBatch(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevConfigBatchParam> devConfigBatchParamList) {
devConfigService.editBatch(devConfigBatchParamList);
return CommonResult.ok();
}
}

View File

@@ -0,0 +1,55 @@
package mjkf.xinke.dev.modular.config.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
/**
* 配置实体
*
*
* @date 2022/2/23 18:27
**/
@Getter
@Setter
@TableName("DEV_CONFIG")
public class DevConfig extends CommonEntity {
/** id */
@ApiModelProperty(value = "id", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 配置键 */
@ApiModelProperty(value = "配置键", position = 3)
private String configKey;
/** 配置值 */
@ApiModelProperty(value = "配置值", position = 4)
private String configValue;
/** 分类 */
@ApiModelProperty(value = "分类", position = 5)
private String category;
/** 备注 */
@ApiModelProperty(value = "备注", position = 6)
private String remark;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 7)
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 8)
@TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
private String extJson;
}

View File

@@ -0,0 +1,109 @@
package mjkf.xinke.dev.modular.config.enums;
import lombok.Getter;
import mjkf.xinke.common.exception.CommonException;
/**
* 配置分类枚举
*
*
* @date 2022/7/6 22:21
*/
@Getter
public enum DevConfigCategoryEnum {
/**
* 系统基础
*/
SYS_BASE("SYS_BASE"),
/**
* 业务定义
*/
BIZ_DEFINE("BIZ_DEFINE"),
/**
* 三方登录-码云
*/
THIRD_GITEE("THIRD_GITEE"),
/**
* 三方登录-微信
*/
THIRD_WECHAT("THIRD_WECHAT"),
/**
* 文件-本地
*/
FILE_LOCAL("FILE_LOCAL"),
/**
* 文件-腾讯云
*/
FILE_TENCENT("FILE_TENCENT"),
/**
* 文件-阿里云
*/
FILE_ALIYUN("FILE_ALIYUN"),
/**
* 文件-MINIO
*/
FILE_MINIO("FILE_MINIO"),
/**
* 邮件-本地
*/
EMAIL_LOCAL("EMAIL_LOCAL"),
/**
* 邮件-腾讯云
*/
EMAIL_TENCENT("EMAIL_TENCENT"),
/**
* 邮件-阿里云
*/
EMAIL_ALIYUN("EMAIL_ALIYUN"),
/**
* 短信-腾讯云
*/
SMS_TENCENT("SMS_TENCENT"),
/**
* 短信-阿里云
*/
SMS_ALIYUN("SMS_ALIYUN"),
/**
* 支付-支付宝
*/
PAY_ALI("PAY_ALI"),
/**
* 支付-微信
*/
PAY_WX("PAY_WX");
private final String value;
DevConfigCategoryEnum(String value) {
this.value = value;
}
public static void validate(String value) {
boolean flag = SYS_BASE.getValue().equals(value) || BIZ_DEFINE.getValue().equals(value) ||
THIRD_GITEE.getValue().equals(value) || THIRD_WECHAT.getValue().equals(value) ||
FILE_LOCAL.getValue().equals(value) || FILE_TENCENT.getValue().equals(value) ||
FILE_ALIYUN.getValue().equals(value) || FILE_MINIO.getValue().equals(value) ||
EMAIL_TENCENT.getValue().equals(value) || EMAIL_ALIYUN.getValue().equals(value) ||
SMS_TENCENT.getValue().equals(value) || SMS_ALIYUN.getValue().equals(value) ||
PAY_ALI.getValue().equals(value) || PAY_WX.getValue().equals(value);
if(!flag) {
throw new CommonException("不支持的配置分类:{}", value);
}
}
}

View File

@@ -0,0 +1,14 @@
package mjkf.xinke.dev.modular.config.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.config.entity.DevConfig;
/**
* 配置Mapper接口
*
*
* @date 2022/4/22 10:43
**/
public interface DevConfigMapper extends BaseMapper<DevConfig> {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.config.mapper.DevConfigMapper">
</mapper>

View File

@@ -0,0 +1,43 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 配置添加参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevConfigAddParam {
/** 配置键 */
@ApiModelProperty(value = "配置键", required = true, position = 1)
@NotBlank(message = "configKey不能为空")
private String configKey;
/** 配置值 */
@ApiModelProperty(value = "配置值", required = true, position = 2)
@NotBlank(message = "configValue不能为空")
private String configValue;
/** 备注 */
@ApiModelProperty(value = "备注", position = 3)
private String remark;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 4)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 5)
private String extJson;
}

View File

@@ -0,0 +1,30 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 配置批量更新参数
*
*
* @date 2022/7/7 17:01
**/
@Getter
@Setter
public class DevConfigBatchParam {
/** 配置键 */
@ApiModelProperty(value = "配置键", required = true, position = 1)
@NotBlank(message = "configKey不能为空")
private String configKey;
/** 配置值 */
@ApiModelProperty(value = "配置值", required = true, position = 2)
@NotBlank(message = "configValue不能为空")
private String configValue;
}

View File

@@ -0,0 +1,48 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 配置编辑参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevConfigEditParam {
/** id */
@ApiModelProperty(value = "id", required = true, position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 配置键 */
@ApiModelProperty(value = "配置键", required = true, position = 2)
@NotBlank(message = "configKey不能为空")
private String configKey;
/** 配置值 */
@ApiModelProperty(value = "配置值", required = true, position = 3)
@NotBlank(message = "configValue不能为空")
private String configValue;
/** 备注 */
@ApiModelProperty(value = "备注", position = 4)
private String remark;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 5)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 6)
private String extJson;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 配置Id参数
*
*
* @date 2022/7/30 17:52
*/
@Getter
@Setter
public class DevConfigIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,21 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 配置列表参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevConfigListParam {
/** 配置分类 */
@ApiModelProperty(value = "配置分类")
private String category;
}

View File

@@ -0,0 +1,37 @@
package mjkf.xinke.dev.modular.config.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 配置查询参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevConfigPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 配置键关键词 */
@ApiModelProperty(value = "配置键关键词")
private String searchKey;
}

View File

@@ -0,0 +1,26 @@
package mjkf.xinke.dev.modular.config.provider;
import org.springframework.stereotype.Service;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.config.service.DevConfigService;
import javax.annotation.Resource;
/**
* 配置API接口实现类
*
*
* @date 2022/6/17 14:43
**/
@Service
public class DevConfigApiProvider implements DevConfigApi {
@Resource
private DevConfigService devConfigService;
@Override
public String getValueByKey(String key) {
return devConfigService.getValueByKey(key);
}
}

View File

@@ -0,0 +1,90 @@
package mjkf.xinke.dev.modular.config.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import mjkf.xinke.dev.modular.config.entity.DevConfig;
import mjkf.xinke.dev.modular.config.param.*;
import java.util.List;
/**
* 配置Service接口
*
*
* @date 2022/4/22 10:41
**/
public interface DevConfigService extends IService<DevConfig> {
/**
* 根据键获取值
*
*
* @date 2022/4/22 14:52
**/
String getValueByKey(String key);
/**
* 获取配置分页
*
*
* @date 2022/4/24 20:08
*/
Page<DevConfig> page(DevConfigPageParam devConfigPageParam);
/**
* 获取配置列表
*
*
* @date 2022/4/24 20:08
*/
List<DevConfig> list(DevConfigListParam devConfigListParam);
/**
* 添加配置
*
*
* @date 2022/4/24 20:48
*/
void add(DevConfigAddParam devConfigAddParam);
/**
* 编辑配置
*
*
* @date 2022/4/24 21:13
*/
void edit(DevConfigEditParam devConfigEditParam);
/**
* 删除配置
*
*
* @date 2022/4/24 21:18
*/
void delete(List<DevConfigIdParam> devConfigIdParamList);
/**
* 获取配置详情
*
*
* @date 2022/4/24 21:18
*/
DevConfig detail(DevConfigIdParam devConfigIdParam);
/**
* 获取配置详情
*
*
* @date 2022/4/24 21:18
*/
DevConfig queryEntity(String id);
/**
* 配置批量更新
*
*
* @date 2022/6/28 11:09
**/
void editBatch(List<DevConfigBatchParam> devConfigBatchParamList);
}

View File

@@ -0,0 +1,191 @@
package mjkf.xinke.dev.modular.config.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import mjkf.xinke.common.cache.CommonCacheOperator;
import mjkf.xinke.common.enums.CommonSortOrderEnum;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.page.CommonPageRequest;
import mjkf.xinke.dev.modular.config.entity.DevConfig;
import mjkf.xinke.dev.modular.config.enums.DevConfigCategoryEnum;
import mjkf.xinke.dev.modular.config.mapper.DevConfigMapper;
import mjkf.xinke.dev.modular.config.param.*;
import mjkf.xinke.dev.modular.config.service.DevConfigService;
import mjkf.xinke.ten.api.TenApi;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 配置Service接口实现类
*
*
* @date 2022/4/22 10:41
**/
@Service
public class DevConfigServiceImpl extends ServiceImpl<DevConfigMapper, DevConfig> implements DevConfigService {
private static final String CONFIG_CACHE_KEY = "dev-config:";
private static final String SNOWY_SYS_DEFAULT_PASSWORD_KEY = "SNOWY_SYS_DEFAULT_PASSWORD";
@Resource
private TenApi tenApi;
@Resource
private CommonCacheOperator commonCacheOperator;
@Override
public String getValueByKey(String key) {
// 缓存的键前缀
String cacheKeyPrefix = CONFIG_CACHE_KEY + tenApi.getCurrentTenDomain() + ":";
// 从缓存中取
Object cacheValue = commonCacheOperator.get(cacheKeyPrefix + key);
if(ObjectUtil.isNotEmpty(cacheValue)) {
return Convert.toStr(cacheValue);
}
DevConfig devConfig = this.getOne(new LambdaQueryWrapper<DevConfig>().eq(DevConfig::getConfigKey, key));
if(ObjectUtil.isNotEmpty(devConfig)) {
// 更新到缓存
commonCacheOperator.put(cacheKeyPrefix + key, devConfig.getConfigValue());
return devConfig.getConfigValue();
}
return null;
}
@Override
public Page<DevConfig> page(DevConfigPageParam devConfigPageParam) {
QueryWrapper<DevConfig> queryWrapper = new QueryWrapper<>();
// 查询部分字段
queryWrapper.lambda().select(DevConfig::getId, DevConfig::getConfigKey, DevConfig::getConfigValue,
DevConfig::getCategory, DevConfig::getRemark, DevConfig::getSortCode);
if(ObjectUtil.isNotEmpty(devConfigPageParam.getSearchKey())) {
queryWrapper.lambda().like(DevConfig::getConfigKey, devConfigPageParam.getSearchKey());
}
if(ObjectUtil.isAllNotEmpty(devConfigPageParam.getSortField(), devConfigPageParam.getSortOrder())) {
CommonSortOrderEnum.validate(devConfigPageParam.getSortOrder());
queryWrapper.orderBy(true, devConfigPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
StrUtil.toUnderlineCase(devConfigPageParam.getSortField()));
} else {
queryWrapper.lambda().orderByAsc(DevConfig::getSortCode);
}
queryWrapper.lambda().eq(DevConfig::getCategory, DevConfigCategoryEnum.BIZ_DEFINE.getValue());
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Override
public List<DevConfig> list(DevConfigListParam devConfigListParam) {
LambdaQueryWrapper<DevConfig> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 查询部分字段
lambdaQueryWrapper.select(DevConfig::getId, DevConfig::getConfigKey, DevConfig::getConfigValue,
DevConfig::getCategory, DevConfig::getSortCode);
if(ObjectUtil.isNotEmpty(devConfigListParam.getCategory())) {
lambdaQueryWrapper.eq(DevConfig::getCategory, devConfigListParam.getCategory());
}
return this.list(lambdaQueryWrapper).stream().peek(devConfig -> {
if(devConfig.getConfigKey().equals(SNOWY_SYS_DEFAULT_PASSWORD_KEY)) {
devConfig.setConfigValue(DesensitizedUtil.password(devConfig.getConfigValue()));
}
}).collect(Collectors.toList());
}
@Override
public void add(DevConfigAddParam devConfigAddParam) {
checkParam(devConfigAddParam);
DevConfig devConfig = BeanUtil.toBean(devConfigAddParam, DevConfig.class);
devConfig.setCategory(DevConfigCategoryEnum.BIZ_DEFINE.getValue());
this.save(devConfig);
}
private void checkParam(DevConfigAddParam devConfigAddParam) {
boolean hasSameConfig = this.count(new LambdaQueryWrapper<DevConfig>()
.eq(DevConfig::getConfigKey, devConfigAddParam.getConfigKey())) > 0;
if (hasSameConfig) {
throw new CommonException("存在重复的配置,配置键为:{}", devConfigAddParam.getConfigKey());
}
}
@Override
public void edit(DevConfigEditParam devConfigEditParam) {
DevConfig devConfig = this.queryEntity(devConfigEditParam.getId());
checkParam(devConfigEditParam);
BeanUtil.copyProperties(devConfigEditParam, devConfig);
devConfig.setCategory(DevConfigCategoryEnum.BIZ_DEFINE.getValue());
this.updateById(devConfig);
// 移除对应的缓存
commonCacheOperator.remove(CONFIG_CACHE_KEY + tenApi.getCurrentTenDomain() + ":" + devConfig.getConfigKey());
}
private void checkParam(DevConfigEditParam devConfigEditParam) {
boolean hasSameConfig = this.count(new LambdaQueryWrapper<DevConfig>()
.eq(DevConfig::getConfigKey, devConfigEditParam.getConfigKey())
.ne(DevConfig::getId, devConfigEditParam.getId())) > 0;
if (hasSameConfig) {
throw new CommonException("存在重复的配置,配置键为:{}", devConfigEditParam.getConfigKey());
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(List<DevConfigIdParam> devConfigIdParamList) {
List<String> devConfigIdList = CollStreamUtil.toList(devConfigIdParamList, DevConfigIdParam::getId);
if(ObjectUtil.isNotEmpty(devConfigIdList)) {
List<DevConfig> devConfigList = this.listByIds(devConfigIdList);
if(ObjectUtil.isNotEmpty(devConfigList)) {
List<String> devConfigResultList = CollectionUtil.newArrayList(devConfigList.stream()
.map(DevConfig::getCategory).collect(Collectors.toSet()));
if (devConfigResultList.size() != 1 || !devConfigResultList.get(0).equals(DevConfigCategoryEnum.BIZ_DEFINE.getValue())) {
throw new CommonException("不可删除系统内置配置");
}
List<DevConfig> deleteDevConfigList = this.listByIds(devConfigIdList);
// 执行删除
this.removeByIds(devConfigIdList);
deleteDevConfigList.forEach(devConfig -> {
// 移除对应的缓存
commonCacheOperator.remove(CONFIG_CACHE_KEY + tenApi.getCurrentTenDomain() + ":" + devConfig.getConfigKey());
});
}
}
}
@Override
public DevConfig detail(DevConfigIdParam devConfigIdParam) {
return this.queryEntity(devConfigIdParam.getId());
}
@Override
public DevConfig queryEntity(String id) {
DevConfig devConfig = this.getById(id);
if(ObjectUtil.isEmpty(devConfig)) {
throw new CommonException("配置不存在id值为{}", id);
}
return devConfig;
}
@Override
public void editBatch(List<DevConfigBatchParam> devConfigBatchParamList) {
devConfigBatchParamList.forEach(devConfigBatchParam -> {
this.update(new LambdaUpdateWrapper<DevConfig>()
.eq(DevConfig::getConfigKey, devConfigBatchParam.getConfigKey())
.set(DevConfig::getConfigValue, devConfigBatchParam.getConfigValue()));
// 移除对应的缓存
commonCacheOperator.remove(CONFIG_CACHE_KEY + tenApi.getCurrentTenDomain() + ":" + devConfigBatchParam.getConfigKey());
});
}
}

View File

@@ -0,0 +1,106 @@
package mjkf.xinke.dev.modular.dev;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import mjkf.xinke.dev.api.DevApi;
import mjkf.xinke.dev.modular.config.entity.DevConfig;
import mjkf.xinke.dev.modular.config.enums.DevConfigCategoryEnum;
import mjkf.xinke.dev.modular.config.service.DevConfigService;
import mjkf.xinke.dev.modular.dict.entity.DevDict;
import mjkf.xinke.dev.modular.dict.enums.DevDictCategoryEnum;
import mjkf.xinke.dev.modular.dict.service.DevDictService;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 开发工具模块综合API接口实现类
*
*
* @date 2022/9/26 14:30
**/
@Service
public class DevApiProvider implements DevApi {
@Resource
private DevDictService devDictService;
@Resource
private DevConfigService devConfigService;
@Transactional(rollbackFor = Exception.class)
@Override
public void initTenDataForCategoryId(String tenId, String tenName) {
// ==============给租户初始化配置=================
List<DevConfig> devConfigList = devConfigService.list(new LambdaQueryWrapper<DevConfig>().ne(DevConfig::getCategory,
DevConfigCategoryEnum.BIZ_DEFINE.getValue())).stream().peek(devConfig -> {
devConfig.setId(null);
devConfig.setTenantId(tenId);
// 非系统基础的设置为“未配置”
if(!devConfig.getCategory().equals(DevConfigCategoryEnum.SYS_BASE.getValue())) {
devConfig.setConfigValue("未配置");
}
// 将系统名称设置为租户名称
if("SNOWY_SYS_NAME".equals(devConfig.getConfigKey())) {
devConfig.setConfigValue(tenName);
}
// 将系统默认工作台数据设置为空
if("SNOWY_SYS_DEFAULT_WORKBENCH_DATA".equals(devConfig.getConfigKey())) {
devConfig.setConfigValue("{\"shortcut\":[]}");
}
}).collect(Collectors.toList());
// 批量保存配置
devConfigService.saveBatch(devConfigList);
// ==============给租户初始化字典=================
List<DevDict> devDictList = devDictService.list(new LambdaQueryWrapper<DevDict>().eq(DevDict::getCategory,
DevDictCategoryEnum.FRM.getValue()));
List<TreeNode<String>> treeNodeList = devDictList.stream().map(devDict ->
new TreeNode<>(devDict.getId(), devDict.getParentId(),
devDict.getDictLabel(), devDict.getSortCode()).setExtra(JSONUtil.parseObj(devDict)))
.collect(Collectors.toList());
List<Tree<String>> treeList = TreeUtil.build(treeNodeList, "0");
// 批量保存字典
execRecursionInsertDict("0", treeList, tenId);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void removeTenDataForCategoryId(String tenId) {
// ==============移除租户初始化的配置=================
devConfigService.remove(new LambdaQueryWrapper<DevConfig>().eq(DevConfig::getTenantId, tenId));
// ==============移除租户初始化的字典=================
devDictService.remove(new LambdaQueryWrapper<DevDict>().eq(DevDict::getTenantId, tenId));
}
/**
* 递归插入字典
*
*
* @date 2022/9/26 15:33
**/
private void execRecursionInsertDict(String parentId, List<Tree<String>> treeList, String tenId) {
treeList.forEach(tree -> {
DevDict devDict = JSONUtil.toBean(JSONUtil.parseObj(tree), DevDict.class);
devDict.setId(null);
devDict.setParentId(parentId);
devDict.setTenantId(tenId);
devDictService.save(devDict);
String dictId = devDict.getId();
List<Tree<String>> children = tree.getChildren();
if(ObjectUtil.isNotEmpty(children)) {
execRecursionInsertDict(dictId, children, tenId);
}
});
}
}

View File

@@ -0,0 +1,196 @@
package mjkf.xinke.dev.modular.dfc.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.modular.dfc.entity.DevDfc;
import mjkf.xinke.dev.modular.dfc.param.*;
import mjkf.xinke.dev.modular.dfc.result.DevDfcDbsSelectorResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableColumnResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableResult;
import mjkf.xinke.dev.modular.dfc.service.DevDfcService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 动态字段配置控制器
*
*
* @date 2023/08/04 08:18
*/
@Api(tags = "动态字段配置控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 1)
@RestController
@Validated
public class DevDfcController {
@Resource
private DevDfcService devDfcService;
/**
* 获取动态字段配置分页
*
*
* @date 2023/08/04 08:18
*/
@ApiOperationSupport(order = 1)
@ApiOperation("获取动态字段配置分页")
@GetMapping("/dev/dfc/page")
public CommonResult<Page<DevDfc>> page(DevDfcPageParam devDfcPageParam) {
return CommonResult.data(devDfcService.page(devDfcPageParam));
}
/**
* 添加动态字段配置
*
*
* @date 2023/08/04 08:18
*/
@ApiOperationSupport(order = 2)
@ApiOperation("添加动态字段配置")
@CommonLog("添加动态字段配置")
@PostMapping("/dev/dfc/add")
public CommonResult<String> add(@RequestBody @Valid DevDfcAddParam devDfcAddParam) {
devDfcService.add(devDfcAddParam);
return CommonResult.ok();
}
/**
* 编辑动态字段配置
*
*
* @date 2023/08/04 08:18
*/
@ApiOperationSupport(order = 3)
@ApiOperation("编辑动态字段配置")
@CommonLog("编辑动态字段配置")
@PostMapping("/dev/dfc/edit")
public CommonResult<String> edit(@RequestBody @Valid DevDfcEditParam devDfcEditParam) {
devDfcService.edit(devDfcEditParam);
return CommonResult.ok();
}
/**
* 删除动态字段配置
*
*
* @date 2023/08/04 08:18
*/
@ApiOperationSupport(order = 4)
@ApiOperation("删除动态字段配置")
@CommonLog("删除动态字段配置")
@PostMapping("/dev/dfc/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevDfcIdParam> devDfcIdParamList) {
devDfcService.delete(devDfcIdParamList);
return CommonResult.ok();
}
/**
* 获取动态字段配置详情
*
*
* @date 2023/08/04 08:18
*/
@ApiOperationSupport(order = 5)
@ApiOperation("获取动态字段配置详情")
@GetMapping("/dev/dfc/detail")
public CommonResult<DevDfc> detail(@Valid DevDfcIdParam devDfcIdParam) {
return CommonResult.data(devDfcService.detail(devDfcIdParam));
}
/**
* 获取所有数据源信息
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 6)
@ApiOperation("获取所有数据源信息")
@GetMapping("/dev/dfc/dbsSelector")
public CommonResult<List<DevDfcDbsSelectorResult>> dbsSelector() {
return CommonResult.data(devDfcService.dbsSelector());
}
/**
* 根据数据源id获取对应库所有表信息
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 7)
@ApiOperation("根据数据源id获取对应库所有表信息")
@GetMapping("/dev/dfc/tablesByDbsId")
public CommonResult<List<DevDfcTableResult>> tablesByDbsId(@Valid DevDfcDbsTableParam devDfcDbsTableParam) {
return CommonResult.data(devDfcService.tablesByDbsId(devDfcDbsTableParam));
}
/**
* 获取当前库数据表内所有字段信息
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 8)
@ApiOperation("获取当前库数据表内所有字段信息")
@GetMapping("/dev/dfc/tableColumns")
public CommonResult<List<DevDfcTableColumnResult>> tableColumns(@Valid DevDfcTableColumnParam genBasicTableColumnParam) {
return CommonResult.data(devDfcService.tableColumns(genBasicTableColumnParam));
}
/**
* 根据数据源id获取对应库数据表内所有字段信息
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 9)
@ApiOperation("根据数据源id获取对应库数据表内所有字段信息")
@GetMapping("/dev/dfc/tableColumnsByDbsId")
public CommonResult<List<DevDfcTableColumnResult>> tableColumnsByDbsId(@Valid DevDfcDbsTableColumnParam dbsTableColumnParam) {
return CommonResult.data(devDfcService.tableColumnsByDbsId(dbsTableColumnParam));
}
/**
* 获取当前库所有表信息
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 10)
@ApiOperation("获取当前库所有表信息")
@GetMapping("/dev/dfc/tables")
public CommonResult<List<DevDfcTableResult>> tables() {
return CommonResult.data(devDfcService.tables());
}
/**
* 迁移数据
*
*
* @date 2023/8/7 22:53
*/
@ApiOperationSupport(order = 11)
@ApiOperation("迁移数据")
@PostMapping("/dev/dfc/migrate")
public CommonResult<String> migrate(@RequestBody @Valid DevDfcMigrateParam devDfcMigrateParam) {
devDfcService.migrate(devDfcMigrateParam);
return CommonResult.ok();
}
}

View File

@@ -0,0 +1,89 @@
package mjkf.xinke.dev.modular.dfc.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fhs.core.trans.vo.TransPojo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
import java.math.BigDecimal;
import java.util.Date;
/**
* 动态字段配置实体
*
*
* @date 2023/08/04 08:18
**/
@Getter
@Setter
@TableName("DEV_DFC")
public class DevDfc extends CommonEntity implements TransPojo {
/** 主键 */
@TableId
@ApiModelProperty(value = "主键", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 数据源 */
@ApiModelProperty(value = "数据源", position = 3)
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称", position = 4)
private String tableName;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 5)
private Integer sortCode;
/** 表单域属性名 */
@ApiModelProperty(value = "表单域属性名", position = 6)
private String name;
/** 标签文本 */
@ApiModelProperty(value = "标签文本", position = 7)
private String label;
/** 必填 */
@ApiModelProperty(value = "必填", position = 8)
private Integer required;
/** 提示语 */
@ApiModelProperty(value = "提示语", position = 9)
private String placeholder;
/** 字段类型 */
@ApiModelProperty(value = "字段类型", position = 10)
private String type;
/** 选择项类型 */
@ApiModelProperty(value = "选择项类型", position = 11)
private String selectOptionType;
/** 字典 */
@ApiModelProperty(value = "字典", position = 12)
private String dictTypeCode;
/** 选择项api地址 */
@ApiModelProperty(value = "选择项api地址", position = 13)
private String selOptionApiUrl;
/** 已选择数据api地址 */
@ApiModelProperty(value = "已选择数据api地址", position = 14)
private String selDataApiUrl;
/** 是否多选 */
@ApiModelProperty(value = "是否多选", position = 15)
private Integer isMultiple;
/** 状态 */
@ApiModelProperty(value = "状态", position = 16)
private String status;
}

View File

@@ -0,0 +1,30 @@
package mjkf.xinke.dev.modular.dfc.enums;
import lombok.Getter;
/**
* 动态字段配置枚举
*
*
* @date 2023/08/04 08:18
**/
@Getter
public enum DevDfcEnum {
/**
* 正常
*/
ENABLE("ENABLE"),
/**
* 停用
*/
DISABLED("DISABLED");
private final String value;
DevDfcEnum(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,14 @@
package mjkf.xinke.dev.modular.dfc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.dfc.entity.DevDfc;
/**
* 动态字段配置Mapper接口
*
*
* @date 2023/08/04 08:18
**/
public interface DevDfcMapper extends BaseMapper<DevDfc> {
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.dfc.mapper.DevDfcMapper">
</mapper>

View File

@@ -0,0 +1,74 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
/**
* 动态字段配置添加参数
*
*
* @date 2023/08/04 08:18
**/
@Getter
@Setter
public class DevDfcAddParam {
/** 数据源 */
@ApiModelProperty(value = "数据源", position = 1)
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称", position = 2)
private String tableName;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 3)
private Integer sortCode;
/** 表单域属性名 */
@ApiModelProperty(value = "表单域属性名", position = 4)
private String name;
/** 标签文本 */
@ApiModelProperty(value = "标签文本", position = 5)
private String label;
/** 必填 */
@ApiModelProperty(value = "必填", position = 6)
private Integer required;
/** 提示语 */
@ApiModelProperty(value = "提示语", position = 7)
private String placeholder;
/** 字段类型 */
@ApiModelProperty(value = "字段类型", position = 8)
private String type;
/** 选择项类型 */
@ApiModelProperty(value = "选择项类型", position = 9)
private String selectOptionType;
/** 字典 */
@ApiModelProperty(value = "字典", position = 10)
private String dictTypeCode;
/** 选择项api地址 */
@ApiModelProperty(value = "选择项api地址", position = 11)
private String selOptionApiUrl;
/** 已选择数据api地址 */
@ApiModelProperty(value = "已选择数据api地址", position = 12)
private String selDataApiUrl;
/** 是否多选 */
@ApiModelProperty(value = "是否多选", position = 13)
private Integer isMultiple;
}

View File

@@ -0,0 +1,29 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 数据源库字段列表参数
*
*
* @date 2022/7/29 9:59
*/
@Getter
@Setter
public class DevDfcDbsTableColumnParam {
/** 数据源Id */
@ApiModelProperty(value = "数据源Id", required = true, position = 1)
@NotBlank(message = "dbsId不能为空")
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称", required = true, position = 1)
@NotBlank(message = "tableName不能为空")
private String tableName;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 数据源库表参数
*
*
* @date 2022/7/29 9:59
*/
@Getter
@Setter
public class DevDfcDbsTableParam {
/** 数据源Id */
@ApiModelProperty(value = "数据源Id", required = true, position = 1)
@NotBlank(message = "dbsId不能为空")
private String dbsId;
}

View File

@@ -0,0 +1,83 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
/**
* 动态字段配置编辑参数
*
*
* @date 2023/08/04 08:18
**/
@Getter
@Setter
public class DevDfcEditParam {
/** 主键 */
@ApiModelProperty(value = "主键", required = true, position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 数据源 */
@ApiModelProperty(value = "数据源", position = 2)
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称", position = 3)
private String tableName;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 4)
private Integer sortCode;
/** 表单域属性名 */
@ApiModelProperty(value = "表单域属性名", position = 5)
private String name;
/** 标签文本 */
@ApiModelProperty(value = "标签文本", position = 6)
private String label;
/** 必填 */
@ApiModelProperty(value = "必填", position = 7)
private Integer required;
/** 提示语 */
@ApiModelProperty(value = "提示语", position = 8)
private String placeholder;
/** 字段类型 */
@ApiModelProperty(value = "字段类型", position = 9)
private String type;
/** 选择项类型 */
@ApiModelProperty(value = "选择项类型", position = 10)
private String selectOptionType;
/** 字典 */
@ApiModelProperty(value = "字典", position = 11)
private String dictTypeCode;
/** 选择项api地址 */
@ApiModelProperty(value = "选择项api地址", position = 12)
private String selOptionApiUrl;
/** 已选择数据api地址 */
@ApiModelProperty(value = "已选择数据api地址", position = 13)
private String selDataApiUrl;
/** 是否多选 */
@ApiModelProperty(value = "是否多选", position = 14)
private Integer isMultiple;
/** 状态 */
@ApiModelProperty(value = "状态", position = 15)
private String status;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 动态字段配置Id参数
*
*
* @date 2023/08/04 08:18
**/
@Getter
@Setter
public class DevDfcIdParam {
/** 主键 */
@ApiModelProperty(value = "主键", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,38 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 迁移数据参数
*
*
* @date 2023/8/7 22:59
*/
@Getter
@Setter
public class DevDfcMigrateParam {
/** 主键 */
@ApiModelProperty(value = "主键", required = true, position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 数据源Id */
@ApiModelProperty(value = "数据源Id", required = true, position = 2)
@NotBlank(message = "dbsId不能为空")
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称", required = true, position = 3)
@NotBlank(message = "tableName不能为空")
private String tableName;
/** 字段名称 */
@ApiModelProperty(value = "字段名称", required = true, position = 4)
@NotBlank(message = "tableColumn不能为空")
private String tableColumn;
}

View File

@@ -0,0 +1,48 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.util.Date;
/**
* 动态字段配置查询参数
*
*
* @date 2023/08/04 08:18
**/
@Getter
@Setter
public class DevDfcPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 关键词 */
@ApiModelProperty(value = "关键词")
private String searchKey;
/** 数据源 */
@ApiModelProperty(value = "数据源")
private String dbsId;
/** 表名称 */
@ApiModelProperty(value = "表名称")
private String tableName;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.dfc.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 数据源库字段列表参数
*
*
* @date 2022/7/29 9:59
*/
@Getter
@Setter
public class DevDfcTableColumnParam {
/** 表名称 */
@ApiModelProperty(value = "表名称", required = true)
@NotBlank(message = "tableName不能为空")
private String tableName;
}

View File

@@ -0,0 +1,28 @@
package mjkf.xinke.dev.modular.dfc.provider;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Service;
import mjkf.xinke.dev.api.DevDfcApi;
import mjkf.xinke.dev.modular.dfc.service.DevDfcService;
import javax.annotation.Resource;
import java.util.List;
/**
* 动态字段API接口实现类
*
*
* @date 2022/9/2 16:05
*/
@Service
public class DevDfcApiProvider implements DevDfcApi {
@Resource
private DevDfcService devDfcService;
@Override
public List<JSONObject> getTableFieldList(String dbsId, String tableName) {
return devDfcService.getTableFieldList(dbsId, tableName);
}
}

View File

@@ -0,0 +1,25 @@
package mjkf.xinke.dev.modular.dfc.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 代码生成模块所需要用到的数据源选择器的结果
*
*
* @date 2022/7/19 18:55
**/
@Getter
@Setter
public class DevDfcDbsSelectorResult {
/** id */
@ApiModelProperty(value = "id", position = 1)
private String id;
/** 名称 */
@ApiModelProperty(value = "名称", position = 2)
private String poolName;
}

View File

@@ -0,0 +1,29 @@
package mjkf.xinke.dev.modular.dfc.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 数据库表字段结果
*
*
* @date 2022/7/19 19:06
**/
@Getter
@Setter
public class DevDfcTableColumnResult {
/** 字段名称 */
@ApiModelProperty(value = "字段名称", position = 1)
private String columnName;
/** 字段类型 */
@ApiModelProperty(value = "字段类型", position = 2)
private String typeName;
/** 字段注释 */
@ApiModelProperty(value = "字段注释", position = 3)
private String columnRemark;
}

View File

@@ -0,0 +1,25 @@
package mjkf.xinke.dev.modular.dfc.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 数据库表字段结果
*
*
* @date 2022/7/19 19:06
**/
@Getter
@Setter
public class DevDfcTableResult {
/** 表名称 */
@ApiModelProperty(value = "表名称", position = 1)
private String tableName;
/** 表注释 */
@ApiModelProperty(value = "表注释", position = 2)
private String tableRemark;
}

View File

@@ -0,0 +1,126 @@
package mjkf.xinke.dev.modular.dfc.service;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import mjkf.xinke.dev.modular.dfc.entity.DevDfc;
import mjkf.xinke.dev.modular.dfc.param.*;
import mjkf.xinke.dev.modular.dfc.result.DevDfcDbsSelectorResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableColumnResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableResult;
import java.util.List;
/**
* 动态字段配置Service接口
*
*
* @date 2023/08/04 08:18
**/
public interface DevDfcService extends IService<DevDfc> {
/**
* 获取动态字段配置分页
*
*
* @date 2023/08/04 08:18
*/
Page<DevDfc> page(DevDfcPageParam devDfcPageParam);
/**
* 添加动态字段配置
*
*
* @date 2023/08/04 08:18
*/
void add(DevDfcAddParam devDfcAddParam);
/**
* 编辑动态字段配置
*
*
* @date 2023/08/04 08:18
*/
void edit(DevDfcEditParam devDfcEditParam);
/**
* 删除动态字段配置
*
*
* @date 2023/08/04 08:18
*/
void delete(List<DevDfcIdParam> devDfcIdParamList);
/**
* 获取动态字段配置详情
*
*
* @date 2023/08/04 08:18
*/
DevDfc detail(DevDfcIdParam devDfcIdParam);
/**
* 获取动态字段配置详情
*
*
* @date 2023/08/04 08:18
**/
DevDfc queryEntity(String id);
/**
* 获取所有数据源信息
*
*
* @date 2023/2/1 10:43
**/
List<DevDfcDbsSelectorResult> dbsSelector();
/**
* 获取所有表信息
*
*
* @date 2022/10/25 22:33
**/
List<DevDfcTableResult> tablesByDbsId(DevDfcDbsTableParam devDfcDbsTableParam);
/**
* 获取所有表信息
*
*
* @date 2022/10/25 22:33
**/
List<DevDfcTableResult> tables();
/**
* 获取表内所有字段信息
*
*
* @date 2022/10/25 22:33
**/
List<DevDfcTableColumnResult> tableColumns(DevDfcTableColumnParam genBasicTableColumnParam);
/**
* 获取表内所有字段信息
*
*
* @date 2022/10/25 22:33
**/
List<DevDfcTableColumnResult> tableColumnsByDbsId(DevDfcDbsTableColumnParam dbsTableColumnParam);
/**
* 获取表中字段配置
*
*
* @date 2023/08/02 21:31
*/
List<JSONObject> getTableFieldList(String dbsId, String tableName);
/**
* 迁移数据
*
*
* @date 2023/08/02 21:31
*/
void migrate(DevDfcMigrateParam devDfcMigrateParam);
}

View File

@@ -0,0 +1,368 @@
package mjkf.xinke.dev.modular.dfc.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import mjkf.xinke.common.enums.CommonSortOrderEnum;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.page.CommonPageRequest;
import mjkf.xinke.dbs.api.DbsApi;
import mjkf.xinke.dev.modular.dfc.entity.DevDfc;
import mjkf.xinke.dev.modular.dfc.enums.DevDfcEnum;
import mjkf.xinke.dev.modular.dfc.mapper.DevDfcMapper;
import mjkf.xinke.dev.modular.dfc.param.*;
import mjkf.xinke.dev.modular.dfc.result.DevDfcDbsSelectorResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableColumnResult;
import mjkf.xinke.dev.modular.dfc.result.DevDfcTableResult;
import mjkf.xinke.dev.modular.dfc.service.DevDfcService;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 动态字段配置Service接口实现类
*
*
* @date 2023/08/04 08:18
**/
@Service
public class DevDfcServiceImpl extends ServiceImpl<DevDfcMapper, DevDfc> implements DevDfcService {
private static final String DB_URL_KEY = "spring.datasource.dynamic.datasource.master.url";
private static final String DB_USERNAME_KEY = "spring.datasource.dynamic.datasource.master.username";
private static final String DB_PASSWORD_KEY = "spring.datasource.dynamic.datasource.master.password";
@Resource
private Environment environment;
@Resource
private DbsApi dbsApi;
@Override
public Page<DevDfc> page(DevDfcPageParam devDfcPageParam) {
QueryWrapper<DevDfc> queryWrapper = new QueryWrapper<>();
if(ObjectUtil.isNotEmpty(devDfcPageParam.getDbsId())) {
queryWrapper.lambda().eq(DevDfc::getDbsId, devDfcPageParam.getDbsId());
}
if(ObjectUtil.isNotEmpty(devDfcPageParam.getTableName())) {
queryWrapper.lambda().eq(DevDfc::getTableName, devDfcPageParam.getTableName());
}
if(ObjectUtil.isAllNotEmpty(devDfcPageParam.getSortField(), devDfcPageParam.getSortOrder())) {
CommonSortOrderEnum.validate(devDfcPageParam.getSortOrder());
queryWrapper.orderBy(true, devDfcPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
StrUtil.toUnderlineCase(devDfcPageParam.getSortField()));
} else {
queryWrapper.lambda().orderByAsc(DevDfc::getDbsId).orderByAsc(DevDfc::getTableName).orderByAsc(DevDfc::getSortCode);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void add(DevDfcAddParam devDfcAddParam) {
DevDfc devDfc = BeanUtil.toBean(devDfcAddParam, DevDfc.class);
boolean repeatName = this.count(new LambdaQueryWrapper<DevDfc>()
.eq(DevDfc::getDbsId, devDfc.getDbsId())
.eq(DevDfc::getTableName, devDfc.getTableName())
.eq(DevDfc::getName, devDfc.getName())) > 0;
if(repeatName) {
throw new CommonException("存在重复的表单域属性名,表单域属性名为:{}", devDfc.getName());
}
devDfc.setStatus(DevDfcEnum.ENABLE.getValue());
this.save(devDfc);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void edit(DevDfcEditParam devDfcEditParam) {
DevDfc devDfc = this.queryEntity(devDfcEditParam.getId());
BeanUtil.copyProperties(devDfcEditParam, devDfc);
boolean repeatName = this.count(new LambdaQueryWrapper<DevDfc>()
.eq(DevDfc::getDbsId, devDfc.getDbsId())
.eq(DevDfc::getTableName, devDfc.getTableName())
.eq(DevDfc::getName, devDfc.getName())
.ne(DevDfc::getId, devDfc.getId())) > 0;
if(repeatName) {
throw new CommonException("存在重复的表单域属性名,表单域属性名为:{}", devDfc.getName());
}
this.updateById(devDfc);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(List<DevDfcIdParam> devDfcIdParamList) {
// 执行删除
this.removeByIds(CollStreamUtil.toList(devDfcIdParamList, DevDfcIdParam::getId));
}
@Override
public DevDfc detail(DevDfcIdParam devDfcIdParam) {
return this.queryEntity(devDfcIdParam.getId());
}
@Override
public DevDfc queryEntity(String id) {
DevDfc devDfc = this.getById(id);
if(ObjectUtil.isEmpty(devDfc)) {
throw new CommonException("动态字段配置不存在id值为{}", id);
}
return devDfc;
}
@Override
public List<DevDfcDbsSelectorResult> dbsSelector() {
return dbsApi.dbsSelector().stream()
.map(jsonObject -> JSONUtil.toBean(jsonObject, DevDfcDbsSelectorResult.class)).collect(Collectors.toList());
}
@Override
public List<DevDfcTableResult> tablesByDbsId(DevDfcDbsTableParam devDfcDbsTableParam) {
JSONObject jsonObject = dbsApi.dbsDetail(devDfcDbsTableParam.getDbsId());
String poolName = jsonObject.getStr("poolName");
String url = jsonObject.getStr("url");
String userName = jsonObject.getStr("username");
String password = jsonObject.getStr("password");
if(ObjectUtil.hasEmpty(url, userName, password)) {
throw new CommonException("数据源{}配置信息不完整", poolName);
}
return queryTables(url, userName, password);
}
@Override
public List<DevDfcTableResult> tables() {
String url = environment.getProperty(DB_URL_KEY);
String userName = environment.getProperty(DB_USERNAME_KEY);
String password = environment.getProperty(DB_PASSWORD_KEY);
if(ObjectUtil.hasEmpty(url, userName, password)) {
throw new CommonException("当前数据源配置信息不完整");
}
return this.queryTables(url, userName, password);
}
@Override
public List<DevDfcTableColumnResult> tableColumns(DevDfcTableColumnParam genBasicTableColumnParam) {
String url = environment.getProperty(DB_URL_KEY);
String userName = environment.getProperty(DB_USERNAME_KEY);
String password = environment.getProperty(DB_PASSWORD_KEY);
if(ObjectUtil.hasEmpty(url, userName, password)) {
throw new CommonException("当前数据源配置信息不完整");
}
return this.queryTableColumns(url, userName, password, genBasicTableColumnParam.getTableName());
}
@Override
public List<DevDfcTableColumnResult> tableColumnsByDbsId(DevDfcDbsTableColumnParam dbsTableColumnParam) {
JSONObject jsonObject = dbsApi.dbsDetail(dbsTableColumnParam.getDbsId());
String poolName = jsonObject.getStr("poolName");
String url = jsonObject.getStr("url");
String userName = jsonObject.getStr("username");
String password = jsonObject.getStr("password");
if(ObjectUtil.hasEmpty(url, userName, password)) {
throw new CommonException("数据源{}配置信息不完整", poolName);
}
return queryTableColumns(url, userName, password, dbsTableColumnParam.getTableName());
}
@Override
public List<JSONObject> getTableFieldList(String dbsId, String tableName) {
QueryWrapper<DevDfc> queryWrapper = new QueryWrapper<>();
if(ObjectUtil.isNotEmpty(dbsId)) {
queryWrapper.lambda().eq(DevDfc::getDbsId, dbsId);
}
if(ObjectUtil.isNotEmpty(tableName)) {
queryWrapper.lambda().eq(DevDfc::getTableName, tableName);
}
queryWrapper.lambda().eq(DevDfc::getStatus, DevDfcEnum.ENABLE.getValue());
queryWrapper.lambda().orderByAsc(DevDfc::getSortCode);
return this.list(queryWrapper)
.stream()
.map(item -> JSONUtil.createObj()
.set("name", item.getName())
.set("label", item.getLabel())
.set("type", item.getType())
.set("required", item.getRequired())
.set("placeholder", item.getPlaceholder())
.set("selectOptionType", item.getSelectOptionType())
.set("dictTypeCode", item.getDictTypeCode())
.set("selOptionApiUrl", item.getSelOptionApiUrl())
.set("selDataApiUrl", item.getSelDataApiUrl())
.set("isMultiple", item.getIsMultiple())
)
.collect(Collectors.toList());
}
@Override
public void migrate(DevDfcMigrateParam devDfcMigrateParam) {
String url = environment.getProperty(DB_URL_KEY);
String userName = environment.getProperty(DB_USERNAME_KEY);
String password = environment.getProperty(DB_PASSWORD_KEY);
if (!"master".equals(devDfcMigrateParam.getDbsId())){
JSONObject jsonObject = dbsApi.dbsDetail(devDfcMigrateParam.getDbsId());
url = jsonObject.getStr("url");
userName = jsonObject.getStr("username");
password = jsonObject.getStr("password");
}
if(ObjectUtil.hasEmpty(url, userName, password)) {
throw new CommonException("当前数据源配置信息不完整");
}
this.migrateData(url, userName, password, devDfcMigrateParam);
}
/**
* 迁移数据
*
*
* @date 2023/2/1 10:31
**/
private void migrateData(String url, String userName, String password, DevDfcMigrateParam devDfcMigrateParam){
Connection conn = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, userName, password);
DevDfc devDfc = this.getById(devDfcMigrateParam.getId());
boolean executeUpdateSql = false;
PreparedStatement preparedStatement = conn.prepareStatement(StrUtil.format("SELECT COUNT(*) FROM {} WHERE {} IS NOT NULL", devDfc.getTableName(), devDfcMigrateParam.getTableColumn()));
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
if (resultSet.getInt(1) <= 0){
executeUpdateSql = true;
}
}
if(!executeUpdateSql) {
preparedStatement.close();
conn.close();
throw new CommonException("目标字段存在数据,终止数据迁移");
}
if (url.toLowerCase().contains("jdbc:mysql")) {
preparedStatement = conn.prepareStatement(StrUtil.format("UPDATE {} SET {} = JSON_UNQUOTE(JSON_EXTRACT(EXT_JSON, '$.{}'))", devDfc.getTableName(), devDfcMigrateParam.getTableColumn(), devDfc.getName()));
}
if (!url.toLowerCase().contains("jdbc:mysql")) {
preparedStatement = conn.prepareStatement(StrUtil.format("UPDATE {} SET {} = JSON_VALUE(EXT_JSON, '$.{}')", devDfc.getTableName(), devDfcMigrateParam.getTableColumn(), devDfc.getName()));
}
preparedStatement.executeUpdate();
preparedStatement.close();
conn.close();
} catch (SQLException sqlException) {
sqlException.printStackTrace();
throw new CommonException("获取数据库表失败");
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeConnection(conn);
}
}
/**
* 查询指定数据源中的所有表
*
*
* @date 2023/2/1 10:31
**/
private List<DevDfcTableResult> queryTables(String url, String userName, String password) {
Connection conn = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, userName, password);
DatabaseMetaData metaData = conn.getMetaData();
String schema = null;
if (metaData.getURL().toLowerCase().contains("jdbc:oracle")) {
schema = metaData.getUserName();
}
List<DevDfcTableResult> tables = new ArrayList<>();
rs = metaData.getTables(null, schema, "%", new String[]{"TABLE"});
while (rs.next()) {
String tableName = rs.getString("TABLE_NAME");
if (!StrUtil.startWithIgnoreCase(tableName, "ACT_")
&& !StrUtil.startWithIgnoreCase(tableName, "CLIENT_")
&& !StrUtil.startWithIgnoreCase(tableName, "DEV_")
&& !StrUtil.startWithIgnoreCase(tableName, "EXT_")
&& !StrUtil.startWithIgnoreCase(tableName, "GEN_")
&& !StrUtil.startWithIgnoreCase(tableName, "MOBILE_")
&& !StrUtil.startWithIgnoreCase(tableName, "PAY_")
&& !StrUtil.startWithIgnoreCase(tableName, "AUTH_")
) {
DevDfcTableResult genBasicTableResult = new DevDfcTableResult();
genBasicTableResult.setTableName(tableName);
String remarks = rs.getString("REMARKS");
if(ObjectUtil.isEmpty(remarks)) {
genBasicTableResult.setTableRemark(tableName);
} else {
genBasicTableResult.setTableRemark(remarks);
}
tables.add(genBasicTableResult);
}
}
return tables;
} catch (SQLException sqlException) {
sqlException.printStackTrace();
throw new CommonException("获取数据库表失败");
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeConnection(conn);
}
}
/**
* 查询指定数据源中指定表的所有字段
*
*
* @date 2023/2/1 11:09
**/
private List<DevDfcTableColumnResult> queryTableColumns(String url, String userName, String password, String tableName) {
Connection conn = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(url, userName, password);
DatabaseMetaData metaData = conn.getMetaData();
String schema = null;
if (metaData.getURL().toLowerCase().contains("jdbc:oracle")) {
schema = metaData.getUserName();
}
List<DevDfcTableColumnResult> columns = new ArrayList<>();
rs = metaData.getColumns(null, schema, tableName, "%");
while (rs.next()) {
String columnName = rs.getString("COLUMN_NAME").toUpperCase();
DevDfcTableColumnResult devDfcTableColumnResult = new DevDfcTableColumnResult();
devDfcTableColumnResult.setColumnName(columnName);
String remarks = rs.getString("REMARKS");
if(ObjectUtil.isEmpty(remarks)) {
devDfcTableColumnResult.setColumnRemark(columnName);
} else {
devDfcTableColumnResult.setColumnRemark(remarks);
}
String typeName = rs.getString("TYPE_NAME").toUpperCase();
if(ObjectUtil.isEmpty(typeName)) {
devDfcTableColumnResult.setTypeName("NONE");
} else {
devDfcTableColumnResult.setTypeName(typeName);
}
columns.add(devDfcTableColumnResult);
}
return columns;
} catch (SQLException sqlException) {
sqlException.printStackTrace();
throw new CommonException("获取数据库表字段失败,表名称:{}", tableName);
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeConnection(conn);
}
}
}

View File

@@ -0,0 +1,139 @@
package mjkf.xinke.dev.modular.dict.controller;
import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.modular.dict.entity.DevDict;
import mjkf.xinke.dev.modular.dict.param.*;
import mjkf.xinke.dev.modular.dict.service.DevDictService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 字典控制器
*
*
* @date 2022/6/21 14:58
**/
@Api(tags = "字典控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 2)
@RestController
@Validated
public class DevDictController {
@Resource
private DevDictService devDictService;
/**
* 获取字典分页
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 1)
@ApiOperation("获取字典分页")
@GetMapping("/dev/dict/page")
public CommonResult<Page<DevDict>> page(DevDictPageParam devDictPageParam) {
return CommonResult.data(devDictService.page(devDictPageParam));
}
/**
* 获取字典列表
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 2)
@ApiOperation("获取字典列表")
@GetMapping("/dev/dict/list")
public CommonResult<List<DevDict>> list(DevDictListParam devDictListParam) {
return CommonResult.data(devDictService.list(devDictListParam));
}
/**
* 获取字典树
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 3)
@ApiOperation("获取字典树")
@GetMapping("/dev/dict/tree")
public CommonResult<List<Tree<String>>> tree(DevDictTreeParam devDictTreeParam) {
return CommonResult.data(devDictService.tree(devDictTreeParam));
}
/**
* 添加字典
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 4)
@ApiOperation("添加字典")
@CommonLog("添加字典")
@PostMapping("/dev/dict/add")
public CommonResult<String> add(@RequestBody @Valid DevDictAddParam devDictAddParam) {
devDictService.add(devDictAddParam);
return CommonResult.ok();
}
/**
* 编辑字典
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 5)
@ApiOperation("编辑字典")
@CommonLog("编辑字典")
@PostMapping("/dev/dict/edit")
public CommonResult<String> edit(@RequestBody @Valid DevDictEditParam devDictEditParam) {
devDictService.edit(devDictEditParam);
return CommonResult.ok();
}
/**
* 删除字典
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 6)
@ApiOperation("删除字典")
@CommonLog("删除字典")
@PostMapping("/dev/dict/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevDictIdParam> devDictIdParamList) {
devDictService.delete(devDictIdParamList);
return CommonResult.ok();
}
/**
* 获取字典详情
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 7)
@ApiOperation("获取字典详情")
@GetMapping("/dev/dict/detail")
public CommonResult<DevDict> detail(@Valid DevDictIdParam devDictIdParam) {
return CommonResult.data(devDictService.detail(devDictIdParam));
}
}

View File

@@ -0,0 +1,55 @@
package mjkf.xinke.dev.modular.dict.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
/**
* 字典实体
*
*
* @date 2022/2/23 18:27
**/
@Getter
@Setter
@TableName("DEV_DICT")
public class DevDict extends CommonEntity {
/** id */
@ApiModelProperty(value = "id", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 父id */
@ApiModelProperty(value = "父id", position = 3)
private String parentId;
/** 字典文字 */
@ApiModelProperty(value = "字典文字", position = 4)
private String dictLabel;
/** 字典值 */
@ApiModelProperty(value = "字典值", position = 5)
private String dictValue;
/** 分类 */
@ApiModelProperty(value = "分类", position = 6)
private String category;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 7)
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 8)
@TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
private String extJson;
}

View File

@@ -0,0 +1,38 @@
package mjkf.xinke.dev.modular.dict.enums;
import lombok.Getter;
import mjkf.xinke.common.exception.CommonException;
/**
* 字典分类枚举
*
*
* @date 2022/7/6 22:21
*/
@Getter
public enum DevDictCategoryEnum {
/**
* 框架
*/
FRM("FRM"),
/**
* 业务
*/
BIZ("BIZ");
private final String value;
DevDictCategoryEnum(String value) {
this.value = value;
}
public static void validate(String value) {
boolean flag = FRM.getValue().equals(value) || BIZ.getValue().equals(value);
if(!flag) {
throw new CommonException("不支持的字典分类:{}", value);
}
}
}

View File

@@ -0,0 +1,14 @@
package mjkf.xinke.dev.modular.dict.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.dict.entity.DevDict;
/**
* 字典Mapper接口
*
*
* @date 2022/4/22 10:40
**/
public interface DevDictMapper extends BaseMapper<DevDict> {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.dict.mapper.DevDictMapper">
</mapper>

View File

@@ -0,0 +1,49 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 字典添加参数
*
*
* @date 2022/7/30 21:48
*/
@Getter
@Setter
public class DevDictAddParam {
/** 父id */
@ApiModelProperty(value = "父id", position = 1)
@NotBlank(message = "parentId不能为空")
private String parentId;
/** 字典文字 */
@ApiModelProperty(value = "字典文字", position = 2)
@NotBlank(message = "dictLabel不能为空")
private String dictLabel;
/** 字典值 */
@ApiModelProperty(value = "字典值", position = 3)
@NotBlank(message = "dictValue不能为空")
private String dictValue;
/** 分类 */
@ApiModelProperty(value = "分类", position = 4)
@NotBlank(message = "category不能为空")
private String category;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 5)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 6)
private String extJson;
}

View File

@@ -0,0 +1,54 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 字典编辑参数
*
*
* @date 2022/7/30 21:48
*/
@Getter
@Setter
public class DevDictEditParam {
/** id */
@ApiModelProperty(value = "id", position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 父id */
@ApiModelProperty(value = "父id", position = 2)
@NotBlank(message = "parentId不能为空")
private String parentId;
/** 字典文字 */
@ApiModelProperty(value = "字典文字", position = 3)
@NotBlank(message = "dictLabel不能为空")
private String dictLabel;
/** 字典值 */
@ApiModelProperty(value = "字典值", position = 4)
@NotBlank(message = "dictValue不能为空")
private String dictValue;
/** 分类 */
@ApiModelProperty(value = "分类", position = 5)
@NotBlank(message = "category不能为空")
private String category;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 6)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 7)
private String extJson;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 字典Id参数
*
*
* @date 2022/7/30 21:48
*/
@Getter
@Setter
public class DevDictIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,25 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 字典列表参数
*
*
* @date 2022/7/30 21:49
*/
@Getter
@Setter
public class DevDictListParam {
/** 父id */
@ApiModelProperty(value = "父id")
private String parentId;
/** 字典分类 */
@ApiModelProperty(value = "字典分类")
private String category;
}

View File

@@ -0,0 +1,45 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 字典查询参数
*
*
* @date 2022/7/30 21:49
*/
@Getter
@Setter
public class DevDictPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 父id */
@ApiModelProperty(value = "父id")
private String parentId;
/** 字典分类 */
@ApiModelProperty(value = "字典分类")
private String category;
/** 字典文字关键词 */
@ApiModelProperty(value = "字典文字关键词")
private String searchKey;
}

View File

@@ -0,0 +1,21 @@
package mjkf.xinke.dev.modular.dict.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 字典树参数
*
*
* @date 2022/7/30 21:49
*/
@Getter
@Setter
public class DevDictTreeParam {
/** 字典分类 */
@ApiModelProperty(value = "字典分类")
private String category;
}

View File

@@ -0,0 +1,15 @@
package mjkf.xinke.dev.modular.dict.provider;
import org.springframework.stereotype.Service;
import mjkf.xinke.dev.api.DevDictApi;
/**
* 字典API接口实现类
*
*
* @date 2022/9/2 16:05
*/
@Service
public class DevDictApiProvider implements DevDictApi {
}

View File

@@ -0,0 +1,83 @@
package mjkf.xinke.dev.modular.dict.service;
import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import mjkf.xinke.dev.modular.dict.entity.DevDict;
import mjkf.xinke.dev.modular.dict.param.*;
import java.util.List;
/**
* 字典Service接口
*
*
* @date 2022/4/22 10:41
**/
public interface DevDictService extends IService<DevDict> {
/**
* 获取字典分页
*
*
* @date 2022/4/24 20:08
*/
Page<DevDict> page(DevDictPageParam devDictPageParam);
/**
* 获取字典列表
*
*
* @date 2022/4/24 20:08
*/
List<DevDict> list(DevDictListParam devDictListParam);
/**
* 获取字典树
*
*
* @date 2022/4/24 20:08
*/
List<Tree<String>> tree(DevDictTreeParam devDictTreeParam);
/**
* 添加字典
*
*
* @date 2022/4/24 20:48
*/
void add(DevDictAddParam devDictAddParam);
/**
* 编辑字典
*
*
* @date 2022/4/24 21:13
*/
void edit(DevDictEditParam devDictEditParam);
/**
* 删除字典
*
*
* @date 2022/4/24 21:18
*/
void delete(List<DevDictIdParam> devDictIdParamList);
/**
* 获取字典详情
*
*
* @date 2022/4/24 21:18
*/
DevDict detail(DevDictIdParam devDictIdParam);
/**
* 获取字典详情
*
*
* @date 2022/4/24 21:18
*/
DevDict queryEntity(String id);
}

View File

@@ -0,0 +1,209 @@
package mjkf.xinke.dev.modular.dict.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fhs.trans.service.impl.DictionaryTransService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import mjkf.xinke.common.enums.CommonSortOrderEnum;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.page.CommonPageRequest;
import mjkf.xinke.dev.modular.dict.entity.DevDict;
import mjkf.xinke.dev.modular.dict.enums.DevDictCategoryEnum;
import mjkf.xinke.dev.modular.dict.mapper.DevDictMapper;
import mjkf.xinke.dev.modular.dict.param.*;
import mjkf.xinke.dev.modular.dict.service.DevDictService;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* 字典Service接口实现类
*
*
* @date 2022/4/22 10:41
**/
@Service
public class DevDictServiceImpl extends ServiceImpl<DevDictMapper, DevDict> implements DevDictService, InitializingBean {
private static final String ROOT_PARENT_ID = "0";
@Resource
private DictionaryTransService dictionaryTransService;
@Override
public Page<DevDict> page(DevDictPageParam devDictPageParam) {
QueryWrapper<DevDict> queryWrapper = new QueryWrapper<>();
// 查询部分字段
queryWrapper.lambda().select(DevDict::getId, DevDict::getParentId, DevDict::getCategory, DevDict::getDictLabel,
DevDict::getDictValue, DevDict::getSortCode);
if (ObjectUtil.isNotEmpty(devDictPageParam.getParentId())) {
queryWrapper.lambda().eq(DevDict::getParentId, devDictPageParam.getParentId())
.or().eq(DevDict::getId, devDictPageParam.getParentId());
}
if (ObjectUtil.isNotEmpty(devDictPageParam.getCategory())) {
queryWrapper.lambda().eq(DevDict::getCategory, devDictPageParam.getCategory());
}
if (ObjectUtil.isNotEmpty(devDictPageParam.getSearchKey())) {
queryWrapper.lambda().like(DevDict::getDictLabel, devDictPageParam.getSearchKey());
}
if (ObjectUtil.isAllNotEmpty(devDictPageParam.getSortField(), devDictPageParam.getSortOrder())) {
CommonSortOrderEnum.validate(devDictPageParam.getSortOrder());
queryWrapper.orderBy(true, devDictPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
StrUtil.toUnderlineCase(devDictPageParam.getSortField()));
} else {
queryWrapper.lambda().orderByAsc(DevDict::getSortCode);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Override
public List<DevDict> list(DevDictListParam devDictListParam) {
LambdaQueryWrapper<DevDict> lambdaQueryWrapper = new LambdaQueryWrapper<>();
if (ObjectUtil.isNotEmpty(devDictListParam.getParentId())) {
lambdaQueryWrapper.eq(DevDict::getParentId, devDictListParam.getParentId());
}
if (ObjectUtil.isNotEmpty(devDictListParam.getCategory())) {
lambdaQueryWrapper.eq(DevDict::getCategory, devDictListParam.getCategory());
}
return this.list(lambdaQueryWrapper);
}
@Override
public List<Tree<String>> tree(DevDictTreeParam devDictTreeParam) {
LambdaQueryWrapper<DevDict> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.orderByAsc(DevDict::getSortCode);
if (ObjectUtil.isNotEmpty(devDictTreeParam.getCategory())) {
lambdaQueryWrapper.eq(DevDict::getCategory, devDictTreeParam.getCategory());
}
List<DevDict> devDictList = this.list(lambdaQueryWrapper);
List<TreeNode<String>> treeNodeList = devDictList.stream().map(devDict ->
new TreeNode<>(devDict.getId(), devDict.getParentId(),
devDict.getDictLabel(), devDict.getSortCode()).setExtra(JSONUtil.parseObj(devDict)))
.collect(Collectors.toList());
return TreeUtil.build(treeNodeList, "0");
}
@Override
public void add(DevDictAddParam devDictAddParam) {
checkParam(devDictAddParam);
DevDict devDict = BeanUtil.toBean(devDictAddParam, DevDict.class);
this.save(devDict);
refreshTransCache();
}
private void checkParam(DevDictAddParam devDictAddParam) {
DevDictCategoryEnum.validate(devDictAddParam.getCategory());
boolean hasSameDictLabel = this.count(new LambdaQueryWrapper<DevDict>()
.eq(DevDict::getParentId, devDictAddParam.getParentId())
.eq(DevDict::getCategory, devDictAddParam.getCategory())
.eq(DevDict::getDictLabel, devDictAddParam.getDictLabel())) > 0;
if (hasSameDictLabel) {
throw new CommonException("存在重复的字典文字,名称为:{}", devDictAddParam.getDictLabel());
}
boolean hasSameDictValue = this.count(new LambdaQueryWrapper<DevDict>()
.eq(DevDict::getParentId, devDictAddParam.getParentId())
.eq(DevDict::getDictValue, devDictAddParam.getDictValue())) > 0;
if (hasSameDictValue) {
throw new CommonException("存在重复的字典值,名称为:{}", devDictAddParam.getDictValue());
}
}
@Override
public void edit(DevDictEditParam devDictEditParam) {
DevDict devDict = this.queryEntity(devDictEditParam.getId());
checkParam(devDictEditParam);
BeanUtil.copyProperties(devDictEditParam, devDict);
this.updateById(devDict);
refreshTransCache();
}
private void checkParam(DevDictEditParam devDictEditParam) {
DevDictCategoryEnum.validate(devDictEditParam.getCategory());
boolean hasSameDictLabel = this.count(new LambdaQueryWrapper<DevDict>()
.eq(DevDict::getParentId, devDictEditParam.getParentId())
.eq(DevDict::getCategory, devDictEditParam.getCategory())
.eq(DevDict::getDictLabel, devDictEditParam.getDictLabel())
.ne(DevDict::getId, devDictEditParam.getId())) > 0;
if (hasSameDictLabel) {
throw new CommonException("存在重复的字典文字,名称为:{}", devDictEditParam.getDictLabel());
}
boolean hasSameDictValue = this.count(new LambdaQueryWrapper<DevDict>()
.eq(DevDict::getParentId, devDictEditParam.getParentId())
.eq(DevDict::getDictValue, devDictEditParam.getDictValue())
.ne(DevDict::getId, devDictEditParam.getId())) > 0;
if (hasSameDictValue) {
throw new CommonException("存在重复的字典值,名称为:{}", devDictEditParam.getDictValue());
}
}
@Override
public void delete(List<DevDictIdParam> devDictIdParamList) {
List<String> devDictIdList = CollStreamUtil.toList(devDictIdParamList, DevDictIdParam::getId);
if (ObjectUtil.isNotEmpty(devDictIdList)) {
boolean systemDict = this.listByIds(devDictIdList).stream().map(DevDict::getCategory)
.collect(Collectors.toSet()).contains(DevDictCategoryEnum.FRM.getValue());
if (systemDict) {
throw new CommonException("不可删除系统内置字典");
}
// 删除
this.removeByIds(devDictIdList);
}
}
@Override
public DevDict detail(DevDictIdParam devDictIdParam) {
return this.queryEntity(devDictIdParam.getId());
}
@Override
public DevDict queryEntity(String id) {
DevDict devDict = this.getById(id);
if (ObjectUtil.isEmpty(devDict)) {
throw new CommonException("字典不存在id值为{}", id);
}
return devDict;
}
@Override
public void afterPropertiesSet() {
refreshTransCache();
}
private void refreshTransCache() {
// 异步不阻塞主线程,不会 增加启动用时
CompletableFuture.supplyAsync(() -> {
// 使用redis能解决共享问题但是性能没有直接取缓存的好。
dictionaryTransService.makeUseRedis();
List<DevDict> devDictList = super.list(new LambdaQueryWrapper<>());
// 非root级别的字典根据ParentId分组
Map<String,List<DevDict>> devDictGroupByPIDMap = devDictList.stream().filter(dict -> !ROOT_PARENT_ID
.equals(dict.getParentId())).collect(Collectors.groupingBy(DevDict::getParentId));
Map<String,String> parentDictIdValMap = devDictList.stream().filter(dict -> ROOT_PARENT_ID
.equals(dict.getParentId())).collect(Collectors.toMap(DevDict::getId, DevDict::getDictValue));
for (String parentId : parentDictIdValMap.keySet()) {
if(devDictGroupByPIDMap.containsKey(parentId)){
dictionaryTransService.refreshCache(parentDictIdValMap.get(parentId), devDictGroupByPIDMap.get(parentId).stream()
.collect(Collectors.toMap(DevDict::getDictValue, DevDict::getDictLabel)));
}
}
return null;
});
}
}

View File

@@ -0,0 +1,201 @@
package mjkf.xinke.dev.modular.email.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.modular.email.entity.DevEmail;
import mjkf.xinke.dev.modular.email.param.*;
import mjkf.xinke.dev.modular.email.service.DevEmailService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
/**
* 邮件控制器
*
*
* @date 2022/2/23 18:26
**/
@Api(tags = "邮件控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 3)
@RestController
@Validated
public class DevEmailController {
@Resource
private DevEmailService devEmailService;
/**
* 发送邮件——本地TXT
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 1)
@ApiOperation("发送本地文本邮件")
@CommonLog("发送本地文本邮件")
@PostMapping("/dev/email/sendLocalTxt")
public CommonResult<String> sendLocal(@RequestBody @Valid DevEmailSendLocalTxtParam devEmailSendLocalTxtParam) {
devEmailService.sendLocal(devEmailSendLocalTxtParam);
return CommonResult.ok();
}
/**
* 发送邮件——本地HTML
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 2)
@ApiOperation("发送本地HTML邮件")
@CommonLog("发送本地HTML邮件")
@PostMapping("/dev/email/sendLocalHtml")
public CommonResult<String> sendLocal(@RequestBody @Valid DevEmailSendLocalHtmlParam devEmailSendLocalHtmlParam) {
devEmailService.sendLocal(devEmailSendLocalHtmlParam);
return CommonResult.ok();
}
/**
* 发送邮件——阿里云TXT
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 3)
@ApiOperation("发送阿里云文本邮件")
@CommonLog("发送阿里云文本邮件")
@PostMapping("/dev/email/sendAliyunTxt")
public CommonResult<String> sendAliyun(@RequestBody @Valid DevEmailSendAliyunTxtParam devEmailSendAliyunTxtParam) {
devEmailService.sendAliyun(devEmailSendAliyunTxtParam);
return CommonResult.ok();
}
/**
* 发送邮件——阿里云HTML
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 4)
@ApiOperation("发送阿里云HTML邮件")
@CommonLog("发送阿里云HTML邮件")
@PostMapping("/dev/email/sendAliyunHtml")
public CommonResult<String> sendAliyun(@RequestBody @Valid DevEmailSendAliyunHtmlParam devEmailSendAliyunHtmlParam) {
devEmailService.sendAliyun(devEmailSendAliyunHtmlParam);
return CommonResult.ok();
}
/**
* 发送邮件——阿里云TMP
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 5)
@ApiOperation("发送阿里云模板邮件")
@CommonLog("发送阿里云模板邮件")
@PostMapping("/dev/email/sendAliyunTmp")
public CommonResult<String> sendAliyun(@RequestBody @Valid DevEmailSendAliyunTmpParam devEmailSendAliyunTmpParam) {
devEmailService.sendAliyun(devEmailSendAliyunTmpParam);
return CommonResult.ok();
}
/**
* 发送邮件——腾讯云TXT
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 6)
@ApiOperation("发送腾讯云文本邮件")
@CommonLog("发送腾讯云文本邮件")
@PostMapping("/dev/email/sendTencentTxt")
public CommonResult<String> sendTencent(@RequestBody @Valid DevEmailSendTencentTxtParam devEmailSendTencentTxtParam) {
devEmailService.sendTencent(devEmailSendTencentTxtParam);
return CommonResult.ok();
}
/**
* 发送邮件——腾讯云HTML
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 7)
@ApiOperation("发送腾讯云HTML邮件")
@CommonLog("发送腾讯云HTML邮件")
@PostMapping("/dev/email/sentTencentHtml")
public CommonResult<String> sendTencent(@RequestBody @Valid DevEmailSendTencentHtmlParam devEmailSendTencentHtmlParam) {
devEmailService.sendTencent(devEmailSendTencentHtmlParam);
return CommonResult.ok();
}
/**
* 发送邮件——腾讯云TMP
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 8)
@ApiOperation("发送腾讯云模板邮件")
@CommonLog("发送腾讯云模板邮件")
@PostMapping("/dev/email/sentTencentTmp")
public CommonResult<String> sendTencent(@RequestBody @Valid DevEmailSendTencentTmpParam devEmailSendTencentTmpParam) {
devEmailService.sendTencent(devEmailSendTencentTmpParam);
return CommonResult.ok();
}
/**
* 获取邮件分页
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 9)
@ApiOperation("获取邮件分页")
@GetMapping("/dev/email/page")
public CommonResult<Page<DevEmail>> page(DevEmailPageParam devEmailPageParam) {
return CommonResult.data(devEmailService.page(devEmailPageParam));
}
/**
* 删除邮件
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 10)
@ApiOperation("删除邮件")
@CommonLog("删除邮件")
@PostMapping("/dev/email/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevEmailIdParam> devEmailIdParamList) {
devEmailService.delete(devEmailIdParamList);
return CommonResult.ok();
}
/**
* 获取邮件详情
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 11)
@ApiOperation("获取邮件详情")
@GetMapping("/dev/email/detail")
public CommonResult<DevEmail> detail(@Valid DevEmailIdParam devEmailIdParam) {
return CommonResult.data(devEmailService.detail(devEmailIdParam));
}
}

View File

@@ -0,0 +1,72 @@
package mjkf.xinke.dev.modular.email.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
/**
* 邮件实体
*
*
* @date 2022/2/23 18:27
**/
@Getter
@Setter
@TableName("DEV_EMAIL")
public class DevEmail extends CommonEntity {
/** id */
@ApiModelProperty(value = "主键", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 邮件引擎 */
@ApiModelProperty(value = "邮件引擎", position = 3)
private String engine;
/** 发件人邮箱 */
@ApiModelProperty(value = "发件人邮箱", position = 4)
private String sendAccount;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
/** 接收人 */
@ApiModelProperty(value = "接收人", position = 6)
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", position = 7)
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", position = 8)
private String content;
/** 标签名 */
@ApiModelProperty(value = "标签名", position = 9)
private String tagName;
/** 模板名 */
@ApiModelProperty(value = "模板名", position = 10)
private String templateName;
/** 发送参数 */
@ApiModelProperty(value = "发送参数", position = 11)
private String templateParam;
/** 回执信息 */
@ApiModelProperty(value = "回执信息", position = 12)
private String receiptInfo;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 13)
private String extJson;
}

View File

@@ -0,0 +1,29 @@
package mjkf.xinke.dev.modular.email.enums;
import lombok.Getter;
/**
* 邮件发送引擎类型枚举
*
*
* @date 2022/6/16 16:14
**/
@Getter
public enum DevEmailEngineTypeEnum {
/** 本地 */
LOCAL("LOCAL"),
/** 阿里云 */
ALIYUN("ALIYUN"),
/** 腾讯云 */
TENCENT("TENCENT");
private final String value;
DevEmailEngineTypeEnum(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,29 @@
package mjkf.xinke.dev.modular.email.enums;
import lombok.Getter;
/**
* 邮件类型枚举
*
*
* @date 2022/6/16 16:14
**/
@Getter
public enum DevEmailTypeEnum {
/** 纯文本 */
TEXT("TEXT"),
/** 富文本 */
HTML("HTML"),
/** 模板 */
TEMPLATE("TEMPLATE");
private final String value;
DevEmailTypeEnum(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,14 @@
package mjkf.xinke.dev.modular.email.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.email.entity.DevEmail;
/**
* 邮件Mapper接口
*
*
* @date 2022/2/23 18:40
**/
public interface DevEmailMapper extends BaseMapper<DevEmail> {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.email.mapper.DevEmailMapper">
</mapper>

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.email.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 邮件Id参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,41 @@
package mjkf.xinke.dev.modular.email.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 邮件查询参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 邮件引擎 */
@ApiModelProperty(value = "邮件引擎")
private String engine;
/** 邮件主题关键词 */
@ApiModelProperty(value = "邮件主题关键词")
private String searchKey;
}

View File

@@ -0,0 +1,43 @@
package mjkf.xinke.dev.modular.email.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 邮件发送——阿里云HTML参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendAliyunHtmlParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "发件人邮箱,即管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 3)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 4)
@NotBlank(message = "content不能为空")
private String content;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
}

View File

@@ -0,0 +1,38 @@
package mjkf.xinke.dev.modular.email.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 邮件发送——阿里云TMP参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendAliyunTmpParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "预先创建且上传了收件人的收件人列表名称", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 模板名 */
@ApiModelProperty(value = "预先创建且通过审核的模板名称", required = true, position = 4)
@NotBlank(message = "templateName不能为空")
private String templateName;
/** 标签名 */
@ApiModelProperty(value = "标签名", position = 5)
private String tagName;
}

View File

@@ -0,0 +1,43 @@
package mjkf.xinke.dev.modular.email.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 邮件发送——阿里云TXT参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendAliyunTxtParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "发件人邮箱,即管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 3)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 4)
@NotBlank(message = "content不能为空")
private String content;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
}

View File

@@ -0,0 +1,48 @@
package mjkf.xinke.dev.modular.email.param;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* 邮件发送——本地HTML参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendLocalHtmlParam {
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 1)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 2)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 3)
@NotBlank(message = "content不能为空")
private String content;
/** 图片展位符 */
@ApiModelProperty(value = "图片展位符", position = 4, hidden = true)
private Map<String, InputStream> imageMap = MapUtil.newHashMap();
/** 附件列表 */
@ApiModelProperty(value = "附件列表", position = 5, hidden = true)
private List<File> files = CollectionUtil.newArrayList();
}

View File

@@ -0,0 +1,41 @@
package mjkf.xinke.dev.modular.email.param;
import cn.hutool.core.collection.CollectionUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.util.List;
/**
* 邮件发送——本地TXT参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendLocalTxtParam {
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 1)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 2)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 3)
@NotBlank(message = "content不能为空")
private String content;
/** 附件列表 */
@ApiModelProperty(value = "附件列表", position = 4, hidden = true)
private List<File> files = CollectionUtil.newArrayList();
}

View File

@@ -0,0 +1,50 @@
package mjkf.xinke.dev.modular.email.param;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* 邮件发送——腾讯云HTML参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendTencentHtmlParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 3)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 4)
@NotBlank(message = "content不能为空")
private String content;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
/** 附件列表 */
@ApiModelProperty(value = "附件列表", position = 6, hidden = true)
List<JSONObject> attachmentList = CollectionUtil.newArrayList();
}

View File

@@ -0,0 +1,54 @@
package mjkf.xinke.dev.modular.email.param;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* 邮件发送——腾讯云TMP参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendTencentTmpParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "预先创建且上传了收件人的收件人列表id", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 模板名 */
@ApiModelProperty(value = "预先创建且通过审核的模板Id", required = true, position = 3)
@NotBlank(message = "templateName不能为空")
private String templateName;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 4)
@NotBlank(message = "subject不能为空")
private String subject;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
/** 发送参数 */
@ApiModelProperty(value = "预先创建且通过审核的模板的参数json", position = 6)
private String templateParam;
/** 附件列表 */
@ApiModelProperty(value = "附件列表", position = 7, hidden = true)
List<JSONObject> attachmentList = CollectionUtil.newArrayList();
}

View File

@@ -0,0 +1,50 @@
package mjkf.xinke.dev.modular.email.param;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* 邮件发送——腾讯云TXT参数
*
*
* @date 2022/6/21 15:38
**/
@Getter
@Setter
public class DevEmailSendTencentTxtParam {
/** 发件人邮箱 */
@ApiModelProperty(value = "管理控制台中配置的发信地址", required = true, position = 1)
@NotBlank(message = "sendAccount不能为空")
private String sendAccount;
/** 接收人 */
@ApiModelProperty(value = "接收人邮箱地址,多个逗号拼接", required = true, position = 2)
@NotBlank(message = "receiveAccounts不能为空")
private String receiveAccounts;
/** 邮件主题 */
@ApiModelProperty(value = "邮件主题", required = true, position = 3)
@NotBlank(message = "subject不能为空")
private String subject;
/** 邮件正文 */
@ApiModelProperty(value = "邮件正文", required = true, position = 4)
@NotBlank(message = "content不能为空")
private String content;
/** 发件人昵称 */
@ApiModelProperty(value = "发件人昵称", position = 5)
private String sendUser;
/** 附件列表 */
@ApiModelProperty(value = "附件列表", position = 6, hidden = true)
List<JSONObject> attachmentList = CollectionUtil.newArrayList();
}

View File

@@ -0,0 +1,114 @@
package mjkf.xinke.dev.modular.email.provider;
import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Service;
import mjkf.xinke.dev.api.DevEmailApi;
import mjkf.xinke.dev.modular.email.param.*;
import mjkf.xinke.dev.modular.email.service.DevEmailService;
import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* 邮件API接口提供者
*
*
* @date 2022/6/22 15:32
**/
@Service
public class DevEmailApiProvider implements DevEmailApi {
@Resource
private DevEmailService devEmailService;
@Override
public void sendTextEmailLocal(String tos, String subject, String content, List<File> files) {
DevEmailSendLocalTxtParam devEmailSendLocalTxtParam = new DevEmailSendLocalTxtParam();
devEmailSendLocalTxtParam.setReceiveAccounts(tos);
devEmailSendLocalTxtParam.setSubject(subject);
devEmailSendLocalTxtParam.setContent(content);
devEmailSendLocalTxtParam.setFiles(files);
devEmailService.sendLocal(devEmailSendLocalTxtParam);
}
@Override
public void sendHtmlEmailLocal(String tos, String subject, String content, Map<String, InputStream> imageMap, List<File> files) {
DevEmailSendLocalHtmlParam devEmailSendLocalHtmlParam = new DevEmailSendLocalHtmlParam();
devEmailSendLocalHtmlParam.setReceiveAccounts(tos);
devEmailSendLocalHtmlParam.setSubject(subject);
devEmailSendLocalHtmlParam.setContent(content);
devEmailSendLocalHtmlParam.setImageMap(imageMap);
devEmailSendLocalHtmlParam.setFiles(files);
devEmailService.sendLocal(devEmailSendLocalHtmlParam);
}
@Override
public void sendTextEmailAliyun(String from, String user, String tos, String subject, String content) {
DevEmailSendAliyunTxtParam devEmailSendAliyunTxtParam = new DevEmailSendAliyunTxtParam();
devEmailSendAliyunTxtParam.setSendAccount(from);
devEmailSendAliyunTxtParam.setSendUser(user);
devEmailSendAliyunTxtParam.setReceiveAccounts(tos);
devEmailSendAliyunTxtParam.setSubject(subject);
devEmailSendAliyunTxtParam.setContent(content);
devEmailService.sendAliyun(devEmailSendAliyunTxtParam);
}
@Override
public void sendHtmlEmailAliyun(String from, String user, String tos, String subject, String content) {
DevEmailSendAliyunHtmlParam devEmailSendAliyunHtmlParam = new DevEmailSendAliyunHtmlParam();
devEmailSendAliyunHtmlParam.setSendAccount(from);
devEmailSendAliyunHtmlParam.setSendUser(user);
devEmailSendAliyunHtmlParam.setReceiveAccounts(tos);
devEmailSendAliyunHtmlParam.setSubject(subject);
devEmailSendAliyunHtmlParam.setContent(content);
devEmailService.sendAliyun(devEmailSendAliyunHtmlParam);
}
@Override
public void sendEmailWithTemplateAliyun(String from, String tagName, String toName, String templateName) {
DevEmailSendAliyunTmpParam devEmailSendAliyunTmpParam = new DevEmailSendAliyunTmpParam();
devEmailSendAliyunTmpParam.setReceiveAccounts(toName);
devEmailSendAliyunTmpParam.setTagName(tagName);
devEmailSendAliyunTmpParam.setTemplateName(templateName);
devEmailService.sendAliyun(devEmailSendAliyunTmpParam);
}
@Override
public void sendTextEmailTencent(String from, String user, String tos, String subject, String content, List<JSONObject> attachmentList) {
DevEmailSendTencentTxtParam devEmailSendTencentTxtParam = new DevEmailSendTencentTxtParam();
devEmailSendTencentTxtParam.setSendAccount(from);
devEmailSendTencentTxtParam.setSendUser(user);
devEmailSendTencentTxtParam.setReceiveAccounts(tos);
devEmailSendTencentTxtParam.setSubject(subject);
devEmailSendTencentTxtParam.setContent(content);
devEmailSendTencentTxtParam.setAttachmentList(attachmentList);
devEmailService.sendTencent(devEmailSendTencentTxtParam);
}
@Override
public void sendHtmlEmailTencent(String from, String user, String tos, String subject, String content, List<JSONObject> attachmentList) {
DevEmailSendTencentHtmlParam devEmailSendTencentHtmlParam = new DevEmailSendTencentHtmlParam();
devEmailSendTencentHtmlParam.setSendAccount(from);
devEmailSendTencentHtmlParam.setSendUser(user);
devEmailSendTencentHtmlParam.setReceiveAccounts(tos);
devEmailSendTencentHtmlParam.setSubject(subject);
devEmailSendTencentHtmlParam.setContent(content);
devEmailSendTencentHtmlParam.setAttachmentList(attachmentList);
devEmailService.sendTencent(devEmailSendTencentHtmlParam);
}
@Override
public void sendEmailWithTemplateTencent(String from, String user, String toId, String templateId, String templateParam, String subject, List<JSONObject> attachmentList) {
DevEmailSendTencentTmpParam devEmailSendTencentTmpParam = new DevEmailSendTencentTmpParam();
devEmailSendTencentTmpParam.setSendAccount(from);
devEmailSendTencentTmpParam.setSendUser(user);
devEmailSendTencentTmpParam.setReceiveAccounts(toId);
devEmailSendTencentTmpParam.setTemplateName(templateId);
devEmailSendTencentTmpParam.setAttachmentList(attachmentList);
devEmailService.sendTencent(devEmailSendTencentTmpParam);
}
}

View File

@@ -0,0 +1,114 @@
package mjkf.xinke.dev.modular.email.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import mjkf.xinke.dev.modular.email.entity.DevEmail;
import mjkf.xinke.dev.modular.email.param.*;
import java.util.List;
/**
* 邮件Service接口
*
*
* @date 2022/2/23 18:27
**/
public interface DevEmailService extends IService<DevEmail> {
/**
* 发送邮件——本地TXT
*
*
* @date 2022/6/21 18:37
**/
void sendLocal(DevEmailSendLocalTxtParam devEmailSendLocalTxtParam);
/**
* 发送邮件——本地HTML
*
*
* @date 2022/6/21 18:37
**/
void sendLocal(DevEmailSendLocalHtmlParam devEmailSendLocalHtmlParam);
/**
* 发送邮件——阿里云TXT
*
*
* @date 2022/6/21 18:37
**/
void sendAliyun(DevEmailSendAliyunTxtParam devEmailSendAliyunTxtParam);
/**
* 发送邮件——阿里云HTML
*
*
* @date 2022/6/21 18:37
**/
void sendAliyun(DevEmailSendAliyunHtmlParam devEmailSendAliyunHtmlParam);
/**
* 发送邮件——阿里云TMP
*
*
* @date 2022/6/21 18:37
**/
void sendAliyun(DevEmailSendAliyunTmpParam devEmailSendAliyunTmpParam);
/**
* 发送邮件——腾讯云TXT
*
*
* @date 2022/6/21 18:37
**/
void sendTencent(DevEmailSendTencentTxtParam devEmailSendTencentTxtParam);
/**
* 发送邮件——腾讯云HTML
*
*
* @date 2022/6/21 18:37
**/
void sendTencent(DevEmailSendTencentHtmlParam devEmailSendTencentHtmlParam);
/**
* 发送邮件——腾讯云TMP
*
*
* @date 2022/6/21 18:37
**/
void sendTencent(DevEmailSendTencentTmpParam devEmailSendTencentTmpParam);
/**
* 获取邮件分页
*
*
* @date 2022/4/24 20:08
*/
Page<DevEmail> page(DevEmailPageParam devEmailPageParam);
/**
* 删除邮件
*
*
* @date 2022/8/4 10:36
**/
void delete(List<DevEmailIdParam> devEmailIdParamList);
/**
* 获取邮件详情
*
*
* @date 2022/4/24 20:08
*/
DevEmail detail(DevEmailIdParam devEmailIdParam);
/**
* 获取邮件详情
*
*
* @date 2022/4/24 20:08
*/
DevEmail queryEntity(String id);
}

View File

@@ -0,0 +1,190 @@
package mjkf.xinke.dev.modular.email.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import mjkf.xinke.common.enums.CommonSortOrderEnum;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.page.CommonPageRequest;
import mjkf.xinke.common.util.CommonEmailUtil;
import mjkf.xinke.dev.modular.email.entity.DevEmail;
import mjkf.xinke.dev.modular.email.enums.DevEmailEngineTypeEnum;
import mjkf.xinke.dev.modular.email.mapper.DevEmailMapper;
import mjkf.xinke.dev.modular.email.param.*;
import mjkf.xinke.dev.modular.email.service.DevEmailService;
import mjkf.xinke.dev.modular.email.util.DevEmailAliyunUtil;
import mjkf.xinke.dev.modular.email.util.DevEmailLocalUtil;
import mjkf.xinke.dev.modular.email.util.DevEmailTencentUtil;
import java.util.List;
/**
* 邮件Service接口实现类
*
*
* @date 2022/2/23 18:43
**/
@Service
public class DevEmailServiceImpl extends ServiceImpl<DevEmailMapper, DevEmail> implements DevEmailService {
@Transactional(rollbackFor = Exception.class)
@Override
public void sendLocal(DevEmailSendLocalTxtParam devEmailSendLocalTxtParam) {
CommonEmailUtil.validEmail(devEmailSendLocalTxtParam.getReceiveAccounts());
String receiptInfo = DevEmailLocalUtil.sendTextEmail(devEmailSendLocalTxtParam.getReceiveAccounts(),
devEmailSendLocalTxtParam.getSubject(), devEmailSendLocalTxtParam.getContent(), devEmailSendLocalTxtParam.getFiles());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendLocalTxtParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.LOCAL.getValue());
devEmail.setReceiptInfo(receiptInfo);
MailAccount client = DevEmailLocalUtil.getClient();
devEmail.setSendAccount(client.getFrom());
devEmail.setSendUser(client.getUser());
this.save(devEmail);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void sendLocal(DevEmailSendLocalHtmlParam devEmailSendLocalHtmlParam) {
CommonEmailUtil.validEmail(devEmailSendLocalHtmlParam.getReceiveAccounts());
String receiptInfo = DevEmailLocalUtil.sendHtmlEmail(devEmailSendLocalHtmlParam.getReceiveAccounts(),
devEmailSendLocalHtmlParam.getSubject(), devEmailSendLocalHtmlParam.getContent(), devEmailSendLocalHtmlParam.getImageMap(),
devEmailSendLocalHtmlParam.getFiles());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendLocalHtmlParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.LOCAL.getValue());
devEmail.setReceiptInfo(receiptInfo);
MailAccount client = DevEmailLocalUtil.getClient();
devEmail.setSendAccount(client.getFrom());
devEmail.setSendUser(client.getUser());
this.save(devEmail);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void sendAliyun(DevEmailSendAliyunTxtParam devEmailSendAliyunTxtParam) {
CommonEmailUtil.validEmail(devEmailSendAliyunTxtParam.getReceiveAccounts());
String receiptInfo = DevEmailAliyunUtil.sendTextEmail(devEmailSendAliyunTxtParam.getSendAccount(),
devEmailSendAliyunTxtParam.getSendUser(), devEmailSendAliyunTxtParam.getReceiveAccounts(),
devEmailSendAliyunTxtParam.getSubject(), devEmailSendAliyunTxtParam.getContent());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendAliyunTxtParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.ALIYUN.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void sendAliyun(DevEmailSendAliyunHtmlParam devEmailSendAliyunHtmlParam) {
CommonEmailUtil.validEmail(devEmailSendAliyunHtmlParam.getReceiveAccounts());
String receiptInfo = DevEmailAliyunUtil.sendHtmlEmail(devEmailSendAliyunHtmlParam.getSendAccount(),
devEmailSendAliyunHtmlParam.getSendUser(), devEmailSendAliyunHtmlParam.getReceiveAccounts(),
devEmailSendAliyunHtmlParam.getSubject(), devEmailSendAliyunHtmlParam.getContent());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendAliyunHtmlParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.ALIYUN.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Override
public void sendAliyun(DevEmailSendAliyunTmpParam devEmailSendAliyunTmpParam) {
CommonEmailUtil.validEmail(devEmailSendAliyunTmpParam.getReceiveAccounts());
String receiptInfo = DevEmailAliyunUtil.sendEmailWithTemplate(devEmailSendAliyunTmpParam.getSendAccount(),
devEmailSendAliyunTmpParam.getTagName(), devEmailSendAliyunTmpParam.getReceiveAccounts(),
devEmailSendAliyunTmpParam.getTemplateName());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendAliyunTmpParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.ALIYUN.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Override
public void sendTencent(DevEmailSendTencentTxtParam devEmailSendTencentTxtParam) {
CommonEmailUtil.validEmail(devEmailSendTencentTxtParam.getReceiveAccounts());
String receiptInfo = DevEmailTencentUtil.sendTextEmail(devEmailSendTencentTxtParam.getSendAccount(),
devEmailSendTencentTxtParam.getSendUser(), devEmailSendTencentTxtParam.getReceiveAccounts(),
devEmailSendTencentTxtParam.getSubject(), devEmailSendTencentTxtParam.getContent(),
devEmailSendTencentTxtParam.getAttachmentList());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendTencentTxtParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.TENCENT.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Override
public void sendTencent(DevEmailSendTencentHtmlParam devEmailSendTencentHtmlParam) {
CommonEmailUtil.validEmail(devEmailSendTencentHtmlParam.getReceiveAccounts());
String receiptInfo = DevEmailTencentUtil.sendHtmlEmail(devEmailSendTencentHtmlParam.getSendAccount(),
devEmailSendTencentHtmlParam.getSendUser(), devEmailSendTencentHtmlParam.getReceiveAccounts(),
devEmailSendTencentHtmlParam.getSubject(), devEmailSendTencentHtmlParam.getContent(),
devEmailSendTencentHtmlParam.getAttachmentList());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendTencentHtmlParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.TENCENT.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Override
public void sendTencent(DevEmailSendTencentTmpParam devEmailSendTencentTmpParam) {
CommonEmailUtil.validEmail(devEmailSendTencentTmpParam.getReceiveAccounts());
String receiptInfo = DevEmailTencentUtil.sendEmailWithTemplate(devEmailSendTencentTmpParam.getSendAccount(),
devEmailSendTencentTmpParam.getSendUser(), devEmailSendTencentTmpParam.getReceiveAccounts(),
devEmailSendTencentTmpParam.getTemplateName(), devEmailSendTencentTmpParam.getTemplateParam(),
devEmailSendTencentTmpParam.getSubject(), CollectionUtil.newArrayList());
DevEmail devEmail = new DevEmail();
BeanUtil.copyProperties(devEmailSendTencentTmpParam, devEmail);
devEmail.setEngine(DevEmailEngineTypeEnum.TENCENT.getValue());
devEmail.setReceiptInfo(receiptInfo);
this.save(devEmail);
}
@Override
public Page<DevEmail> page(DevEmailPageParam devEmailPageParam) {
QueryWrapper<DevEmail> queryWrapper = new QueryWrapper<>();
if(ObjectUtil.isNotEmpty(devEmailPageParam.getEngine())) {
queryWrapper.lambda().eq(DevEmail::getEngine, devEmailPageParam.getEngine());
}
if(ObjectUtil.isNotEmpty(devEmailPageParam.getSearchKey())) {
queryWrapper.lambda().like(DevEmail::getSubject, devEmailPageParam.getSearchKey());
}
if(ObjectUtil.isAllNotEmpty(devEmailPageParam.getSortField(), devEmailPageParam.getSortOrder())) {
CommonSortOrderEnum.validate(devEmailPageParam.getSortOrder());
queryWrapper.orderBy(true, devEmailPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
StrUtil.toUnderlineCase(devEmailPageParam.getSortField()));
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Override
public void delete(List<DevEmailIdParam> devEmailIdParamList) {
this.removeByIds(CollStreamUtil.toList(devEmailIdParamList, DevEmailIdParam::getId));
}
@Override
public DevEmail detail(DevEmailIdParam devEmailIdParam) {
return this.queryEntity(devEmailIdParam.getId());
}
@Override
public DevEmail queryEntity(String id) {
DevEmail devEmail = this.getById(id);
if(ObjectUtil.isEmpty(devEmail)) {
throw new CommonException("邮件发送记录不存在id值为{}", id);
}
return devEmail;
}
}

View File

@@ -0,0 +1,210 @@
package mjkf.xinke.dev.modular.email.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dm.model.v20151123.BatchSendMailRequest;
import com.aliyuncs.dm.model.v20151123.SingleSendMailRequest;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.extern.slf4j.Slf4j;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
/**
* 阿里云邮件工具类
* 参考文档https://help.aliyun.com/document_detail/29459.html
*
*
* @date 2022/6/17 10:17
**/
@Slf4j
public class DevEmailAliyunUtil {
private static IAcsClient client;
private static final String SNOWY_EMAIL_ALIYUN_ACCESS_KEY_ID_KEY = "SNOWY_EMAIL_ALIYUN_ACCESS_KEY_ID";
private static final String SNOWY_EMAIL_ALIYUN_ACCESS_KEY_SECRET_KEY = "SNOWY_EMAIL_ALIYUN_ACCESS_KEY_SECRET";
private static final String SNOWY_EMAIL_ALIYUN_REGION_ID_KEY = "SNOWY_EMAIL_ALIYUN_REGION_ID";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* accessKeyId */
String accessKeyId = devConfigApi.getValueByKey(SNOWY_EMAIL_ALIYUN_ACCESS_KEY_ID_KEY);
if(ObjectUtil.isEmpty(accessKeyId)) {
throw new CommonException("阿里云邮件操作客户端未正确配置accessKeyId为空");
}
/* accessKeySecret */
String accessKeySecret = devConfigApi.getValueByKey(SNOWY_EMAIL_ALIYUN_ACCESS_KEY_SECRET_KEY);
if(ObjectUtil.isEmpty(accessKeySecret)) {
throw new CommonException("阿里云邮件操作客户端未正确配置accessKeySecret为空");
}
/* regionId */
String regionId = devConfigApi.getValueByKey(SNOWY_EMAIL_ALIYUN_REGION_ID_KEY);
if(ObjectUtil.isEmpty(regionId)) {
throw new CommonException("阿里云邮件操作客户端未正确配置regionId为空");
}
client = new DefaultAcsClient(DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret));
}
/**
* 发送纯文本邮件不使用模板频率限制100 QPS
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param user 发信人昵称长度小于15个字符可不传
* @param tos 目标地址,多个 email 地址可以用逗号分隔最多100个地址必传且必须正确
* @param subject 邮件主题,必传
* @param content 邮件 txt 正文限制28K必传
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendTextEmail(String from, String user, String tos, String subject, String content) {
try {
initClient();
SingleSendMailRequest singleSendMailRequest = createSingleSendRequest(from, user, tos, subject, content, false);
return client.getAcsResponse(singleSendMailRequest).getEnvId();
} catch (ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 发送HTML邮件不使用模板频率限制100 QPS
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param user 发信人昵称长度小于15个字符可不传
* @param tos 目标地址,多个 email 地址可以用逗号分隔最多100个地址必传且必须正确
* @param subject 邮件主题,必传
* @param content 邮件 html 正文限制28K必传
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendHtmlEmail(String from, String user, String tos, String subject, String content) {
try {
initClient();
SingleSendMailRequest singleSendMailRequest = createSingleSendRequest(from, user, tos, subject, content, true);
return client.getAcsResponse(singleSendMailRequest).getEnvId();
} catch (ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 使用模板发送邮件国内频率限制是20/min海外频率限制是10/min。
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param tagName 控制台创建的邮件标签,可不传
* @param toName 预先创建且上传了收件人的收件人列表名称,必传且必须正确
* @param templateName 预先创建且通过审核的模板名称,必传且必须正确
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendEmailWithTemplate(String from, String tagName, String toName, String templateName) {
try {
initClient();
BatchSendMailRequest batchSendMailRequest = createBatchSendRequest(from, tagName, toName, templateName);
return client.getAcsResponse(batchSendMailRequest).getEnvId();
} catch (ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 创建发送一个邮件的请求
*
*
* @date 2022/2/23 13:33
**/
private static SingleSendMailRequest createSingleSendRequest(String from, String user, String tos, String subject, String content, boolean isHtml) {
SingleSendMailRequest request = new SingleSendMailRequest();
// 控制台创建的发信地址
request.setAccountName(from);
// 发信人昵称
request.setFromAlias(user);
// 地址类型0-为随机账号1-为发信地址
request.setAddressType(1);
// 使用管理台配置的回信地址
request.setReplyToAddress(true);
// 目标地址
request.setToAddress(tos);
// 邮件主题
request.setSubject(subject);
//如果采用byte[].toString的方式的话请确保最终转换成utf-8的格式再放入htmlbody和textbody若编码不一致则会被当成垃圾邮件。
if(isHtml) {
request.setHtmlBody(content);
} else {
request.setTextBody(content);
}
//SDK 采用的是http协议的发信方式, 默认是GET方法有一定的长度限制。
//若textBody、htmlBody或content的大小不确定建议采用POST方式提交避免出现uri is not valid异常
request.setSysMethod(MethodType.POST);
//是否开启追踪功能开启需要备案0关闭1开启
request.setClickTrace("0");
return request;
}
/**
* 创建发送批量邮件的请求
*
* @author fengshuonan
* @date 2020/10/30 22:39
*/
private static BatchSendMailRequest createBatchSendRequest(String from, String tagName, String toName, String templateName) {
BatchSendMailRequest request = new BatchSendMailRequest();
// 控制台创建的发信地址
request.setAccountName(from);
// 预先创建且上传了收件人的收件人列表名称
request.setReceiversName(toName);
// 邮件模板,在控制台创建,相当于邮件的内容
request.setTemplateName(templateName);
// 地址类型0-为随机账号1-为发信地址
request.setAddressType(1);
// 控制台创建的标签
request.setTagName(tagName);
//SDK 采用的是http协议的发信方式, 默认是GET方法有一定的长度限制。
//若textBody、htmlBody或content的大小不确定建议采用POST方式提交避免出现uri is not valid异常
request.setSysMethod(MethodType.POST);
//是否开启追踪功能开启需要备案0关闭1开启
request.setClickTrace("0");
return request;
}
}

View File

@@ -0,0 +1,106 @@
package mjkf.xinke.dev.modular.email.util;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* 本地邮件工具类
*
*
* @date 2022/6/17 11:15
**/
@Slf4j
public class DevEmailLocalUtil {
private static MailAccount mailAccount;
private static final String SNOWY_EMAIL_LOCAL_FROM_KEY = "SNOWY_EMAIL_LOCAL_FROM";
private static final String SNOWY_EMAIL_LOCAL_PASSWORD_KEY = "SNOWY_EMAIL_LOCAL_PASSWORD";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* 发件人(必须正确,否则发送失败) */
String from = devConfigApi.getValueByKey(SNOWY_EMAIL_LOCAL_FROM_KEY);
if(ObjectUtil.isEmpty(from)) {
throw new CommonException("本地邮件操作客户端未正确配置from为空");
}
/* 密码注意某些邮箱需要为SMTP服务单独设置授权码详情查看相关帮助 */
String pass = devConfigApi.getValueByKey(SNOWY_EMAIL_LOCAL_PASSWORD_KEY);
if(ObjectUtil.isEmpty(pass)) {
throw new CommonException("本地邮件操作客户端未正确配置pass为空");
}
mailAccount = new MailAccount();
mailAccount.setFrom(from);
mailAccount.setPass(pass);
}
public static MailAccount getClient() {
initClient();
return mailAccount;
}
/**
* 发送纯文本邮件
*
* @param tos 收件人邮箱,逗号拼接
* @param subject 邮件主题
* @param content 邮件内容
* @param files 附件列表
* @return 发送成功的回执id
*
* @date 2022/2/7 22:29
*/
public static String sendTextEmail(String tos, String subject, String content, List<File> files) {
try {
initClient();
return MailUtil.send(mailAccount, tos, subject, content, false, ArrayUtil.toArray(files, File.class));
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 发送HTML邮件
*
* @param tos 收件人邮箱列表,逗号拼接
* @param subject 邮件主题
* @param content 邮件内容
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return 发送成功的回执id
*
* @date 2022/2/7 22:29
*/
public static String sendHtmlEmail(String tos, String subject, String content, Map<String, InputStream> imageMap, List<File> files) {
try {
initClient();
return MailUtil.send(mailAccount, tos, subject, content, imageMap, true, ArrayUtil.toArray(files, File.class));
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,202 @@
package mjkf.xinke.dev.modular.email.util;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.ses.v20201002.SesClient;
import com.tencentcloudapi.ses.v20201002.models.*;
import lombok.extern.slf4j.Slf4j;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import java.util.List;
/**
* 腾讯云邮件工具类
* 参考文档https://cloud.tencent.com/document/api/1288/51034
*
*
* @date 2022/6/17 11:26
**/
@Slf4j
public class DevEmailTencentUtil {
private static SesClient client;
private static final String SNOWY_EMAIL_TENCENT_SECRET_ID_KEY = "SNOWY_EMAIL_TENCENT_SECRET_ID";
private static final String SNOWY_EMAIL_TENCENT_SECRET_KEY_KEY = "SNOWY_EMAIL_TENCENT_SECRET_KEY";
private static final String SNOWY_EMAIL_TENCENT_REGION_ID_KEY = "SNOWY_EMAIL_TENCENT_REGION_ID";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* secretId */
String secretId = devConfigApi.getValueByKey(SNOWY_EMAIL_TENCENT_SECRET_ID_KEY);
if(ObjectUtil.isEmpty(secretId)) {
throw new CommonException("腾讯云邮件操作客户端未正确配置secretId为空");
}
/* secretKey */
String secretKey = devConfigApi.getValueByKey(SNOWY_EMAIL_TENCENT_SECRET_KEY_KEY);
if(ObjectUtil.isEmpty(secretKey)) {
throw new CommonException("腾讯云邮件操作客户端未正确配置secretKey为空");
}
/* regionId */
String regionId = devConfigApi.getValueByKey(SNOWY_EMAIL_TENCENT_REGION_ID_KEY);
if(ObjectUtil.isEmpty(regionId)) {
throw new CommonException("腾讯云邮件操作客户端未正确配置regionId为空");
}
client = new SesClient(new Credential(secretId, secretKey), regionId);
}
/**
* 发送纯文本邮件不使用模板默认接口请求频率限制20次/秒。)
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param user 发信人昵称,可不传
* @param tos 目标地址,多个 email 地址可以用逗号分隔最多50个地址必传且必须正确非群发邮件请多次调用API发送
* @param subject 邮件主题,必传
* @param content 邮件 txt 正文必传注意腾讯云api目前要求请求包大小不得超过8 MB。
* @param attachmentList 需要发送附件时,填写附件相关参数,格式:[{"FileName": "xxxx", "Content": "xxx"}]
* 支持的格式与说明见https://cloud.tencent.com/document/api/1288/51053#Attachment
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendTextEmail(String from, String user, String tos, String subject, String content, List<JSONObject> attachmentList) {
try {
initClient();
SendEmailRequest singleSendMailRequest = createSingleSendRequest(from, user, tos, subject, content, false, attachmentList);
return client.SendEmail(singleSendMailRequest).getMessageId();
} catch (TencentCloudSDKException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 发送HTML邮件不使用模板默认接口请求频率限制20次/秒。)
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param user 发信人昵称,可不传
* @param tos 目标地址,多个 email 地址可以用逗号分隔最多50个地址必传且必须正确非群发邮件请多次调用API发送
* @param subject 邮件主题,必传
* @param content 邮件 txt 正文必传注意腾讯云api目前要求请求包大小不得超过8 MB。
* @param attachmentList 需要发送附件时,填写附件相关参数,格式:[{"FileName": "xxxx", "Content": "xxx"}]
* 支持的格式与说明见https://cloud.tencent.com/document/api/1288/51053#Attachment
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendHtmlEmail(String from, String user, String tos, String subject, String content, List<JSONObject> attachmentList) {
try {
initClient();
SendEmailRequest singleSendMailRequest = createSingleSendRequest(from, user, tos, subject, content, true, attachmentList);
return client.SendEmail(singleSendMailRequest).getMessageId();
} catch (TencentCloudSDKException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 使用模板发送邮件默认接口请求频率限制20次/秒。
*
* @param from 管理控制台中配置的发信地址,必传且必须正确
* @param user 发信人昵称,可不传
* @param toId 预先创建且上传了收件人的收件人列表id必传且必须正确
* @param templateId 预先创建且通过审核的模板Id必传且必须正确
* @param templateParam 预先创建且通过审核的模板的参数json格式{"name":"张三"},可不传
* @param subject 邮件主题,必传
* @param attachmentList 需要发送附件时,填写附件相关参数,格式:[{"FileName": "xxxx", "Content": "xxx"}]
* 支持的格式与说明见https://cloud.tencent.com/document/api/1288/51053#Attachment
* @return 发送成功的回执id
*
* @date 2022/2/23 14:24
**/
public static String sendEmailWithTemplate(String from, String user, String toId, String templateId,
String templateParam, String subject, List<JSONObject> attachmentList) {
try {
initClient();
BatchSendEmailRequest batchSendEmailRequest = createBatchSendRequest(from, user, toId, templateId, templateParam, subject, attachmentList);
return client.BatchSendEmail(batchSendEmailRequest).getTaskId().toString();
} catch (TencentCloudSDKException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 创建发送一个邮件的请求
*
*
* @date 2022/2/23 13:33
**/
private static SendEmailRequest createSingleSendRequest(String from, String user, String tos, String subject,
String content, boolean isHtml, List<JSONObject> attachmentList) {
SendEmailRequest sendEmailRequest = new SendEmailRequest();
sendEmailRequest.setFromEmailAddress(ObjectUtil.isNotEmpty(user)?user + " <" + from + ">" :from);
sendEmailRequest.setDestination(StrUtil.splitToArray(tos, StrUtil.COMMA));
sendEmailRequest.setSubject(subject);
Simple simple = new Simple();
if(isHtml) {
simple.setHtml(Base64.encode(content));
} else {
simple.setText(Base64.encode(content));
}
sendEmailRequest.setSimple(simple);
if(ObjectUtil.isNotEmpty(attachmentList)) {
Attachment[] attachments = (Attachment[]) attachmentList.stream().map(jsonObject -> {
Attachment attachment = new Attachment();
BeanUtil.copyProperties(jsonObject, attachment);
return attachment;
}).toArray();
sendEmailRequest.setAttachments(attachments);
}
return sendEmailRequest;
}
/**
* 创建发送批量邮件的请求
*
*
* @date 2022/2/23 13:33
**/
private static BatchSendEmailRequest createBatchSendRequest(String from, String user, String toId, String templateId,
String templateParam, String subject, List<JSONObject> attachmentList) {
BatchSendEmailRequest batchSendEmailRequest = new BatchSendEmailRequest();
batchSendEmailRequest.setFromEmailAddress(ObjectUtil.isNotEmpty(user)?user + " <" + from + ">" :from);
batchSendEmailRequest.setReceiverId(Convert.toLong(toId));
Template template = new Template();
template.setTemplateID(Convert.toLong(templateId));
template.setTemplateData(templateParam);
batchSendEmailRequest.setTemplate(template);
batchSendEmailRequest.setSubject(subject);
batchSendEmailRequest.setTaskType(1L);
if(ObjectUtil.isNotEmpty(attachmentList)) {
Attachment[] attachments = (Attachment[]) attachmentList.stream().map(jsonObject -> {
Attachment attachment = new Attachment();
BeanUtil.copyProperties(jsonObject, attachment);
return attachment;
}).toArray();
batchSendEmailRequest.setAttachments(attachments);
}
return batchSendEmailRequest;
}
}

View File

@@ -0,0 +1,260 @@
package mjkf.xinke.dev.modular.file.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.file.entity.DevFile;
import mjkf.xinke.dev.modular.file.enums.DevFileEngineTypeEnum;
import mjkf.xinke.dev.modular.file.param.DevFileIdParam;
import mjkf.xinke.dev.modular.file.param.DevFileListParam;
import mjkf.xinke.dev.modular.file.param.DevFilePageParam;
import mjkf.xinke.dev.modular.file.service.DevFileService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.io.IOException;
import java.util.List;
/**
* 文件控制器
*
*
* @date 2022/2/23 18:26
**/
@Api(tags = "文件控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 4)
@RestController
@Validated
public class DevFileController {
/** 默认文件引擎 */
private static final String SNOWY_SYS_DEFAULT_FILE_ENGINE_KEY = "SNOWY_SYS_DEFAULT_FILE_ENGINE";
@Resource
private DevConfigApi devConfigApi;
@Resource
private DevFileService devFileService;
/**
* 动态上传文件返回id
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 1)
@ApiOperation("动态上传文件返回id")
@CommonLog("动态上传文件返回id")
@PostMapping("/dev/file/uploadDynamicReturnId")
public CommonResult<String> uploadDynamicReturnId(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnId(devConfigApi.getValueByKey(SNOWY_SYS_DEFAULT_FILE_ENGINE_KEY), file));
}
/**
* 动态上传文件返回url
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 2)
@ApiOperation("动态上传文件返回url")
@CommonLog("动态上传文件返回url")
@PostMapping("/dev/file/uploadDynamicReturnUrl")
public CommonResult<String> uploadDynamicReturnUrl(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnUrl(devConfigApi.getValueByKey(SNOWY_SYS_DEFAULT_FILE_ENGINE_KEY), file));
}
/**
* 本地文件上传返回文件id
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 3)
@ApiOperation("上传本地文件返回id")
@CommonLog("上传本地文件返回id")
@PostMapping("/dev/file/uploadLocalReturnId")
public CommonResult<String> uploadLocalReturnId(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnId(DevFileEngineTypeEnum.LOCAL.getValue(), file));
}
/**
* 本地文件上传返回文件Url
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 4)
@ApiOperation("上传本地文件返回url")
@CommonLog("上传本地文件返回url")
@PostMapping("/dev/file/uploadLocalReturnUrl")
public CommonResult<String> uploadLocalReturnUrl(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnUrl(DevFileEngineTypeEnum.LOCAL.getValue(), file));
}
/**
* 阿里云文件上传返回文件id
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 5)
@ApiOperation("上传阿里云文件返回id")
@CommonLog("上传阿里云文件返回id")
@PostMapping("/dev/file/uploadAliyunReturnId")
public CommonResult<String> uploadAliyunReturnId(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnId(DevFileEngineTypeEnum.ALIYUN.getValue(), file));
}
/**
* 阿里云文件上传返回文件Url
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 6)
@ApiOperation("上传阿里云文件返回url")
@CommonLog("上传阿里云文件返回url")
@PostMapping("/dev/file/uploadAliyunReturnUrl")
public CommonResult<String> uploadAliyunReturnUrl(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnUrl(DevFileEngineTypeEnum.ALIYUN.getValue(), file));
}
/**
* 腾讯云文件上传返回文件id
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 7)
@ApiOperation("上传腾讯云文件返回id")
@CommonLog("上传腾讯云文件返回id")
@PostMapping("/dev/file/uploadTencentReturnId")
public CommonResult<String> uploadTencentReturnId(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnId(DevFileEngineTypeEnum.TENCENT.getValue(), file));
}
/**
* 腾讯云文件上传返回文件Url
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 8)
@ApiOperation("上传腾讯云文件返回url")
@CommonLog("上传腾讯云文件返回url")
@PostMapping("/dev/file/uploadTencentReturnUrl")
public CommonResult<String> uploadTencentReturnUrl(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnUrl(DevFileEngineTypeEnum.TENCENT.getValue(), file));
}
/**
* MINIO文件上传返回文件id
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 9)
@ApiOperation("上传MINIO文件返回id")
@CommonLog("上传MINIO文件返回id")
@PostMapping("/dev/file/uploadMinioReturnId")
public CommonResult<String> uploadMinioReturnId(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnId(DevFileEngineTypeEnum.MINIO.getValue(), file));
}
/**
* MINIO文件上传返回文件Url
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 10)
@ApiOperation("上传MINIO文件返回url")
@CommonLog("上传MINIO文件返回url")
@PostMapping("/dev/file/uploadMinioReturnUrl")
public CommonResult<String> uploadMinioReturnUrl(@RequestPart("file") MultipartFile file) {
return CommonResult.data(devFileService.uploadReturnUrl(DevFileEngineTypeEnum.MINIO.getValue(), file));
}
/**
* 获取文件分页列表
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 11)
@ApiOperation("获取文件分页列表")
@GetMapping("/dev/file/page")
public CommonResult<Page<DevFile>> page(DevFilePageParam devFilePageParam) {
return CommonResult.data(devFileService.page(devFilePageParam));
}
/**
* 获取文件列表
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 12)
@ApiOperation("获取文件列表")
@GetMapping("/dev/file/list")
public CommonResult<List<DevFile>> list(DevFileListParam devFileListParam) {
return CommonResult.data(devFileService.list(devFileListParam));
}
/**
* 下载文件
*
*
* @date 2022/6/21 15:44
**/
@ApiOperationSupport(order = 13)
@ApiOperation("下载文件")
@CommonLog("下载文件")
@GetMapping(value = "/dev/file/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void download(@Valid DevFileIdParam devFileIdParam, HttpServletResponse response) throws IOException {
devFileService.download(devFileIdParam, response);
}
/**
* 删除文件
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 14)
@ApiOperation("删除文件")
@CommonLog("删除文件")
@PostMapping(value = "/dev/file/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevFileIdParam> devFileIdParamList) {
devFileService.delete(devFileIdParamList);
return CommonResult.ok();
}
/**
* 获取文件详情
*
*
* @date 2022/6/21 15:44
**/
@ApiOperationSupport(order = 15)
@ApiOperation("获取文件详情")
@GetMapping("/dev/file/detail")
public CommonResult<DevFile> detail(@Valid DevFileIdParam devFileIdParam) {
return CommonResult.data(devFileService.detail(devFileIdParam));
}
}

View File

@@ -0,0 +1,72 @@
package mjkf.xinke.dev.modular.file.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
/**
* 文件实体
*
*
* @date 2022/2/23 18:27
**/
@Getter
@Setter
@TableName("DEV_FILE")
public class DevFile extends CommonEntity {
/** id */
@ApiModelProperty(value = "主键", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 存储引擎 */
@ApiModelProperty(value = "存储引擎", position = 3)
private String engine;
/** 存储桶 */
@ApiModelProperty(value = "存储桶", position = 4)
private String bucket;
/** 文件名称 */
@ApiModelProperty(value = "文件名称", position = 5)
private String name;
/** 文件后缀 */
@ApiModelProperty(value = "文件后缀", position = 6)
private String suffix;
/** 文件大小kb */
@ApiModelProperty(value = "文件大小kb", position = 7)
private String sizeKb;
/** 文件大小(格式化后) */
@ApiModelProperty(value = "文件大小(格式化后)", position = 8)
private String sizeInfo;
/** 文件的对象名(唯一名称) */
@ApiModelProperty(value = "文件的对象名(唯一名称)", position = 9)
private String objName;
/** 文件存储路径 */
@ApiModelProperty(value = "文件存储路径", position = 10)
private String storagePath;
/** 文件下载路径 */
@ApiModelProperty(value = "文件下载路径", position = 11)
private String downloadPath;
/** 图片缩略图 */
@ApiModelProperty(value = "图片缩略图", position = 12)
private String thumbnail;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 13)
private String extJson;
}

View File

@@ -0,0 +1,27 @@
package mjkf.xinke.dev.modular.file.enums;
/**
* 文件存储桶的权限策略枚举
*
*
* @date 2022/1/5 23:29
*/
public enum DevFileBucketAuthEnum {
/**
* 私有的(仅有 owner 可以读写)
*/
PRIVATE,
/**
* 公有读,私有写( owner 可以读写, 其他客户可以读)
*/
PUBLIC_READ,
/**
* 公共读写(即所有人都可以读写,慎用)
*/
PUBLIC_READ_WRITE
}

View File

@@ -0,0 +1,32 @@
package mjkf.xinke.dev.modular.file.enums;
import lombok.Getter;
/**
* 文件存储引擎类型枚举
*
*
* @date 2022/6/16 16:14
**/
@Getter
public enum DevFileEngineTypeEnum {
/** 本地 */
LOCAL("LOCAL"),
/** 阿里云 */
ALIYUN("ALIYUN"),
/** 腾讯云 */
TENCENT("TENCENT"),
/** MINIO */
MINIO("MINIO");
private final String value;
DevFileEngineTypeEnum(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,14 @@
package mjkf.xinke.dev.modular.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.file.entity.DevFile;
/**
* 文件Mapper接口
*
*
* @date 2022/2/23 18:40
**/
public interface DevFileMapper extends BaseMapper<DevFile> {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.file.mapper.DevFileMapper">
</mapper>

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.file.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 文件Id参数
*
*
* @date 2022/7/31 10:24
*/
@Getter
@Setter
public class DevFileIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,25 @@
package mjkf.xinke.dev.modular.file.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 文件列表参数
*
*
* @date 2022/7/31 10:24
*/
@Getter
@Setter
public class DevFileListParam {
/** 文件引擎 */
@ApiModelProperty(value = "文件引擎")
private String engine;
/** 文件名关键词 */
@ApiModelProperty(value = "文件名关键词")
private String searchKey;
}

View File

@@ -0,0 +1,25 @@
package mjkf.xinke.dev.modular.file.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 文件分页列表参数
*
*
* @date 2022/7/31 10:24
*/
@Getter
@Setter
public class DevFilePageParam {
/** 文件引擎 */
@ApiModelProperty(value = "文件引擎")
private String engine;
/** 文件名关键词 */
@ApiModelProperty(value = "文件名关键词")
private String searchKey;
}

View File

@@ -0,0 +1,63 @@
package mjkf.xinke.dev.modular.file.provider;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.dev.api.DevFileApi;
import mjkf.xinke.dev.modular.file.enums.DevFileEngineTypeEnum;
import mjkf.xinke.dev.modular.file.service.DevFileService;
import javax.annotation.Resource;
/**
* 文件API接口提供者
*
*
* @date 2022/6/22 15:32
**/
@Service
public class DevFileApiProvider implements DevFileApi {
@Resource
private DevFileService devFileService;
@Override
public String storageFileWithReturnUrlLocal(MultipartFile file) {
return devFileService.uploadReturnUrl(DevFileEngineTypeEnum.LOCAL.getValue(), file);
}
@Override
public String storageFileWithReturnIdLocal(MultipartFile file) {
return devFileService.uploadReturnId(DevFileEngineTypeEnum.LOCAL.getValue(), file);
}
@Override
public String storageFileWithReturnUrlAliyun(MultipartFile file) {
return devFileService.uploadReturnUrl(DevFileEngineTypeEnum.ALIYUN.getValue(), file);
}
@Override
public String storageFileWithReturnIdAliyun(MultipartFile file) {
return devFileService.uploadReturnId(DevFileEngineTypeEnum.ALIYUN.getValue(), file);
}
@Override
public String storageFileWithReturnUrlTencent(MultipartFile file) {
return devFileService.uploadReturnUrl(DevFileEngineTypeEnum.TENCENT.getValue(), file);
}
@Override
public String storageFileWithReturnIdTencent(MultipartFile file) {
return devFileService.uploadReturnId(DevFileEngineTypeEnum.TENCENT.getValue(), file);
}
@Override
public String storageFileWithReturnUrlMinio(MultipartFile file) {
return devFileService.uploadReturnUrl(DevFileEngineTypeEnum.MINIO.getValue(), file);
}
@Override
public String storageFileWithReturnIdMinio(MultipartFile file) {
return devFileService.uploadReturnId(DevFileEngineTypeEnum.MINIO.getValue(), file);
}
}

View File

@@ -0,0 +1,87 @@
package mjkf.xinke.dev.modular.file.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.dev.modular.file.entity.DevFile;
import mjkf.xinke.dev.modular.file.param.DevFileIdParam;
import mjkf.xinke.dev.modular.file.param.DevFileListParam;
import mjkf.xinke.dev.modular.file.param.DevFilePageParam;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* 文件Service接口
*
*
* @date 2022/2/23 18:27
**/
public interface DevFileService extends IService<DevFile> {
/**
* MultipartFile文件上传返回文件id
*
*
* @date 2022/4/22 15:53
**/
String uploadReturnId(String engine, MultipartFile file);
/**
* MultipartFile文件上传返回文件Url
*
*
* @date 2022/4/22 15:53
**/
String uploadReturnUrl(String engine, MultipartFile file);
/**
* 文件分页列表接口
*
*
* @date 2022/6/21 15:44
**/
Page<DevFile> page(DevFilePageParam devFilePageParam);
/**
* 文件列表接口
*
*
* @date 2022/6/21 15:44
**/
List<DevFile> list(DevFileListParam devFileListParam);
/**
* 下载文件
*
*
* @date 2022/6/21 15:44
**/
void download(DevFileIdParam devFileIdParam, HttpServletResponse response) throws IOException;
/**
* 删除文件
*
*
* @date 2022/8/4 10:36
**/
void delete(List<DevFileIdParam> devFileIdParamList);
/**
* 获取文件详情
*
*
* @date 2022/4/24 21:18
*/
DevFile detail(DevFileIdParam devFileIdParam);
/**
* 获取文件详情
*
*
* @date 2022/4/24 21:18
*/
DevFile queryEntity(String id);
}

View File

@@ -0,0 +1,275 @@
package mjkf.xinke.dev.modular.file.service.impl;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.common.page.CommonPageRequest;
import mjkf.xinke.common.prop.CommonProperties;
import mjkf.xinke.common.util.CommonDownloadUtil;
import mjkf.xinke.common.util.CommonResponseUtil;
import mjkf.xinke.dev.modular.file.entity.DevFile;
import mjkf.xinke.dev.modular.file.enums.DevFileEngineTypeEnum;
import mjkf.xinke.dev.modular.file.mapper.DevFileMapper;
import mjkf.xinke.dev.modular.file.param.DevFileIdParam;
import mjkf.xinke.dev.modular.file.param.DevFileListParam;
import mjkf.xinke.dev.modular.file.param.DevFilePageParam;
import mjkf.xinke.dev.modular.file.service.DevFileService;
import mjkf.xinke.dev.modular.file.util.DevFileAliyunUtil;
import mjkf.xinke.dev.modular.file.util.DevFileLocalUtil;
import mjkf.xinke.dev.modular.file.util.DevFileMinIoUtil;
import mjkf.xinke.dev.modular.file.util.DevFileTencentUtil;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
/**
* 文件Service接口实现类
*
*
* @date 2022/2/23 18:43
**/
@Service
public class DevFileServiceImpl extends ServiceImpl<DevFileMapper, DevFile> implements DevFileService {
@Resource
private CommonProperties commonProperties;
@Override
public String uploadReturnId(String engine, MultipartFile file) {
return this.storageFile(engine, file, true);
}
@Override
public String uploadReturnUrl(String engine, MultipartFile file) {
return this.storageFile(engine, file, false);
}
@Override
public Page<DevFile> page(DevFilePageParam devFilePageParam) {
QueryWrapper<DevFile> queryWrapper = new QueryWrapper<>();
if(ObjectUtil.isNotEmpty(devFilePageParam.getEngine())) {
queryWrapper.lambda().eq(DevFile::getEngine, devFilePageParam.getEngine());
}
if(ObjectUtil.isNotEmpty(devFilePageParam.getSearchKey())) {
queryWrapper.lambda().like(DevFile::getName, devFilePageParam.getSearchKey());
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Override
public List<DevFile> list(DevFileListParam devFileListParam) {
QueryWrapper<DevFile> queryWrapper = new QueryWrapper<>();
if(ObjectUtil.isNotEmpty(devFileListParam.getEngine())) {
queryWrapper.lambda().eq(DevFile::getEngine, devFileListParam.getEngine());
}
if(ObjectUtil.isNotEmpty(devFileListParam.getSearchKey())) {
queryWrapper.lambda().like(DevFile::getName, devFileListParam.getSearchKey());
}
return this.list(queryWrapper);
}
@Override
public void download(DevFileIdParam devFileIdParam, HttpServletResponse response) throws IOException {
DevFile devFile;
try {
devFile = this.queryEntity(devFileIdParam.getId());
} catch (Exception e) {
CommonResponseUtil.renderError(response, e.getMessage());
return;
}
if(!devFile.getEngine().equals(DevFileEngineTypeEnum.LOCAL.getValue())) {
CommonResponseUtil.renderError(response, "非本地文件不支持此方式下载id值为" + devFile.getId());
return;
}
File file = FileUtil.file(devFile.getStoragePath());
if(!FileUtil.exist(file)) {
CommonResponseUtil.renderError(response, "找不到存储的文件id值为" + devFile.getId());
return;
}
CommonDownloadUtil.download(devFile.getName(), IoUtil.readBytes(FileUtil.getInputStream(file)), response);
}
@Override
public void delete(List<DevFileIdParam> devFileIdParamList) {
this.removeByIds(CollStreamUtil.toList(devFileIdParamList, DevFileIdParam::getId));
}
/**
* 存储文件
*
*
* @date 2022/6/16 16:24
**/
private String storageFile(String engine, MultipartFile file, boolean returnFileId) {
// 如果引擎为空,默认使用本地
if(ObjectUtil.isEmpty(engine)) {
engine = DevFileEngineTypeEnum.LOCAL.getValue();
}
// 生成id
String fileId = IdWorker.getIdStr();
// 存储桶名称
String bucketName;
// 定义存储的url本地文件返回文件实际路径其他引擎返回网络地址
String storageUrl;
// 根据引擎类型执行不同方法
if(engine.equals(DevFileEngineTypeEnum.LOCAL.getValue())) {
// 使用固定名称defaultBucketName
bucketName = "defaultBucketName";
storageUrl = DevFileLocalUtil.storageFileWithReturnUrl(bucketName, genFileKey(fileId, file), file);
} else if(engine.equals(DevFileEngineTypeEnum.ALIYUN.getValue())) {
// 使用阿里云默认配置的bucketName
bucketName = DevFileAliyunUtil.getDefaultBucketName();
storageUrl = DevFileAliyunUtil.storageFileWithReturnUrl(bucketName, genFileKey(fileId, file), file);
} else if(engine.equals(DevFileEngineTypeEnum.TENCENT.getValue())) {
// 使用腾讯云默认配置的bucketName
bucketName = DevFileTencentUtil.getDefaultBucketName();
storageUrl = DevFileTencentUtil.storageFileWithReturnUrl(bucketName, genFileKey(fileId, file), file);
} else if(engine.equals(DevFileEngineTypeEnum.MINIO.getValue())) {
// 使用MINIO默认配置的bucketName
bucketName = DevFileMinIoUtil.getDefaultBucketName();
storageUrl = DevFileMinIoUtil.storageFileWithReturnUrl(bucketName, genFileKey(fileId, file), file);
} else {
throw new CommonException("不支持的文件引擎:{}", engine);
}
// 将文件信息保存到数据库
DevFile devFile = new DevFile();
// 设置文件id
devFile.setId(fileId);
// 设置存储引擎类型
devFile.setEngine(engine);
devFile.setBucket(bucketName);
devFile.setName(file.getOriginalFilename());
String suffix = ObjectUtil.isNotEmpty(file.getOriginalFilename())?StrUtil.subAfter(file.getOriginalFilename(),
StrUtil.DOT, true):null;
devFile.setSuffix(suffix);
devFile.setSizeKb(Convert.toStr(NumberUtil.div(new BigDecimal(file.getSize()), BigDecimal.valueOf(1024))
.setScale(0, BigDecimal.ROUND_HALF_UP)));
devFile.setSizeInfo(FileUtil.readableFileSize(file.getSize()));
devFile.setObjName(ObjectUtil.isNotEmpty(devFile.getSuffix())?fileId + StrUtil.DOT + devFile.getSuffix():null);
// 如果是图片,则压缩生成缩略图
if(ObjectUtil.isNotEmpty(suffix)) {
if(isPic(suffix)) {
try {
devFile.setThumbnail(ImgUtil.toBase64DataUri(ImgUtil.scale(ImgUtil.toImage(file.getBytes()),
100, 100, null), suffix));
} catch (Exception ignored) {
}
}
}
// 存储路径
devFile.setStoragePath(storageUrl);
// 定义下载地址
String downloadUrl;
// 下载路径,注意:本地文件下载地址设置为下载接口地址 + 文件id
if(engine.equals(DevFileEngineTypeEnum.LOCAL.getValue())) {
String apiUrl = commonProperties.getBackendUrl();
if(ObjectUtil.isEmpty(apiUrl)) {
throw new CommonException("后端域名地址未正确配置mjkf-xinke.config.common.backend-url为空");
}
downloadUrl= apiUrl + "/dev/file/download?id=" + fileId;
devFile.setDownloadPath(downloadUrl);
} else {
// 阿里云、腾讯云、MINIO可以直接使用存储地址公网作为下载地址
downloadUrl= storageUrl;
devFile.setDownloadPath(devFile.getStoragePath());
}
this.save(devFile);
// 如果是返回id则返回文件id
if(returnFileId) {
return fileId;
} else {
// 否则返回下载地址
return downloadUrl;
}
}
/**
* 生成文件的key格式如 2021/10/11/1377109572375810050.docx
*
*
* @date 2022/4/22 15:58
**/
public String genFileKey(String fileId, MultipartFile file) {
// 获取文件原始名称
String originalFileName = file.getOriginalFilename();
// 获取文件后缀
String fileSuffix = FileUtil.getSuffix(originalFileName);
// 生成文件的对象名称,格式如:1377109572375810050.docx
String fileObjectName = fileId + StrUtil.DOT + fileSuffix;
// 获取日期文件夹格式如2021/10/11/
String dateFolderPath = DateUtil.thisYear() + StrUtil.SLASH +
(DateUtil.thisMonth() + 1) + StrUtil.SLASH +
DateUtil.thisDayOfMonth() + StrUtil.SLASH;
// 返回
return dateFolderPath + fileObjectName;
}
@Override
public DevFile detail(DevFileIdParam devFileIdParam) {
return this.queryEntity(devFileIdParam.getId());
}
@Override
public DevFile queryEntity(String id) {
DevFile devFile = this.getById(id);
if(ObjectUtil.isEmpty(devFile)) {
throw new CommonException("文件不存在id值为{}", id);
}
return devFile;
}
/**
* 根据文件后缀判断是否图片
*
*
* @date 2020/7/6 15:31
*/
private static boolean isPic(String fileSuffix) {
fileSuffix = fileSuffix.toLowerCase();
return ImgUtil.IMAGE_TYPE_GIF.equals(fileSuffix)
|| ImgUtil.IMAGE_TYPE_JPG.equals(fileSuffix)
|| ImgUtil.IMAGE_TYPE_JPEG.equals(fileSuffix)
|| ImgUtil.IMAGE_TYPE_BMP.equals(fileSuffix)
|| ImgUtil.IMAGE_TYPE_PNG.equals(fileSuffix)
|| ImgUtil.IMAGE_TYPE_PSD.equals(fileSuffix);
}
}

View File

@@ -0,0 +1,460 @@
package mjkf.xinke.dev.modular.file.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.aliyun.oss.*;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.file.enums.DevFileBucketAuthEnum;
import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.List;
/**
* 阿里云文件工具类
* 参考文档https://help.aliyun.com/document_detail/32010.html
*
*
* @date 2022/1/2 18:13
*/
@Slf4j
public class DevFileAliyunUtil {
private static OSS client;
private static String defaultBucketName;
private static final String SNOWY_FILE_ALIYUN_ACCESS_KEY_ID_KEY = "SNOWY_FILE_ALIYUN_ACCESS_KEY_ID";
private static final String SNOWY_FILE_ALIYUN_ACCESS_KEY_SECRET_KEY = "SNOWY_FILE_ALIYUN_ACCESS_KEY_SECRET";
private static final String SNOWY_FILE_ALIYUN_END_POINT_KEY = "SNOWY_FILE_ALIYUN_END_POINT";
private static final String SNOWY_FILE_ALIYUN_DEFAULT_BUCKET_NAME = "SNOWY_FILE_ALIYUN_DEFAULT_BUCKET_NAME";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* accessKeyId */
String accessKeyId = devConfigApi.getValueByKey(SNOWY_FILE_ALIYUN_ACCESS_KEY_ID_KEY);
if(ObjectUtil.isEmpty(accessKeyId)) {
throw new CommonException("阿里云文件操作客户端未正确配置accessKeyId为空");
}
/* accessKeySecret */
String accessKeySecret = devConfigApi.getValueByKey(SNOWY_FILE_ALIYUN_ACCESS_KEY_SECRET_KEY);
if(ObjectUtil.isEmpty(accessKeySecret)) {
throw new CommonException("阿里云文件操作客户端未正确配置accessKeySecret为空");
}
/* endpoint */
String endpoint = devConfigApi.getValueByKey(SNOWY_FILE_ALIYUN_END_POINT_KEY);
if(ObjectUtil.isEmpty(accessKeySecret)) {
throw new CommonException("阿里云文件操作客户端未正确配置endpoint为空");
}
/* 默认BucketName */
defaultBucketName = devConfigApi.getValueByKey(SNOWY_FILE_ALIYUN_DEFAULT_BUCKET_NAME);
if(ObjectUtil.isEmpty(defaultBucketName)) {
throw new CommonException("阿里云文件操作客户端未正确配置defaultBucketName为空");
}
client = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
/**
* 获取默认存储桶名称
*
*
* @date 2022/6/22 18:05
**/
public static String getDefaultBucketName() {
initClient();
return defaultBucketName;
}
/**
* 销毁操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static void destroyClient() {
initClient();
client.shutdown();
}
/**
* 获取操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public OSS getClient() {
initClient();
return client;
}
/**
* 查询存储桶是否存在
* 例如传入参数examplebucket-1250000000返回true代表存在此桶
*
* @param bucketName 桶名称
*
* @date 2022/1/5 23:24
*/
public boolean doesBucketExist(String bucketName) {
try {
initClient();
return client.doesBucketExist(bucketName);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 设置预定义策略
* 预定义策略如公有读、公有读写、私有读
*
* @param bucketName 桶名称
* @param devFileBucketAuthEnum 存储桶权限
*
* @date 2022/1/5 23:24
*/
public static void setBucketAcl(String bucketName, DevFileBucketAuthEnum devFileBucketAuthEnum) {
try {
initClient();
if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PRIVATE)) {
client.setBucketAcl(bucketName, CannedAccessControlList.Private);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ)) {
client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ_WRITE)) {
client.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite);
}
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 判断是否存在文件
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static boolean isExistingFile(String bucketName, String key) {
try {
initClient();
return client.doesObjectExist(bucketName, key);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, File file) {
BufferedInputStream inputStream;
try {
inputStream = FileUtil.getInputStream(file);
} catch (IORuntimeException e) {
throw new CommonException("获取文件流异常,名称是:{}", file.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, MultipartFile multipartFile) {
InputStream inputStream;
try {
inputStream = multipartFile.getInputStream();
} catch (IOException e) {
throw new CommonException("获取文件流异常,名称是:{}", multipartFile.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
try {
initClient();
byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(getFileContentType(key));
client.putObject(bucketName, key, byteArrayInputStream, objectMetadata);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(byteArrayInputStream);
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, InputStream inputStream) {
try {
initClient();
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(getFileContentType(key));
client.putObject(bucketName, key, inputStream, objectMetadata);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(inputStream);
}
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, File file) {
storageFile(bucketName, key, file);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
storageFile(bucketName, key, multipartFile);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, byte[] bytes) {
storageFile(bucketName, key, bytes);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, InputStream inputStream) {
storageFile(bucketName, key, inputStream);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 获取某个bucket下的文件字节
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static byte[] getFileBytes(String bucketName, String key) {
InputStream objectContent = null;
try {
initClient();
OSSObject ossObject = client.getObject(bucketName, key);
objectContent = ossObject.getObjectContent();
return IoUtil.readBytes(objectContent);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(objectContent);
}
}
/**
* 设置文件访问权限管理
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param devFileBucketAuthEnum 文件权限
*
* @date 2022/1/5 23:24
*/
public static void setFileAcl(String bucketName, String key, DevFileBucketAuthEnum devFileBucketAuthEnum) {
try {
initClient();
if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PRIVATE)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.Private);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ_WRITE)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite);
}
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 拷贝文件
*
* @param originBucketName 源文件桶
* @param originFileKey 源文件名称
* @param newBucketName 新文件桶
* @param newFileKey 新文件名称
*
* @date 2022/1/5 23:24
*/
public static void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
try {
initClient();
client.copyObject(originBucketName, originFileKey, newBucketName, newFileKey);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 获取文件的下载地址(带鉴权和有效时间的),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param timeoutMillis 时效
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) {
initClient();
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key, HttpMethod.GET);
Date expirationDate = new Date(System.currentTimeMillis() + timeoutMillis);
request.setExpiration(expirationDate);
URL url;
try {
url = client.generatePresignedUrl(request);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
return url.toString();
}
/**
* 获取文件的下载地址(永久的,文件必须为公有读),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key) {
try {
initClient();
OSSClient ossClient = (OSSClient) client;
List<String> urlList = StrUtil.split(ossClient.getEndpoint().toString(), StrUtil.COLON + StrUtil.SLASH + StrUtil.SLASH);
return urlList.get(0) + StrUtil.COLON + StrUtil.SLASH + StrUtil.SLASH + bucketName + StrUtil.DOT + urlList.get(1) + StrUtil.SLASH + key;
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 删除文件
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static void deleteFile(String bucketName, String key) {
try{
initClient();
client.deleteObject(bucketName, key);
} catch (OSSException | ClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 根据文件名获取ContentType
*
*
* @date 2022/1/6 11:27
**/
private static String getFileContentType(String key) {
// 根据文件名获取contentType
String contentType = "application/octet-stream";
if (key.contains(".")) {
contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key);
}
return contentType;
}
}

View File

@@ -0,0 +1,352 @@
package mjkf.xinke.dev.modular.file.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.system.SystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.file.enums.DevFileBucketAuthEnum;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* 本地文件工具类
*
*
* @date 2022/1/2 18:13
*/
@Slf4j
public class DevFileLocalUtil {
private static JSONObject client;
private static final String SNOWY_FILE_LOCAL_FOLDER_FOR_WINDOWS_KEY = "SNOWY_FILE_LOCAL_FOLDER_FOR_WINDOWS";
private static final String SNOWY_FILE_LOCAL_FOLDER_FOR_UNIX_KEY = "SNOWY_FILE_LOCAL_FOLDER_FOR_UNIX";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
String uploadFileFolder;
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
if(SystemUtil.getOsInfo().isWindows()) {
/* 本地文件上传的位置 windows系统 */
String localFolderForWindows = devConfigApi.getValueByKey(SNOWY_FILE_LOCAL_FOLDER_FOR_WINDOWS_KEY);
if(ObjectUtil.isEmpty(localFolderForWindows)) {
throw new CommonException("本地文件操作客户端未正确配置SNOWY_FILE_LOCAL_FOLDER_FOR_WINDOWS为空");
}
uploadFileFolder = localFolderForWindows;
} else {
/* 本地文件上传的位置 unix系列系统linux、mac等 */
String localFolderForUnix = devConfigApi.getValueByKey(SNOWY_FILE_LOCAL_FOLDER_FOR_UNIX_KEY);
if(ObjectUtil.isEmpty(localFolderForUnix)) {
throw new CommonException("本地文件操作客户端未正确配置SNOWY_FILE_LOCAL_FOLDER_FOR_UNIX为空");
}
uploadFileFolder = localFolderForUnix;
}
if(!FileUtil.exist(uploadFileFolder)) {
FileUtil.mkdir(uploadFileFolder);
}
client = JSONUtil.createObj();
client.set("localFileUploadFolder", uploadFileFolder);
}
/**
* 销毁操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static void destroyClient() {
client.clear();
}
/**
* 获取操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static JSONObject getClient() {
return client;
}
/**
* 获取上传地址
*
*
* @date 2022/1/5 23:24
*/
public static String getUploadFileFolder() {
return client.getStr("localFileUploadFolder");
}
/**
* 查询存储桶是否存在
* 例如传入参数examplebucket-1250000000返回true代表存在此桶
*
* @param bucketName 桶名称
*
* @date 2022/1/5 23:24
*/
public static boolean doesBucketExist(String bucketName) {
initClient();
return FileUtil.exist(getUploadFileFolder() + FileUtil.FILE_SEPARATOR + bucketName);
}
/**
* 设置预定义策略
* 预定义策略如公有读、公有读写、私有读
*
* @param bucketName 桶名称
* @param devFileBucketAuthEnum 存储桶权限
*
* @date 2022/1/5 23:24
*/
public static void setBucketAcl(String bucketName, DevFileBucketAuthEnum devFileBucketAuthEnum) {
// 无需
}
/**
* 判断是否存在文件
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static boolean isExistingFile(String bucketName, String key) {
initClient();
return FileUtil.exist(getUploadFileFolder() + FileUtil.FILE_SEPARATOR + bucketName + FileUtil.FILE_SEPARATOR + key);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, File file) {
BufferedInputStream inputStream;
try {
inputStream = FileUtil.getInputStream(file);
} catch (IORuntimeException e) {
throw new CommonException("获取文件流异常,名称是:{}", file.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, MultipartFile multipartFile) {
InputStream inputStream;
try {
inputStream = multipartFile.getInputStream();
} catch (IOException e) {
throw new CommonException("获取文件流异常,名称是:{}", multipartFile.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, byte[] bytes) {
initClient();
FileUtil.writeBytes(bytes, getUploadFileFolder() + FileUtil.FILE_SEPARATOR + bucketName + FileUtil.FILE_SEPARATOR + key);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, InputStream inputStream) {
initClient();
FileUtil.writeFromStream(inputStream, getUploadFileFolder() + FileUtil.FILE_SEPARATOR + bucketName + FileUtil.FILE_SEPARATOR + key);
}
/**
* 存储文件,返回存储的地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, File file) {
storageFile(bucketName, key, file);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回存储的地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
storageFile(bucketName, key, multipartFile);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回存储的地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, byte[] bytes) {
storageFile(bucketName, key, bytes);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回存储的地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, InputStream inputStream) {
storageFile(bucketName, key, inputStream);
return getFileAuthUrl(bucketName, key);
}
/**
* 获取某个bucket下的文件字节
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static byte[] getFileBytes(String bucketName, String key) {
File file = getFileByBucketNameAndKey(bucketName, key);
return FileUtil.readBytes(file);
}
/**
* 设置文件访问权限管理
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param devFileBucketAuthEnum 文件权限
*
* @date 2022/1/5 23:24
*/
public static void setFileAcl(String bucketName, String key, DevFileBucketAuthEnum devFileBucketAuthEnum) {
// 无需
}
/**
* 拷贝文件
*
* @param originBucketName 源文件桶
* @param originFileKey 源文件名称
* @param newBucketName 新文件桶
* @param newFileKey 新文件名称
*
* @date 2022/1/5 23:24
*/
public static void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
initClient();
File file = getFileByBucketNameAndKey(originBucketName, originFileKey);
File newFile = FileUtil.file(getUploadFileFolder() + FileUtil.FILE_SEPARATOR + newBucketName + FileUtil.FILE_SEPARATOR + newFileKey);
FileUtil.copy(file, newFile, true);
}
/**
* 获取文件的实际存储地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key) {
initClient();
File file = getFileByBucketNameAndKey(bucketName, key);
return file.getAbsolutePath();
}
/**
* 删除文件
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static void deleteFile(String bucketName, String key) {
File file = getFileByBucketNameAndKey(bucketName, key);
FileUtil.del(file);
}
/**
* 根据桶名称和文件key获取文件
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static File getFileByBucketNameAndKey(String bucketName, String key) {
initClient();
String path = getUploadFileFolder() + FileUtil.FILE_SEPARATOR + bucketName + FileUtil.FILE_SEPARATOR + key;
File file = FileUtil.file(path);
if(!FileUtil.exist(file)) {
throw new CommonException("文件{}不存在", path);
}
return file;
}
}

View File

@@ -0,0 +1,455 @@
package mjkf.xinke.dev.modular.file.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.file.enums.DevFileBucketAuthEnum;
import javax.activation.MimetypesFileTypeMap;
import java.io.*;
/**
* MINIO文件工具类
* 参考文档http://docs.minio.org.cn/docs/master/java-client-quickstart-guide
*
*
* @date 2022/1/2 18:13
*/
@Slf4j
public class DevFileMinIoUtil {
private static MinioClient client;
private static String defaultBucketName;
private static final String SNOWY_FILE_MINIO_ACCESS_KEY_KEY = "SNOWY_FILE_MINIO_ACCESS_KEY";
private static final String SNOWY_FILE_MINIO_SECRET_KEY_KEY = "SNOWY_FILE_MINIO_SECRET_KEY";
private static final String SNOWY_FILE_MINIO_END_POINT_KEY = "SNOWY_FILE_MINIO_END_POINT";
private static final String SNOWY_FILE_MINIO_DEFAULT_BUCKET_NAME = "SNOWY_FILE_MINIO_DEFAULT_BUCKET_NAME";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* accessKey */
String accessKey = devConfigApi.getValueByKey(SNOWY_FILE_MINIO_ACCESS_KEY_KEY);
if(ObjectUtil.isEmpty(accessKey)) {
throw new CommonException("MINIO文件操作客户端未正确配置accessKey为空");
}
/* secretKey */
String secretKey = devConfigApi.getValueByKey(SNOWY_FILE_MINIO_SECRET_KEY_KEY);
if(ObjectUtil.isEmpty(secretKey)) {
throw new CommonException("MINIO文件操作客户端未正确配置secretKey为空");
}
/* endpoint */
String endpoint = devConfigApi.getValueByKey(SNOWY_FILE_MINIO_END_POINT_KEY);
if(ObjectUtil.isEmpty(endpoint)) {
throw new CommonException("MINIO文件操作客户端未正确配置secretKey为空");
}
/* 默认BucketName */
defaultBucketName = devConfigApi.getValueByKey(SNOWY_FILE_MINIO_DEFAULT_BUCKET_NAME);
if(ObjectUtil.isEmpty(defaultBucketName)) {
throw new CommonException("MINIO文件操作客户端未正确配置defaultBucketName为空");
}
client = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
/**
* 获取默认存储桶名称
*
*
* @date 2022/6/22 18:05
**/
public static String getDefaultBucketName() {
initClient();
return defaultBucketName;
}
/**
* 销毁操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static void destroyClient() {
// 无需
}
/**
* 获取操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static MinioClient getClient() {
return client;
}
/**
* 查询存储桶是否存在
* 例如传入参数examplebucket-1250000000返回true代表存在此桶
*
* @param bucketName 桶名称
*
* @date 2022/1/5 23:24
*/
public static boolean doesBucketExist(String bucketName) {
try {
initClient();
BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
client.bucketExists(bucketExistsArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
return false;
}
/**
* 设置预定义策略
* 预定义策略如公有读、公有读写、私有读
*
* @param bucketName 桶名称
* @param devFileBucketAuthEnum 存储桶权限
*
* @date 2022/1/5 23:24
*/
public static void setBucketAcl(String bucketName, DevFileBucketAuthEnum devFileBucketAuthEnum) {
setFileAcl(bucketName, "*", devFileBucketAuthEnum);
}
/**
* 判断是否存在文件
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static boolean isExistingFile(String bucketName, String key) {
try {
initClient();
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(key).build();
InputStream object = client.getObject(getObjectArgs);
return !ObjectUtil.isEmpty(object);
} catch (Exception e) {
return false;
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, File file) {
BufferedInputStream inputStream;
try {
inputStream = FileUtil.getInputStream(file);
} catch (IORuntimeException e) {
throw new CommonException("获取文件流异常,名称是:{}", file.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, MultipartFile multipartFile) {
InputStream inputStream;
try {
inputStream = multipartFile.getInputStream();
} catch (IOException e) {
throw new CommonException("获取文件流异常,名称是:{}", multipartFile.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
try {
initClient();
byteArrayInputStream = new ByteArrayInputStream(bytes);
PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(key)
.contentType(getFileContentType(key)).stream(byteArrayInputStream, bytes.length, -1).build();
client.putObject(putObjectArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(byteArrayInputStream);
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, InputStream inputStream) {
try {
initClient();
PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(key)
.contentType(getFileContentType(key)).stream(inputStream, inputStream.available(), -1).build();
client.putObject(putObjectArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(inputStream);
}
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, File file) {
storageFile(bucketName, key, file);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
storageFile(bucketName, key, multipartFile);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, byte[] bytes) {
storageFile(bucketName, key, bytes);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, InputStream inputStream) {
storageFile(bucketName, key, inputStream);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 获取某个bucket下的文件字节
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static byte[] getFileBytes(String bucketName, String key) {
try {
initClient();
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(key).build();
InputStream inputStream = client.getObject(getObjectArgs);
return IoUtil.readBytes(inputStream);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 设置文件访问权限管理
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param devFileBucketAuthEnum 文件权限
*
* @date 2022/1/5 23:24
*/
public static void setFileAcl(String bucketName, String key, DevFileBucketAuthEnum devFileBucketAuthEnum) {
try {
JSONObject configObject = JSONUtil.createObj().set("Version", "2012-10-17");
JSONArray statementArray = JSONUtil.createArray();
JSONArray actionArray = JSONUtil.createArray();
if(devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ)) {
actionArray.put("s3:GetObject");
}
if(devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ_WRITE)) {
actionArray.put("s3:GetObject");
actionArray.put("s3:PutObject");
}
JSONObject statementObject = JSONUtil.createObj();
statementObject.set("Effect", "Allow").set("Principal", JSONUtil.createObj().set("AWS", JSONUtil.createArray().put("*")))
.set("Action", actionArray).set("Resource", JSONUtil.createArray().put("arn:aws:s3:::" + bucketName + "/*"));
statementArray.put(statementObject);
configObject.set("Statement", statementArray);
String config = JSONUtil.toJsonStr(configObject);
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder().bucket(bucketName).config(config).build();
client.setBucketPolicy(setBucketPolicyArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 拷贝文件
*
* @param originBucketName 源文件桶
* @param originFileKey 源文件名称
* @param newBucketName 新文件桶
* @param newFileKey 新文件名称
*
* @date 2022/1/5 23:24
*/
public static void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
try {
initClient();
CopySource copySource = CopySource.builder().bucket(originBucketName).object(originFileKey).build();
CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder().source(copySource).bucket(newBucketName).object(newFileKey).build();
client.copyObject(copyObjectArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 获取文件的下载地址(带鉴权和有效时间的),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param timeoutMillis 时效
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) {
try {
initClient();
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder().bucket(bucketName)
.object(key).method(Method.GET).expiry(timeoutMillis.intValue()).build();
return client.getPresignedObjectUrl(getPresignedObjectUrlArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 获取文件的下载地址(永久的,文件必须为公有读),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key) {
try {
initClient();
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
return devConfigApi.getValueByKey(SNOWY_FILE_MINIO_END_POINT_KEY) + StrUtil.SLASH + bucketName + StrUtil.SLASH + key;
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 删除文件
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static void deleteFile(String bucketName, String key) {
try {
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucketName).object(key).build();
client.removeObject(removeObjectArgs);
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
/**
* 根据文件名获取ContentType
*
*
* @date 2022/1/6 11:27
**/
private static String getFileContentType(String key) {
// 根据文件名获取contentType
String contentType = "application/octet-stream";
if (key.contains(".")) {
contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key);
}
return contentType;
}
}

View File

@@ -0,0 +1,492 @@
package mjkf.xinke.dev.modular.file.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import mjkf.xinke.common.exception.CommonException;
import mjkf.xinke.dev.api.DevConfigApi;
import mjkf.xinke.dev.modular.file.enums.DevFileBucketAuthEnum;
import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 腾讯云文件工具类
* 参考文档https://cloud.tencent.com/document/product/436/10199
*
*
* @date 2022/1/2 18:13
*/
@Slf4j
public class DevFileTencentUtil {
private static COSClient client;
private static String defaultBucketName;
private static TransferManager transferManager;
private static final String SNOWY_FILE_TENCENT_SECRET_ID_KEY = "SNOWY_FILE_TENCENT_SECRET_ID";
private static final String SNOWY_FILE_TENCENT_SECRET_KEY_KEY = "SNOWY_FILE_TENCENT_SECRET_KEY";
private static final String SNOWY_FILE_TENCENT_REGION_ID_KEY = "SNOWY_FILE_TENCENT_REGION_ID";
private static final String SNOWY_FILE_TENCENT_DEFAULT_BUCKET_NAME = "SNOWY_FILE_TENCENT_DEFAULT_BUCKET_NAME";
/**
* 初始化操作的客户端
*
*
* @date 2022/1/5 23:24
*/
private static void initClient() {
DevConfigApi devConfigApi = SpringUtil.getBean(DevConfigApi.class);
/* secretId */
String secretId = devConfigApi.getValueByKey(SNOWY_FILE_TENCENT_SECRET_ID_KEY);
if(ObjectUtil.isEmpty(secretId)) {
throw new CommonException("腾讯云文件操作客户端未正确配置secretId为空");
}
/* secretKey */
String secretKey = devConfigApi.getValueByKey(SNOWY_FILE_TENCENT_SECRET_KEY_KEY);
if(ObjectUtil.isEmpty(secretKey)) {
throw new CommonException("腾讯云文件操作客户端未正确配置secretKey为空");
}
/* regionId */
String regionId = devConfigApi.getValueByKey(SNOWY_FILE_TENCENT_REGION_ID_KEY);
if(ObjectUtil.isEmpty(regionId)) {
throw new CommonException("腾讯云文件操作客户端未正确配置regionId为空");
}
/* 默认BucketName */
defaultBucketName = devConfigApi.getValueByKey(SNOWY_FILE_TENCENT_DEFAULT_BUCKET_NAME);
if(ObjectUtil.isEmpty(defaultBucketName)) {
throw new CommonException("腾讯云文件操作客户端未正确配置defaultBucketName为空");
}
// 1.初始化用户身份信息
Region region = new Region(regionId);
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2.设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
ClientConfig clientConfig = new ClientConfig(region);
// 3.生成 cos 客户端。
client = new COSClient(cred, clientConfig);
// 4.线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM同地域上传 COS的情况下设置成16或32即可可较充分的利用网络资源
// 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
ExecutorService threadPool = Executors.newFixedThreadPool(32);
// 5.传入一个线程池, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
transferManager = new TransferManager(client, threadPool);
// 6.设置高级接口的分块上传阈值和分块大小为10MB
TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
transferManagerConfiguration.setMultipartUploadThreshold(10 * 1024 * 1024);
transferManagerConfiguration.setMinimumUploadPartSize(10 * 1024 * 1024);
transferManager.setConfiguration(transferManagerConfiguration);
}
/**
* 获取默认存储桶名称
*
*
* @date 2022/6/22 18:05
**/
public static String getDefaultBucketName() {
initClient();
return defaultBucketName;
}
/**
* 销毁操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static void destroyClient() {
initClient();
client.shutdown();
}
/**
* 获取操作的客户端
*
*
* @date 2022/1/5 23:24
*/
public static COSClient getClient() {
initClient();
return client;
}
/**
* 查询存储桶是否存在
* 例如传入参数examplebucket-1250000000返回true代表存在此桶
*
* @param bucketName 桶名称
*
* @date 2022/1/5 23:24
*/
public static boolean doesBucketExist(String bucketName) {
try {
initClient();
return client.doesBucketExist(bucketName);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 设置预定义策略
* 预定义策略如公有读、公有读写、私有读
*
* @param bucketName 桶名称
* @param devFileBucketAuthEnum 存储桶权限
*
* @date 2022/1/5 23:24
*/
public static void setBucketAcl(String bucketName, DevFileBucketAuthEnum devFileBucketAuthEnum) {
try {
initClient();
if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PRIVATE)) {
client.setBucketAcl(bucketName, CannedAccessControlList.Private);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ)) {
client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ_WRITE)) {
client.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite);
}
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 判断是否存在文件
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static boolean isExistingFile(String bucketName, String key) {
try {
initClient();
client.getObjectMetadata(bucketName, key);
return true;
} catch (CosClientException e) {
return false;
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, File file) {
BufferedInputStream inputStream;
try {
inputStream = FileUtil.getInputStream(file);
} catch (IORuntimeException e) {
throw new CommonException("获取文件流异常,名称是:{}", file.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static void storageFile(String bucketName, String key, MultipartFile multipartFile) {
InputStream inputStream;
try {
inputStream = multipartFile.getInputStream();
} catch (IOException e) {
throw new CommonException("获取文件流异常,名称是:{}", multipartFile.getName());
}
storageFile(bucketName, key, inputStream);
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
try {
initClient();
byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(getFileContentType(key));
client.putObject(bucketName, key, byteArrayInputStream, objectMetadata);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(byteArrayInputStream);
}
}
/**
* 存储文件,不返回地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static void storageFile(String bucketName, String key, InputStream inputStream) {
try {
initClient();
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(getFileContentType(key));
client.putObject(bucketName, key, inputStream, objectMetadata);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(inputStream);
}
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param file 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, File file) {
storageFile(bucketName, key, file);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param multipartFile 文件
*
* @date 2022/1/5 23:45
*/
public static String storageFileWithReturnUrl(String bucketName, String key, MultipartFile multipartFile) {
storageFile(bucketName, key, multipartFile);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, byte[] bytes) {
storageFile(bucketName, key, bytes);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 存储文件,返回外网地址
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param inputStream 文件流
*
* @date 2022/1/5 23:24
*/
public static String storageFileWithReturnUrl(String bucketName, String key, InputStream inputStream) {
storageFile(bucketName, key, inputStream);
setFileAcl(bucketName, key, DevFileBucketAuthEnum.PUBLIC_READ);
return getFileAuthUrl(bucketName, key);
}
/**
* 获取某个bucket下的文件字节
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static byte[] getFileBytes(String bucketName, String key) {
COSObjectInputStream cosObjectInput = null;
try {
initClient();
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
COSObject cosObject = client.getObject(getObjectRequest);
cosObjectInput = cosObject.getObjectContent();
return IoUtil.readBytes(cosObjectInput);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
} finally {
IoUtil.close(cosObjectInput);
}
}
/**
* 设置文件访问权限管理
*
* @param bucketName 桶名称
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param devFileBucketAuthEnum 文件权限
*
* @date 2022/1/5 23:24
*/
public static void setFileAcl(String bucketName, String key, DevFileBucketAuthEnum devFileBucketAuthEnum) {
try {
initClient();
if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PRIVATE)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.Private);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead);
} else if (devFileBucketAuthEnum.equals(DevFileBucketAuthEnum.PUBLIC_READ_WRITE)) {
client.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite);
}
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 拷贝文件
*
* @param originBucketName 源文件桶
* @param originFileKey 源文件名称
* @param newBucketName 新文件桶
* @param newFileKey 新文件名称
*
* @date 2022/1/5 23:24
*/
public static void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
try {
initClient();
transferManager.copy(originBucketName, originFileKey, newBucketName, newFileKey);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 获取文件的下载地址(带鉴权和有效时间的),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
* @param timeoutMillis 时效
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET);
Date expirationDate = new Date(System.currentTimeMillis() + timeoutMillis);
request.setExpiration(expirationDate);
URL url;
try {
initClient();
url = client.generatePresignedUrl(request);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
return url.toString();
}
/**
* 获取文件的下载地址(永久的,文件必须为公有读),生成外网地址
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static String getFileAuthUrl(String bucketName, String key) {
URL url;
try {
initClient();
url = client.getObjectUrl(bucketName, key);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
return url.toString();
}
/**
* 删除文件
*
* @param bucketName 文件桶
* @param key 唯一标示id例如a.txt, doc/a.txt
*
* @date 2022/1/5 23:24
*/
public static void deleteFile(String bucketName, String key) {
try{
initClient();
client.deleteObject(bucketName, key);
} catch (CosClientException e) {
throw new CommonException(e.getMessage());
}
}
/**
* 根据文件名获取ContentType
*
*
* @date 2022/1/6 11:27
**/
private static String getFileContentType(String key) {
// 根据文件名获取contentType
String contentType = "application/octet-stream";
if (key.contains(".")) {
contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key);
}
return contentType;
}
}

View File

@@ -0,0 +1,183 @@
package mjkf.xinke.dev.modular.job.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import mjkf.xinke.common.annotation.CommonLog;
import mjkf.xinke.common.pojo.CommonResult;
import mjkf.xinke.common.pojo.CommonValidList;
import mjkf.xinke.dev.modular.job.entity.DevJob;
import mjkf.xinke.dev.modular.job.param.*;
import mjkf.xinke.dev.modular.job.service.DevJobService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 定时任务控制器
*
*
* @date 2022/8/5 10:48
**/
@Api(tags = "定时任务控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 7)
@RestController
@Validated
public class DevJobController {
@Resource
private DevJobService devJobService;
/**
* 获取定时任务分页
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 1)
@ApiOperation("获取定时任务分页")
@GetMapping("/dev/job/page")
public CommonResult<Page<DevJob>> page(DevJobPageParam devJobPageParam) {
return CommonResult.data(devJobService.page(devJobPageParam));
}
/**
* 获取定时任务列表
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 2)
@ApiOperation("获取定时任务列表")
@GetMapping("/dev/job/list")
public CommonResult<List<DevJob>> list(DevJobListParam devJobListParam) {
return CommonResult.data(devJobService.list(devJobListParam));
}
/**
* 添加定时任务
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 3)
@ApiOperation("添加定时任务")
@CommonLog("添加定时任务")
@PostMapping("/dev/job/add")
public CommonResult<String> add(@RequestBody @Valid DevJobAddParam devJobAddParam) {
devJobService.add(devJobAddParam);
return CommonResult.ok();
}
/**
* 编辑定时任务
*
*
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 4)
@ApiOperation("编辑定时任务")
@CommonLog("编辑定时任务")
@PostMapping("/dev/job/edit")
public CommonResult<String> edit(@RequestBody @Valid DevJobEditParam devJobEditParam) {
devJobService.edit(devJobEditParam);
return CommonResult.ok();
}
/**
* 删除定时任务
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 5)
@ApiOperation("删除定时任务")
@CommonLog("删除定时任务")
@PostMapping("/dev/job/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<DevJobIdParam> devJobIdParamList) {
devJobService.delete(devJobIdParamList);
return CommonResult.ok();
}
/**
* 获取定时任务详情
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 6)
@ApiOperation("获取定时任务详情")
@GetMapping("/dev/job/detail")
public CommonResult<DevJob> detail(@Valid DevJobIdParam devJobIdParam) {
return CommonResult.data(devJobService.detail(devJobIdParam));
}
/**
* 停止定时任务
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 6)
@ApiOperation("停止定时任务")
@CommonLog("停止定时任务")
@PostMapping("/dev/job/stopJob")
public CommonResult<String> stopJob(@RequestBody DevJobIdParam devJobIdParam) {
devJobService.stopJob(devJobIdParam);
return CommonResult.ok();
}
/**
* 运行定时任务
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 7)
@ApiOperation("运行定时任务")
@CommonLog("运行定时任务")
@PostMapping("/dev/job/runJob")
public CommonResult<String> runJob(@RequestBody @Valid DevJobIdParam devJobIdParam) {
devJobService.runJob(devJobIdParam);
return CommonResult.ok();
}
/**
* 立即运行定时任务
*
*
* @date 2021/10/13 14:01
**/
@ApiOperationSupport(order = 8)
@ApiOperation("立即运行定时任务")
@CommonLog("立即运行定时任务")
@PostMapping("/dev/job/runJobNow")
public CommonResult<String> runJobNow(@RequestBody @Valid DevJobIdParam devJobIdParam) {
devJobService.runJobNow(devJobIdParam);
return CommonResult.ok();
}
/**
* 获取定时任务类
*
*
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 9)
@ApiOperation("获取定时任务类")
@GetMapping("/dev/job/getActionClass")
public CommonResult<List<String>> getActionClass() {
return CommonResult.data(devJobService.getActionClass());
}
}

View File

@@ -0,0 +1,64 @@
package mjkf.xinke.dev.modular.job.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import mjkf.xinke.common.pojo.CommonEntity;
/**
* 定时任务实体类
*
*
* @date 2022/8/5 10:38
**/
@Getter
@Setter
@TableName("DEV_JOB")
public class DevJob extends CommonEntity {
/** id */
@ApiModelProperty(value = "id", position = 1)
private String id;
/** 租户id */
@ApiModelProperty(value = "租户id", position = 2)
private String tenantId;
/** 名称 */
@ApiModelProperty(value = "名称", position = 3)
private String name;
/** 编码 */
@ApiModelProperty(value = "编码", position = 4)
private String code;
/** 分类 */
@ApiModelProperty(value = "分类", position = 5)
private String category;
/** 任务类名 */
@ApiModelProperty(value = "任务类名", position = 6)
private String actionClass;
/** cron表达式 */
@ApiModelProperty(value = "cron表达式", position = 7)
private String cronExpression;
/** 任务状态 */
@ApiModelProperty(value = "任务状态", position = 8)
private String jobStatus;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 9)
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 10)
@TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
private String extJson;
}

View File

@@ -0,0 +1,38 @@
package mjkf.xinke.dev.modular.job.enums;
import lombok.Getter;
import mjkf.xinke.common.exception.CommonException;
/**
* 定时任务分类枚举
*
*
* @date 2022/7/6 22:21
*/
@Getter
public enum DevJobCategoryEnum {
/**
* 框架
*/
FRM("FRM"),
/**
* 业务
*/
BIZ("BIZ");
private final String value;
DevJobCategoryEnum(String value) {
this.value = value;
}
public static void validate(String value) {
boolean flag = FRM.getValue().equals(value) || BIZ.getValue().equals(value);
if(!flag) {
throw new CommonException("不支持的定时任务分类:{}", value);
}
}
}

View File

@@ -0,0 +1,38 @@
package mjkf.xinke.dev.modular.job.enums;
import lombok.Getter;
import mjkf.xinke.common.exception.CommonException;
/**
* 定时任务状态枚举
*
*
* @date 2022/4/27 21:47
*/
@Getter
public enum DevJobStatusEnum {
/**
* 运行
*/
RUNNING("RUNNING"),
/**
* 停止
*/
STOPPED("STOPPED");
private final String value;
DevJobStatusEnum(String value) {
this.value = value;
}
public static void validate(String value) {
boolean flag = RUNNING.getValue().equals(value) || STOPPED.getValue().equals(value);
if(!flag) {
throw new CommonException("不支持的定时任务状态:{}", value);
}
}
}

View File

@@ -0,0 +1,15 @@
package mjkf.xinke.dev.modular.job.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import mjkf.xinke.dev.modular.job.entity.DevJob;
/**
* 定时任务Mapper接口
*
*
* @date 2022/8/5 10:45
**/
public interface DevJobMapper extends BaseMapper<DevJob> {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mjkf.xinke.dev.modular.job.mapper.DevJobMapper">
</mapper>

View File

@@ -0,0 +1,49 @@
package mjkf.xinke.dev.modular.job.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 定时任务添加参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevJobAddParam {
/** 名称 */
@ApiModelProperty(value = "名称", required = true, position = 1)
@NotBlank(message = "name不能为空")
private String name;
/** 分类 */
@ApiModelProperty(value = "分类", required = true, position = 2)
@NotBlank(message = "category不能为空")
private String category;
/** 任务类名 */
@ApiModelProperty(value = "任务类名", required = true, position = 3)
@NotBlank(message = "actionClass不能为空")
private String actionClass;
/** cron表达式 */
@ApiModelProperty(value = "cron表达式", required = true, position = 4)
@NotBlank(message = "cronExpression不能为空")
private String cronExpression;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 5)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 6)
private String extJson;
}

View File

@@ -0,0 +1,54 @@
package mjkf.xinke.dev.modular.job.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 定时任务编辑参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevJobEditParam {
/** id */
@ApiModelProperty(value = "id", required = true, position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 名称 */
@ApiModelProperty(value = "名称", required = true, position = 2)
@NotBlank(message = "name不能为空")
private String name;
/** 分类 */
@ApiModelProperty(value = "分类", required = true, position = 3)
@NotBlank(message = "category不能为空")
private String category;
/** 任务类名 */
@ApiModelProperty(value = "任务类名", required = true, position = 4)
@NotBlank(message = "actionClass不能为空")
private String actionClass;
/** cron表达式 */
@ApiModelProperty(value = "cron表达式", required = true, position = 5)
@NotBlank(message = "cronExpression不能为空")
private String cronExpression;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 6)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 7)
private String extJson;
}

View File

@@ -0,0 +1,24 @@
package mjkf.xinke.dev.modular.job.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 定时任务Id参数
*
*
* @date 2022/7/30 17:52
*/
@Getter
@Setter
public class DevJobIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -0,0 +1,29 @@
package mjkf.xinke.dev.modular.job.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 定时任务列表参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevJobListParam {
/** 任务分类 */
@ApiModelProperty(value = "任务分类")
private String category;
/** 名称关键词 */
@ApiModelProperty(value = "名称关键词")
private String searchKey;
/** 任务状态 */
@ApiModelProperty(value = "任务状态")
private String jobStatus;
}

View File

@@ -0,0 +1,45 @@
package mjkf.xinke.dev.modular.job.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 定时任务查询参数
*
*
* @date 2022/7/30 17:53
*/
@Getter
@Setter
public class DevJobPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 任务分类 */
@ApiModelProperty(value = "任务分类")
private String category;
/** 名称关键词 */
@ApiModelProperty(value = "名称关键词")
private String searchKey;
/** 任务状态 */
@ApiModelProperty(value = "任务状态")
private String jobStatus;
}

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