update 补全漏提交文件
This commit is contained in:
parent
c76ab64e8a
commit
fd45cdb882
@ -0,0 +1,14 @@
|
|||||||
|
--- # 监控配置
|
||||||
|
spring:
|
||||||
|
boot:
|
||||||
|
admin:
|
||||||
|
# Spring Boot Admin Client 客户端的相关配置
|
||||||
|
client:
|
||||||
|
# 增加客户端开关
|
||||||
|
enabled: true
|
||||||
|
# 设置 Spring Boot Admin Server 地址
|
||||||
|
url: http://localhost:9090/admin
|
||||||
|
instance:
|
||||||
|
prefer-ip: true # 注册实例时,优先使用 IP
|
||||||
|
username: ruoyi
|
||||||
|
password: 123456
|
@ -0,0 +1,14 @@
|
|||||||
|
--- # 监控配置
|
||||||
|
spring:
|
||||||
|
boot:
|
||||||
|
admin:
|
||||||
|
# Spring Boot Admin Client 客户端的相关配置
|
||||||
|
client:
|
||||||
|
# 增加客户端开关
|
||||||
|
enabled: true
|
||||||
|
# 设置 Spring Boot Admin Server 地址
|
||||||
|
url: http://172.30.0.90:9090/admin
|
||||||
|
instance:
|
||||||
|
prefer-ip: true # 注册实例时,优先使用 IP
|
||||||
|
username: ruoyi
|
||||||
|
password: 123456
|
66
ruoyi-ui/src/plugins/tab.js
Normal file
66
ruoyi-ui/src/plugins/tab.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import store from '@/store'
|
||||||
|
import router from '@/router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// 刷新当前tab页签
|
||||||
|
refreshPage(obj) {
|
||||||
|
const { path, matched } = router.currentRoute;
|
||||||
|
if (obj === undefined) {
|
||||||
|
matched.forEach((m) => {
|
||||||
|
if (m.components && m.components.default && m.components.default.name) {
|
||||||
|
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
||||||
|
obj = { name: m.components.default.name, path: path };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
||||||
|
const { path } = obj
|
||||||
|
router.replace({
|
||||||
|
path: '/redirect' + path
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 关闭当前tab页签,打开新页签
|
||||||
|
closeOpenPage(obj) {
|
||||||
|
store.dispatch("tagsView/delView", router.currentRoute);
|
||||||
|
if (obj !== undefined) {
|
||||||
|
return router.push(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 关闭指定tab页签
|
||||||
|
closePage(obj) {
|
||||||
|
if (obj === undefined) {
|
||||||
|
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
|
||||||
|
return router.push(lastPath || '/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return store.dispatch('tagsView/delView', obj);
|
||||||
|
},
|
||||||
|
// 关闭所有tab页签
|
||||||
|
closeAllPage() {
|
||||||
|
return store.dispatch('tagsView/delAllViews');
|
||||||
|
},
|
||||||
|
// 关闭左侧tab页签
|
||||||
|
closeLeftPage(obj) {
|
||||||
|
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
|
||||||
|
},
|
||||||
|
// 关闭右侧tab页签
|
||||||
|
closeRightPage(obj) {
|
||||||
|
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
|
||||||
|
},
|
||||||
|
// 关闭其他tab页签
|
||||||
|
closeOtherPage(obj) {
|
||||||
|
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
|
||||||
|
},
|
||||||
|
// 添加tab页签
|
||||||
|
openPage(title, url) {
|
||||||
|
var obj = { path: url, meta: { title: title } }
|
||||||
|
store.dispatch('tagsView/addView', obj);
|
||||||
|
return router.push(url);
|
||||||
|
},
|
||||||
|
// 修改tab页签
|
||||||
|
updatePage(obj) {
|
||||||
|
return store.dispatch('tagsView/updateVisitedView', obj);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.common.core.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 参数配置服务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ConfigService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据参数 key 获取参数值
|
||||||
|
*
|
||||||
|
* @param configKey 参数 key
|
||||||
|
* @return 参数值
|
||||||
|
*/
|
||||||
|
String getConfigValue(String configKey);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.ruoyi.common.core.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 字典服务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface DictService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
String SEPARATOR = ",";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型和字典值获取字典标签
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param dictValue 字典值
|
||||||
|
* @return 字典标签
|
||||||
|
*/
|
||||||
|
default String getDictLabel(String dictType, String dictValue) {
|
||||||
|
return getDictLabel(dictType, dictValue, SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型和字典标签获取字典值
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param dictLabel 字典标签
|
||||||
|
* @return 字典值
|
||||||
|
*/
|
||||||
|
default String getDictValue(String dictType, String dictLabel) {
|
||||||
|
return getDictValue(dictType, dictLabel, SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型和字典值获取字典标签
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param dictValue 字典值
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @return 字典标签
|
||||||
|
*/
|
||||||
|
String getDictLabel(String dictType, String dictValue, String separator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型和字典标签获取字典值
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param dictLabel 字典标签
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @return 字典值
|
||||||
|
*/
|
||||||
|
String getDictValue(String dictType, String dictLabel, String separator);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.ruoyi.common.core.service;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 用户业务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户名查询用户
|
||||||
|
*
|
||||||
|
* @param userName 用户名
|
||||||
|
* @return 用户对象信息
|
||||||
|
*/
|
||||||
|
SysUser selectUserByUserName(String userName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户ID查询用户
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户对象信息
|
||||||
|
*/
|
||||||
|
SysUser selectUserById(Long userId);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.excel.context.AnalysisContext;
|
||||||
|
import com.alibaba.excel.event.AnalysisEventListener;
|
||||||
|
import com.alibaba.excel.exception.ExcelAnalysisException;
|
||||||
|
import com.alibaba.excel.exception.ExcelDataConvertException;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.ruoyi.common.utils.ValidatorUtils;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel 导入监听
|
||||||
|
*
|
||||||
|
* @author Yjoioooo
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否Validator检验,默认为是
|
||||||
|
*/
|
||||||
|
private Boolean isValidate = Boolean.TRUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* excel 表头数据
|
||||||
|
*/
|
||||||
|
private Map<Integer, String> headMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入回执
|
||||||
|
*/
|
||||||
|
private ExcelResult<T> excelResult;
|
||||||
|
|
||||||
|
public DefaultExcelListener(boolean isValidate) {
|
||||||
|
this.excelResult = new DefautExcelResult<>();
|
||||||
|
this.isValidate = isValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理异常
|
||||||
|
*
|
||||||
|
* @param exception ExcelDataConvertException
|
||||||
|
* @param context Excel 上下文
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onException(Exception exception, AnalysisContext context) throws Exception {
|
||||||
|
String errMsg = null;
|
||||||
|
if (exception instanceof ExcelDataConvertException) {
|
||||||
|
// 如果是某一个单元格的转换异常 能获取到具体行号
|
||||||
|
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
|
||||||
|
Integer rowIndex = excelDataConvertException.getRowIndex();
|
||||||
|
Integer columnIndex = excelDataConvertException.getColumnIndex();
|
||||||
|
errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
|
||||||
|
rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.error(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception instanceof ConstraintViolationException) {
|
||||||
|
ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
|
||||||
|
Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
|
||||||
|
String constraintViolationsMsg = constraintViolations.stream()
|
||||||
|
.map(ConstraintViolation::getMessage)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.error(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
excelResult.getErrorList().add(errMsg);
|
||||||
|
throw new ExcelAnalysisException(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
|
||||||
|
this.headMap = headMap;
|
||||||
|
log.debug("解析到一条表头数据: {}", JSON.toJSONString(headMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(T data, AnalysisContext context) {
|
||||||
|
if (isValidate) {
|
||||||
|
ValidatorUtils.validate(data);
|
||||||
|
}
|
||||||
|
excelResult.getList().add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||||
|
log.debug("所有数据解析完成!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExcelResult<T> getExcelResult() {
|
||||||
|
return excelResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认excel返回对象
|
||||||
|
*
|
||||||
|
* @author Yjoioooo
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class DefautExcelResult<T> implements ExcelResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据对象list
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息列表
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private List<String> errorList;
|
||||||
|
|
||||||
|
public DefautExcelResult() {
|
||||||
|
this.list = new ArrayList<>();
|
||||||
|
this.errorList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefautExcelResult(List<T> list, List<String> errorList) {
|
||||||
|
this.list = list;
|
||||||
|
this.errorList = errorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefautExcelResult(ExcelResult<T> excelResult) {
|
||||||
|
this.list = excelResult.getList();
|
||||||
|
this.errorList = excelResult.getErrorList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getErrorList() {
|
||||||
|
return errorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导入回执
|
||||||
|
*
|
||||||
|
* @return 导入回执
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAnalysis() {
|
||||||
|
int successCount = list.size();
|
||||||
|
int errorCount = errorList.size();
|
||||||
|
if (successCount == 0) {
|
||||||
|
return "读取失败,未解析到数据";
|
||||||
|
} else {
|
||||||
|
if (errorCount == 0) {
|
||||||
|
return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import com.alibaba.excel.read.listener.ReadListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel 导入监听
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ExcelListener<T> extends ReadListener<T> {
|
||||||
|
|
||||||
|
ExcelResult<T> getExcelResult();
|
||||||
|
|
||||||
|
}
|
26
ruoyi/src/main/java/com/ruoyi/common/excel/ExcelResult.java
Normal file
26
ruoyi/src/main/java/com/ruoyi/common/excel/ExcelResult.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* excel返回对象
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ExcelResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象列表
|
||||||
|
*/
|
||||||
|
List<T> getList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误列表
|
||||||
|
*/
|
||||||
|
List<String> getErrorList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入回执
|
||||||
|
*/
|
||||||
|
String getAnalysis();
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.ruoyi.common.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.tree.Tree;
|
||||||
|
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||||
|
import cn.hutool.core.lang.tree.TreeUtil;
|
||||||
|
import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展 hutool TreeUtil 封装系统树构建
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class TreeBuildUtils extends TreeUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据前端定制差异化字段
|
||||||
|
*/
|
||||||
|
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认树父节点id
|
||||||
|
*/
|
||||||
|
public static final Long DEFAULT_PARENT_ID = 0L;
|
||||||
|
|
||||||
|
public static <T> List<Tree<Long>> build(List<T> list, NodeParser<T, Long> nodeParser) {
|
||||||
|
return TreeUtil.build(list, DEFAULT_PARENT_ID, DEFAULT_CONFIG, nodeParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.common.utils;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import javax.validation.Validation;
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator 校验框架工具
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class ValidatorUtils {
|
||||||
|
|
||||||
|
private static final Validator VALID = Validation.buildDefaultValidatorFactory().getValidator();
|
||||||
|
|
||||||
|
public static <T> void validate(T object, Class<?>... groups) {
|
||||||
|
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
|
||||||
|
if (!validate.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException("参数校验异常", validate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.ruoyi.demo.domain.bo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试单表业务对象 test_demo
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2021-07-26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("测试单表业务对象")
|
||||||
|
public class TestDemoImportVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门id
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("部门id")
|
||||||
|
@NotNull(message = "部门id不能为空")
|
||||||
|
@ExcelProperty(value = "部门id")
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("用户id")
|
||||||
|
@NotNull(message = "用户id不能为空")
|
||||||
|
@ExcelProperty(value = "用户id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序号
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("排序号")
|
||||||
|
@NotNull(message = "排序号不能为空")
|
||||||
|
@ExcelProperty(value = "排序号")
|
||||||
|
private Long orderNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key键
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("key键")
|
||||||
|
@NotBlank(message = "key键不能为空")
|
||||||
|
@ExcelProperty(value = "key键")
|
||||||
|
private String testKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("值")
|
||||||
|
@NotBlank(message = "值不能为空")
|
||||||
|
@ExcelProperty(value = "值")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
}
|
38
ruoyi/src/main/java/com/ruoyi/oss/constant/OssConstant.java
Normal file
38
ruoyi/src/main/java/com/ruoyi/oss/constant/OssConstant.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.ruoyi.oss.constant;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象存储常量
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class OssConstant {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS模块KEY
|
||||||
|
*/
|
||||||
|
public static final String SYS_OSS_KEY = "sys_oss:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象存储配置KEY
|
||||||
|
*/
|
||||||
|
public static final String OSS_CONFIG_KEY = "OssConfig";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存配置KEY
|
||||||
|
*/
|
||||||
|
public static final String CACHE_CONFIG_KEY = SYS_OSS_KEY + OSS_CONFIG_KEY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览列表资源开关Key
|
||||||
|
*/
|
||||||
|
public static final String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统数据ids
|
||||||
|
*/
|
||||||
|
public static final List<Integer> SYSTEM_DATA_IDS = Arrays.asList(1, 2, 3, 4);
|
||||||
|
|
||||||
|
}
|
63
ruoyi/src/main/java/com/ruoyi/oss/enumd/OssEnumd.java
Normal file
63
ruoyi/src/main/java/com/ruoyi/oss/enumd/OssEnumd.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package com.ruoyi.oss.enumd;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.oss.service.impl.AliyunOssStrategy;
|
||||||
|
import com.ruoyi.oss.service.impl.MinioOssStrategy;
|
||||||
|
import com.ruoyi.oss.service.impl.QcloudOssStrategy;
|
||||||
|
import com.ruoyi.oss.service.impl.QiniuOssStrategy;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象存储服务商枚举
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum OssEnumd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 七牛云
|
||||||
|
*/
|
||||||
|
QINIU("qiniu", QiniuOssStrategy.class),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云
|
||||||
|
*/
|
||||||
|
ALIYUN("aliyun", AliyunOssStrategy.class),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯云
|
||||||
|
*/
|
||||||
|
QCLOUD("qcloud", QcloudOssStrategy.class),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minio
|
||||||
|
*/
|
||||||
|
MINIO("minio", MinioOssStrategy.class);
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
private final Class<?> serviceClass;
|
||||||
|
|
||||||
|
public static Class<?> getServiceClass(String value) {
|
||||||
|
for (OssEnumd clazz : values()) {
|
||||||
|
if (clazz.getValue().equals(value)) {
|
||||||
|
return clazz.getServiceClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServiceName(String value) {
|
||||||
|
for (OssEnumd clazz : values()) {
|
||||||
|
if (clazz.getValue().equals(value)) {
|
||||||
|
return StringUtils.uncapitalize(clazz.getServiceClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.ruoyi.oss.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS对象存储 配置属性
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OssProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 域名
|
||||||
|
*/
|
||||||
|
private String endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前缀
|
||||||
|
*/
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACCESS_KEY
|
||||||
|
*/
|
||||||
|
private String accessKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECRET_KEY
|
||||||
|
*/
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储空间名
|
||||||
|
*/
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储区域
|
||||||
|
*/
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否https(Y=是,N=否)
|
||||||
|
*/
|
||||||
|
private String isHttps;
|
||||||
|
|
||||||
|
}
|
64
ruoyi/src/main/java/com/ruoyi/oss/service/IOssStrategy.java
Normal file
64
ruoyi/src/main/java/com/ruoyi/oss/service/IOssStrategy.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package com.ruoyi.oss.service;
|
||||||
|
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象存储策略
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface IOssStrategy {
|
||||||
|
|
||||||
|
void createBucket();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务商类型
|
||||||
|
*/
|
||||||
|
String getServiceType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*
|
||||||
|
* @param data 文件字节数组
|
||||||
|
* @param path 文件路径,包含文件名
|
||||||
|
* @return 返回http地址
|
||||||
|
*/
|
||||||
|
UploadResult upload(byte[] data, String path, String contentType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件删除
|
||||||
|
*
|
||||||
|
* @param path 文件路径,包含文件名
|
||||||
|
*/
|
||||||
|
void delete(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*
|
||||||
|
* @param data 文件字节数组
|
||||||
|
* @param suffix 后缀
|
||||||
|
* @return 返回http地址
|
||||||
|
*/
|
||||||
|
UploadResult uploadSuffix(byte[] data, String suffix, String contentType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*
|
||||||
|
* @param inputStream 字节流
|
||||||
|
* @param path 文件路径,包含文件名
|
||||||
|
* @return 返回http地址
|
||||||
|
*/
|
||||||
|
UploadResult upload(InputStream inputStream, String path, String contentType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*
|
||||||
|
* @param inputStream 字节流
|
||||||
|
* @param suffix 后缀
|
||||||
|
* @return 返回http地址
|
||||||
|
*/
|
||||||
|
UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.ruoyi.oss.service.abstractd;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
import com.ruoyi.oss.service.IOssStrategy;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象存储策略(支持七牛、阿里云、腾讯云、minio)
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public abstract class AbstractOssStrategy implements IOssStrategy {
|
||||||
|
|
||||||
|
protected OssProperties properties;
|
||||||
|
|
||||||
|
public abstract void init(OssProperties properties);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void createBucket();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getServiceType();
|
||||||
|
|
||||||
|
public String getPath(String prefix, String suffix) {
|
||||||
|
// 生成uuid
|
||||||
|
String uuid = IdUtil.fastSimpleUUID();
|
||||||
|
// 文件路径
|
||||||
|
String path = DateUtils.datePath() + "/" + uuid;
|
||||||
|
if (StringUtils.isNotBlank(prefix)) {
|
||||||
|
path = prefix + "/" + path;
|
||||||
|
}
|
||||||
|
return path + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract UploadResult upload(byte[] data, String path, String contentType);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void delete(String path);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(InputStream inputStream, String path, String contentType) {
|
||||||
|
byte[] data = IoUtil.readBytes(inputStream);
|
||||||
|
return this.upload(data, path, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract UploadResult uploadSuffix(byte[] data, String suffix, String contentType);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);
|
||||||
|
|
||||||
|
public abstract String getEndpointLink();
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package com.ruoyi.oss.service.impl;
|
||||||
|
|
||||||
|
import com.aliyun.oss.ClientConfiguration;
|
||||||
|
import com.aliyun.oss.OSSClient;
|
||||||
|
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
|
||||||
|
import com.aliyun.oss.model.CannedAccessControlList;
|
||||||
|
import com.aliyun.oss.model.CreateBucketRequest;
|
||||||
|
import com.aliyun.oss.model.ObjectMetadata;
|
||||||
|
import com.aliyun.oss.model.PutObjectRequest;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
import com.ruoyi.oss.enumd.OssEnumd;
|
||||||
|
import com.ruoyi.oss.exception.OssException;
|
||||||
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云存储策略
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class AliyunOssStrategy extends AbstractOssStrategy {
|
||||||
|
|
||||||
|
private OSSClient client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OssProperties cloudStorageProperties) {
|
||||||
|
properties = cloudStorageProperties;
|
||||||
|
try {
|
||||||
|
ClientConfiguration configuration = new ClientConfiguration();
|
||||||
|
DefaultCredentialProvider credentialProvider = new DefaultCredentialProvider(
|
||||||
|
properties.getAccessKey(), properties.getSecretKey());
|
||||||
|
client = new OSSClient(properties.getEndpoint(), credentialProvider, configuration);
|
||||||
|
createBucket();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("阿里云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createBucket() {
|
||||||
|
try {
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
|
if (client.doesBucketExist(bucketName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
|
||||||
|
createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
|
||||||
|
client.createBucket(createBucketRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("创建Bucket失败, 请核对阿里云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return OssEnumd.ALIYUN.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(byte[] data, String path, String contentType) {
|
||||||
|
return upload(new ByteArrayInputStream(data), path, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(InputStream inputStream, String path, String contentType) {
|
||||||
|
try {
|
||||||
|
ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
metadata.setContentType(contentType);
|
||||||
|
client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
return new UploadResult().setUrl(getEndpointLink() + "/" + path).setFilename(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String path) {
|
||||||
|
path = path.replace(getEndpointLink() + "/", "");
|
||||||
|
try {
|
||||||
|
client.deleteObject(properties.getBucketName(), path);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
|
||||||
|
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
||||||
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEndpointLink() {
|
||||||
|
String endpoint = properties.getEndpoint();
|
||||||
|
StringBuilder sb = new StringBuilder(endpoint);
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
|
||||||
|
sb.insert(7, properties.getBucketName() + ".");
|
||||||
|
} else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
|
||||||
|
sb.insert(8, properties.getBucketName() + ".");
|
||||||
|
} else {
|
||||||
|
throw new OssException("Endpoint配置错误");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
package com.ruoyi.oss.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
import com.ruoyi.oss.enumd.OssEnumd;
|
||||||
|
import com.ruoyi.oss.enumd.PolicyType;
|
||||||
|
import com.ruoyi.oss.exception.OssException;
|
||||||
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
|
||||||
|
import io.minio.*;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minio存储策略
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class MinioOssStrategy extends AbstractOssStrategy {
|
||||||
|
|
||||||
|
private MinioClient minioClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OssProperties cloudStorageProperties) {
|
||||||
|
properties = cloudStorageProperties;
|
||||||
|
try {
|
||||||
|
minioClient = MinioClient.builder()
|
||||||
|
.endpoint(properties.getEndpoint())
|
||||||
|
.credentials(properties.getAccessKey(), properties.getSecretKey())
|
||||||
|
.build();
|
||||||
|
createBucket();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("Minio存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createBucket() {
|
||||||
|
try {
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
|
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||||
|
if (exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 不存在就创建桶
|
||||||
|
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||||
|
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
|
||||||
|
.bucket(bucketName)
|
||||||
|
.config(getPolicy(bucketName, PolicyType.READ))
|
||||||
|
.build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("创建Bucket失败, 请核对Minio配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return OssEnumd.MINIO.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(byte[] data, String path, String contentType) {
|
||||||
|
return upload(new ByteArrayInputStream(data), path, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(InputStream inputStream, String path, String contentType) {
|
||||||
|
try {
|
||||||
|
minioClient.putObject(PutObjectArgs.builder()
|
||||||
|
.bucket(properties.getBucketName())
|
||||||
|
.object(path)
|
||||||
|
.contentType(StringUtils.blankToDefault(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE))
|
||||||
|
.stream(inputStream, inputStream.available(), -1)
|
||||||
|
.build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请核对Minio配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
return new UploadResult().setUrl(getEndpointLink() + "/" + path).setFilename(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String path) {
|
||||||
|
path = path.replace(getEndpointLink() + "/", "");
|
||||||
|
try {
|
||||||
|
minioClient.removeObject(RemoveObjectArgs.builder()
|
||||||
|
.bucket(properties.getBucketName())
|
||||||
|
.object(path)
|
||||||
|
.build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
|
||||||
|
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
||||||
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEndpointLink() {
|
||||||
|
return properties.getEndpoint() + "/" + properties.getBucketName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPolicy(String bucketName, PolicyType policyType) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("{\n");
|
||||||
|
builder.append(" \"Statement\": [\n");
|
||||||
|
builder.append(" {\n");
|
||||||
|
builder.append(" \"Action\": [\n");
|
||||||
|
if (policyType == PolicyType.WRITE) {
|
||||||
|
builder.append(" \"s3:GetBucketLocation\",\n");
|
||||||
|
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
|
||||||
|
} else if (policyType == PolicyType.READ_WRITE) {
|
||||||
|
builder.append(" \"s3:GetBucketLocation\",\n");
|
||||||
|
builder.append(" \"s3:ListBucket\",\n");
|
||||||
|
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
|
||||||
|
} else {
|
||||||
|
builder.append(" \"s3:GetBucketLocation\"\n");
|
||||||
|
}
|
||||||
|
builder.append(" ],\n");
|
||||||
|
builder.append(" \"Effect\": \"Allow\",\n");
|
||||||
|
builder.append(" \"Principal\": \"*\",\n");
|
||||||
|
builder.append(" \"Resource\": \"arn:aws:s3:::");
|
||||||
|
builder.append(bucketName);
|
||||||
|
builder.append("\"\n");
|
||||||
|
builder.append(" },\n");
|
||||||
|
if (PolicyType.READ.equals(policyType)) {
|
||||||
|
builder.append(" {\n");
|
||||||
|
builder.append(" \"Action\": [\n");
|
||||||
|
builder.append(" \"s3:ListBucket\"\n");
|
||||||
|
builder.append(" ],\n");
|
||||||
|
builder.append(" \"Effect\": \"Deny\",\n");
|
||||||
|
builder.append(" \"Principal\": \"*\",\n");
|
||||||
|
builder.append(" \"Resource\": \"arn:aws:s3:::");
|
||||||
|
builder.append(bucketName);
|
||||||
|
builder.append("\"\n");
|
||||||
|
builder.append(" },\n");
|
||||||
|
}
|
||||||
|
builder.append(" {\n");
|
||||||
|
builder.append(" \"Action\": ");
|
||||||
|
switch (policyType) {
|
||||||
|
case WRITE:
|
||||||
|
builder.append("[\n");
|
||||||
|
builder.append(" \"s3:AbortMultipartUpload\",\n");
|
||||||
|
builder.append(" \"s3:DeleteObject\",\n");
|
||||||
|
builder.append(" \"s3:ListMultipartUploadParts\",\n");
|
||||||
|
builder.append(" \"s3:PutObject\"\n");
|
||||||
|
builder.append(" ],\n");
|
||||||
|
break;
|
||||||
|
case READ_WRITE:
|
||||||
|
builder.append("[\n");
|
||||||
|
builder.append(" \"s3:AbortMultipartUpload\",\n");
|
||||||
|
builder.append(" \"s3:DeleteObject\",\n");
|
||||||
|
builder.append(" \"s3:GetObject\",\n");
|
||||||
|
builder.append(" \"s3:ListMultipartUploadParts\",\n");
|
||||||
|
builder.append(" \"s3:PutObject\"\n");
|
||||||
|
builder.append(" ],\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
builder.append("\"s3:GetObject\",\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
builder.append(" \"Effect\": \"Allow\",\n");
|
||||||
|
builder.append(" \"Principal\": \"*\",\n");
|
||||||
|
builder.append(" \"Resource\": \"arn:aws:s3:::");
|
||||||
|
builder.append(bucketName);
|
||||||
|
builder.append("/*\"\n");
|
||||||
|
builder.append(" }\n");
|
||||||
|
builder.append(" ],\n");
|
||||||
|
builder.append(" \"Version\": \"2012-10-17\"\n");
|
||||||
|
builder.append("}\n");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package com.ruoyi.oss.service.impl;
|
||||||
|
|
||||||
|
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.http.HttpProtocol;
|
||||||
|
import com.qcloud.cos.model.*;
|
||||||
|
import com.qcloud.cos.region.Region;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
import com.ruoyi.oss.enumd.OssEnumd;
|
||||||
|
import com.ruoyi.oss.exception.OssException;
|
||||||
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 腾讯云存储策略
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class QcloudOssStrategy extends AbstractOssStrategy {
|
||||||
|
|
||||||
|
private COSClient client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OssProperties cloudStorageProperties) {
|
||||||
|
properties = cloudStorageProperties;
|
||||||
|
try {
|
||||||
|
COSCredentials credentials = new BasicCOSCredentials(
|
||||||
|
properties.getAccessKey(), properties.getSecretKey());
|
||||||
|
// 初始化客户端配置
|
||||||
|
ClientConfig clientConfig = new ClientConfig();
|
||||||
|
// 设置bucket所在的区域,华南:gz 华北:tj 华东:sh
|
||||||
|
clientConfig.setRegion(new Region(properties.getRegion()));
|
||||||
|
if ("Y".equals(properties.getIsHttps())) {
|
||||||
|
clientConfig.setHttpProtocol(HttpProtocol.https);
|
||||||
|
} else {
|
||||||
|
clientConfig.setHttpProtocol(HttpProtocol.http);
|
||||||
|
}
|
||||||
|
client = new COSClient(credentials, clientConfig);
|
||||||
|
createBucket();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("腾讯云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createBucket() {
|
||||||
|
try {
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
|
if (client.doesBucketExist(bucketName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
|
||||||
|
createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
|
||||||
|
client.createBucket(createBucketRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("创建Bucket失败, 请核对腾讯云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return OssEnumd.QCLOUD.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(byte[] data, String path, String contentType) {
|
||||||
|
return upload(new ByteArrayInputStream(data), path, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(InputStream inputStream, String path, String contentType) {
|
||||||
|
try {
|
||||||
|
ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
metadata.setContentType(contentType);
|
||||||
|
client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请检查腾讯云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
return new UploadResult().setUrl(getEndpointLink() + "/" + path).setFilename(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String path) {
|
||||||
|
path = path.replace(getEndpointLink() + "/", "");
|
||||||
|
try {
|
||||||
|
client.deleteObject(new DeleteObjectRequest(properties.getBucketName(), path));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请检腾讯云查配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
|
||||||
|
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
||||||
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEndpointLink() {
|
||||||
|
String endpoint = properties.getEndpoint();
|
||||||
|
StringBuilder sb = new StringBuilder(endpoint);
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
|
||||||
|
sb.insert(7, properties.getBucketName() + ".");
|
||||||
|
} else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
|
||||||
|
sb.insert(8, properties.getBucketName() + ".");
|
||||||
|
} else {
|
||||||
|
throw new OssException("Endpoint配置错误");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package com.ruoyi.oss.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import com.qiniu.http.Response;
|
||||||
|
import com.qiniu.storage.BucketManager;
|
||||||
|
import com.qiniu.storage.Configuration;
|
||||||
|
import com.qiniu.storage.Region;
|
||||||
|
import com.qiniu.storage.UploadManager;
|
||||||
|
import com.qiniu.util.Auth;
|
||||||
|
import com.ruoyi.oss.entity.UploadResult;
|
||||||
|
import com.ruoyi.oss.enumd.OssEnumd;
|
||||||
|
import com.ruoyi.oss.exception.OssException;
|
||||||
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 七牛云存储策略
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class QiniuOssStrategy extends AbstractOssStrategy {
|
||||||
|
|
||||||
|
private UploadManager uploadManager;
|
||||||
|
private BucketManager bucketManager;
|
||||||
|
private Auth auth;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(OssProperties cloudStorageProperties) {
|
||||||
|
properties = cloudStorageProperties;
|
||||||
|
try {
|
||||||
|
Configuration config = new Configuration(getRegion(properties.getRegion()));
|
||||||
|
// https设置
|
||||||
|
config.useHttpsDomains = false;
|
||||||
|
config.useHttpsDomains = "Y".equals(properties.getIsHttps());
|
||||||
|
uploadManager = new UploadManager(config);
|
||||||
|
auth = Auth.create(properties.getAccessKey(), properties.getSecretKey());
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
|
bucketManager = new BucketManager(auth, config);
|
||||||
|
|
||||||
|
if (!ArrayUtil.contains(bucketManager.buckets(), bucketName)) {
|
||||||
|
bucketManager.createBucket(bucketName, properties.getRegion());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("七牛云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createBucket() {
|
||||||
|
try {
|
||||||
|
String bucketName = properties.getBucketName();
|
||||||
|
if (ArrayUtil.contains(bucketManager.buckets(), bucketName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bucketManager.createBucket(bucketName, properties.getRegion());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("创建Bucket失败, 请核对七牛云配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return OssEnumd.QINIU.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult upload(byte[] data, String path, String contentType) {
|
||||||
|
try {
|
||||||
|
String token = auth.uploadToken(properties.getBucketName());
|
||||||
|
Response res = uploadManager.put(data, path, token, null, contentType, false);
|
||||||
|
if (!res.isOK()) {
|
||||||
|
throw new RuntimeException("上传七牛出错:" + res.error);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请核对七牛配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
return new UploadResult().setUrl(getEndpointLink() + "/" + path).setFilename(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String path) {
|
||||||
|
try {
|
||||||
|
path = path.replace(getEndpointLink() + "/", "");
|
||||||
|
Response res = bucketManager.delete(properties.getBucketName(), path);
|
||||||
|
if (!res.isOK()) {
|
||||||
|
throw new RuntimeException("删除七牛文件出错:" + res.error);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
|
||||||
|
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
|
||||||
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEndpointLink() {
|
||||||
|
return properties.getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Region getRegion(String region) {
|
||||||
|
switch (region) {
|
||||||
|
case "z0":
|
||||||
|
return Region.region0();
|
||||||
|
case "z1":
|
||||||
|
return Region.region1();
|
||||||
|
case "z2":
|
||||||
|
return Region.region2();
|
||||||
|
case "na0":
|
||||||
|
return Region.regionNa0();
|
||||||
|
case "as0":
|
||||||
|
return Region.regionAs0();
|
||||||
|
default:
|
||||||
|
return Region.autoRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package com.ruoyi.system.listener;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.alibaba.excel.context.AnalysisContext;
|
||||||
|
import com.alibaba.excel.event.AnalysisEventListener;
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import com.ruoyi.common.excel.ExcelListener;
|
||||||
|
import com.ruoyi.common.excel.ExcelResult;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||||
|
import com.ruoyi.system.domain.vo.SysUserImportVo;
|
||||||
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统用户自定义导入
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo> implements ExcelListener<SysUserImportVo> {
|
||||||
|
|
||||||
|
private final ISysUserService userService;
|
||||||
|
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
private final Boolean isUpdateSupport;
|
||||||
|
|
||||||
|
private final String operName;
|
||||||
|
|
||||||
|
private int successNum = 0;
|
||||||
|
private int failureNum = 0;
|
||||||
|
private final StringBuilder successMsg = new StringBuilder();
|
||||||
|
private final StringBuilder failureMsg = new StringBuilder();
|
||||||
|
|
||||||
|
public SysUserImportListener(Boolean isUpdateSupport) {
|
||||||
|
this.userService = SpringUtils.getBean(ISysUserService.class);
|
||||||
|
this.password = SpringUtils.getBean(ISysConfigService.class)
|
||||||
|
.selectConfigByKey("sys.user.initPassword");
|
||||||
|
this.isUpdateSupport = isUpdateSupport;
|
||||||
|
this.operName = SecurityUtils.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(SysUserImportVo userVo, AnalysisContext context) {
|
||||||
|
SysUser user = this.userService.selectUserByUserName(userVo.getUserName());
|
||||||
|
try {
|
||||||
|
// 验证是否存在这个用户
|
||||||
|
if (StringUtils.isNull(user)) {
|
||||||
|
user = BeanUtil.toBean(userVo, SysUser.class);
|
||||||
|
user.setPassword(SecurityUtils.encryptPassword(password));
|
||||||
|
user.setCreateBy(operName);
|
||||||
|
userService.insertUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功");
|
||||||
|
} else if (isUpdateSupport) {
|
||||||
|
user.setUpdateBy(operName);
|
||||||
|
userService.updateUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功");
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(user.getUserName()).append(" 已存在");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
|
||||||
|
failureMsg.append(msg).append(e.getMessage());
|
||||||
|
log.error(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExcelResult<SysUserImportVo> getExcelResult() {
|
||||||
|
return new ExcelResult<SysUserImportVo>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAnalysis() {
|
||||||
|
if (failureNum > 0) {
|
||||||
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||||
|
throw new ServiceException(failureMsg.toString());
|
||||||
|
} else {
|
||||||
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||||
|
}
|
||||||
|
return successMsg.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SysUserImportVo> getList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getErrorList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.ruoyi.system.runner;
|
||||||
|
|
||||||
|
import com.ruoyi.common.config.RuoYiConfig;
|
||||||
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
|
import com.ruoyi.system.service.ISysDictTypeService;
|
||||||
|
import com.ruoyi.system.service.ISysOssConfigService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 system 模块对应业务数据
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||||
|
@Component
|
||||||
|
public class SystemApplicationRunner implements ApplicationRunner {
|
||||||
|
|
||||||
|
private final RuoYiConfig ruoyiConfig;
|
||||||
|
private final ISysConfigService configService;
|
||||||
|
private final ISysDictTypeService dictTypeService;
|
||||||
|
private final ISysOssConfigService ossConfigService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
|
ossConfigService.init();
|
||||||
|
log.info("初始化OSS配置成功");
|
||||||
|
if (ruoyiConfig.isCacheLazy()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
configService.loadingConfigCache();
|
||||||
|
log.info("加载参数缓存数据成功");
|
||||||
|
dictTypeService.loadingDictCache();
|
||||||
|
log.info("加载字典缓存数据成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -180,7 +180,6 @@ public class SysUserServiceImpl extends ServicePlusImpl<SysUserMapper, SysUser,
|
|||||||
public String checkPhoneUnique(SysUser user) {
|
public String checkPhoneUnique(SysUser user) {
|
||||||
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
||||||
long count = count(new LambdaQueryWrapper<SysUser>()
|
long count = count(new LambdaQueryWrapper<SysUser>()
|
||||||
.select(SysUser::getUserId, SysUser::getPhonenumber)
|
|
||||||
.eq(SysUser::getPhonenumber, user.getPhonenumber())
|
.eq(SysUser::getPhonenumber, user.getPhonenumber())
|
||||||
.ne(SysUser::getUserId, userId));
|
.ne(SysUser::getUserId, userId));
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
@ -199,7 +198,6 @@ public class SysUserServiceImpl extends ServicePlusImpl<SysUserMapper, SysUser,
|
|||||||
public String checkEmailUnique(SysUser user) {
|
public String checkEmailUnique(SysUser user) {
|
||||||
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
||||||
long count = count(new LambdaQueryWrapper<SysUser>()
|
long count = count(new LambdaQueryWrapper<SysUser>()
|
||||||
.select(SysUser::getUserId, SysUser::getEmail)
|
|
||||||
.eq(SysUser::getEmail, user.getEmail())
|
.eq(SysUser::getEmail, user.getEmail())
|
||||||
.ne(SysUser::getUserId, userId));
|
.ne(SysUser::getUserId, userId));
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
|
3
ruoyi/src/main/resources/mapper/package-info.md
Normal file
3
ruoyi/src/main/resources/mapper/package-info.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
java包使用 `.` 分割 resource 目录使用 `/` 分割
|
||||||
|
<br>
|
||||||
|
此文件目的 防止文件夹粘连找不到 `xml` 文件
|
@ -644,10 +644,10 @@ create table gen_table_column (
|
|||||||
drop table if exists sys_oss;
|
drop table if exists sys_oss;
|
||||||
create table sys_oss (
|
create table sys_oss (
|
||||||
oss_id bigint(20) not null auto_increment comment '对象存储主键',
|
oss_id bigint(20) not null auto_increment comment '对象存储主键',
|
||||||
file_name varchar(64) not null default '' comment '文件名',
|
file_name varchar(255) not null default '' comment '文件名',
|
||||||
original_name varchar(64) not null default '' comment '原名',
|
original_name varchar(255) not null default '' comment '原名',
|
||||||
file_suffix varchar(10) not null default '' comment '文件后缀名',
|
file_suffix varchar(10) not null default '' comment '文件后缀名',
|
||||||
url varchar(200) not null comment 'URL地址',
|
url varchar(500) not null comment 'URL地址',
|
||||||
create_time datetime default null comment '创建时间',
|
create_time datetime default null comment '创建时间',
|
||||||
create_by varchar(64) default '' comment '上传人',
|
create_by varchar(64) default '' comment '上传人',
|
||||||
update_time datetime default null comment '更新时间',
|
update_time datetime default null comment '更新时间',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user