Compare commits
2 Commits
master
...
dev-featur
Author | SHA1 | Date |
---|---|---|
|
6bd0b2f0e0 | |
|
b62b7c75f5 |
|
@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.servlet.ServletUtil;
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
@ -13,6 +14,8 @@ import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -59,6 +62,28 @@ public class ServletUtils {
|
||||||
return ua != null ? ua : "";
|
return ua != null ? ua : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getHeader(HttpServletRequest request, String name){
|
||||||
|
String value = request.getHeader(name);
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
return StringUtils.EMPTY;
|
||||||
|
}
|
||||||
|
return urlDecode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内容解码
|
||||||
|
*
|
||||||
|
* @param str 内容
|
||||||
|
* @return 解码后的内容
|
||||||
|
*/
|
||||||
|
public static String urlDecode(String str) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(str,"UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得请求
|
* 获得请求
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package cn.iocoder.yudao.framework.mybatis.core.dataobject;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity基类
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BaseEntity implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索值
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String searchValue;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 创建部门
|
||||||
|
// */
|
||||||
|
// @TableField(fill = FieldFill.INSERT)
|
||||||
|
// private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Long createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private Long updateBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数
|
||||||
|
*/
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Map<String, Object> params = new HashMap<>();
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseEntity;
|
||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
@ -41,6 +42,28 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||||
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
|
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {
|
||||||
baseDO.setUpdater(userId.toString());
|
baseDO.setUpdater(userId.toString());
|
||||||
}
|
}
|
||||||
|
}else if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) {
|
||||||
|
BaseEntity baseEntity = (BaseEntity) metaObject.getOriginalObject();
|
||||||
|
|
||||||
|
LocalDateTime current = LocalDateTime.now();
|
||||||
|
// 创建时间为空,则以当前时间为插入时间
|
||||||
|
if (Objects.isNull(baseEntity.getCreateTime())) {
|
||||||
|
baseEntity.setCreateTime(current);
|
||||||
|
}
|
||||||
|
// 更新时间为空,则以当前时间为更新时间
|
||||||
|
if (Objects.isNull(baseEntity.getUpdateTime())) {
|
||||||
|
baseEntity.setUpdateTime(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = WebFrameworkUtils.getLoginUserId();
|
||||||
|
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
|
||||||
|
if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getCreateBy())) {
|
||||||
|
baseEntity.setCreateBy(userId);
|
||||||
|
}
|
||||||
|
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||||
|
if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getUpdateBy())) {
|
||||||
|
baseEntity.setUpdateBy(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,12 +74,20 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||||
if (Objects.isNull(modifyTime)) {
|
if (Objects.isNull(modifyTime)) {
|
||||||
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
|
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
|
||||||
}
|
}
|
||||||
|
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
|
||||||
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||||
Object modifier = getFieldValByName("updater", metaObject);
|
Object modifier = getFieldValByName("updater", metaObject);
|
||||||
Long userId = WebFrameworkUtils.getLoginUserId();
|
Long userId = WebFrameworkUtils.getLoginUserId();
|
||||||
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
||||||
setFieldValByName("updater", userId.toString(), metaObject);
|
setFieldValByName("updater", userId.toString(), metaObject);
|
||||||
}
|
}
|
||||||
|
}else if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) {
|
||||||
|
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||||
|
Object modifier = getFieldValByName("updateBy", metaObject);
|
||||||
|
Long userId = WebFrameworkUtils.getLoginUserId();
|
||||||
|
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
||||||
|
setFieldValByName("updateBy", userId, metaObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
package cn.iocoder.yudao.framework.mybatis.core.page;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询实体类
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PageQuery implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页大小
|
||||||
|
*/
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前页数
|
||||||
|
*/
|
||||||
|
private Integer pageNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序列
|
||||||
|
*/
|
||||||
|
private String orderByColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序的方向desc或者asc
|
||||||
|
*/
|
||||||
|
private String isAsc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前记录起始索引 默认值
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_NUM = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每页显示记录数 默认值 默认查全部
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
|
||||||
|
*/
|
||||||
|
public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
|
||||||
|
|
||||||
|
public static final String SEPARATOR = ",";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建分页对象
|
||||||
|
*/
|
||||||
|
public <T> Page<T> build() {
|
||||||
|
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
|
||||||
|
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
|
||||||
|
if (pageNum <= 0) {
|
||||||
|
pageNum = DEFAULT_PAGE_NUM;
|
||||||
|
}
|
||||||
|
Page<T> page = new Page<>(pageNum, pageSize);
|
||||||
|
List<OrderItem> orderItems = buildOrderItem();
|
||||||
|
if (CollUtil.isNotEmpty(orderItems)) {
|
||||||
|
page.addOrder(orderItems);
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建排序
|
||||||
|
*
|
||||||
|
* 支持的用法如下:
|
||||||
|
* {isAsc:"asc",orderByColumn:"id"} order by id asc
|
||||||
|
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
|
||||||
|
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
|
||||||
|
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
|
||||||
|
*/
|
||||||
|
private List<OrderItem> buildOrderItem() {
|
||||||
|
if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String orderBy = escapeOrderBySql(orderByColumn);
|
||||||
|
orderBy = StrUtil.toUnderlineCase(orderBy);
|
||||||
|
|
||||||
|
// 兼容前端排序类型
|
||||||
|
isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
|
||||||
|
|
||||||
|
String[] orderByArr = orderBy.split(SEPARATOR);
|
||||||
|
String[] isAscArr = isAsc.split(SEPARATOR);
|
||||||
|
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
|
||||||
|
throw new ServiceException(500,"排序参数有误");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OrderItem> list = new ArrayList<>();
|
||||||
|
// 每个字段各自排序
|
||||||
|
for (int i = 0; i < orderByArr.length; i++) {
|
||||||
|
String orderByStr = orderByArr[i];
|
||||||
|
String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
|
||||||
|
if ("asc".equals(isAscStr)) {
|
||||||
|
list.add(OrderItem.asc(orderByStr));
|
||||||
|
} else if ("desc".equals(isAscStr)) {
|
||||||
|
list.add(OrderItem.desc(orderByStr));
|
||||||
|
} else {
|
||||||
|
throw new ServiceException(500,"排序参数有误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFirstNum() {
|
||||||
|
return (pageNum - 1) * pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符,防止注入绕过
|
||||||
|
*/
|
||||||
|
public static String escapeOrderBySql(String value) {
|
||||||
|
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
|
||||||
|
throw new IllegalArgumentException("参数不符合规范,不能进行查询");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证 order by 语法是否符合规范
|
||||||
|
*/
|
||||||
|
public static boolean isValidOrderBySql(String value) {
|
||||||
|
return value.matches(SQL_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package cn.iocoder.yudao.framework.mybatis.core.page;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格分页数据对象
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TableDataInfo<T> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总记录数
|
||||||
|
*/
|
||||||
|
private long total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表数据
|
||||||
|
*/
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页
|
||||||
|
*
|
||||||
|
* @param list 列表数据
|
||||||
|
* @param total 总记录数
|
||||||
|
*/
|
||||||
|
public TableDataInfo(List<T> list, long total) {
|
||||||
|
this.list = list;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分页对象构建表格分页数据对象
|
||||||
|
*/
|
||||||
|
public static <T> TableDataInfo<T> build(IPage<T> page) {
|
||||||
|
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||||
|
rspData.setList(page.getRecords());
|
||||||
|
rspData.setTotal(page.getTotal());
|
||||||
|
return rspData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据列表构建表格分页数据对象
|
||||||
|
*/
|
||||||
|
public static <T> TableDataInfo<T> build(List<T> list) {
|
||||||
|
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||||
|
rspData.setList(list);
|
||||||
|
rspData.setTotal(list.size());
|
||||||
|
return rspData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建表格分页数据对象
|
||||||
|
*/
|
||||||
|
public static <T> TableDataInfo<T> build() {
|
||||||
|
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||||
|
return rspData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package cn.iocoder.yudao.framework.mybatis.core.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.time.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AutoQueryUtil {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AutoQueryUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据实体类的属性值是否为空自动创建LambdaQueryWrapper。
|
||||||
|
*
|
||||||
|
* @param entity 实体对象
|
||||||
|
* @param <T> 实体类型
|
||||||
|
* @return LambdaQueryWrapper
|
||||||
|
*/
|
||||||
|
public static <T> QueryWrapper<T> autoQuery(T entity) {
|
||||||
|
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (entity == null) {
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> clazz = entity.getClass();
|
||||||
|
// 遍历当前类和所有父类的字段
|
||||||
|
while (clazz != null) {
|
||||||
|
Map<String, Method> getterMethods = new HashMap<>();
|
||||||
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
String fieldName = field.getName();
|
||||||
|
Class<?> finalClazz = clazz;
|
||||||
|
Method getterMethod = getterMethods.computeIfAbsent(
|
||||||
|
"get" + capitalize(fieldName),
|
||||||
|
k -> {
|
||||||
|
try {
|
||||||
|
return MethodUtils.getAccessibleMethod(finalClazz, k, (Class<?>[]) null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("No such getter method: {}", k, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getterMethod != null) {
|
||||||
|
try {
|
||||||
|
Object value = getterMethod.invoke(entity);
|
||||||
|
if (value != null && !"".equals(value)) {
|
||||||
|
handleFieldValue(queryWrapper, field, value);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error invoking getter method: {}", getterMethod.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取父类,继续遍历父类的字段
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTenantId(){
|
||||||
|
HttpServletRequest request = ServletUtils.getRequest();
|
||||||
|
if (null != request) {
|
||||||
|
// 获取request的header中的x-tenant-id
|
||||||
|
String tenantId = ServletUtils.getHeader(request, "x-tenant-id");
|
||||||
|
if (StringUtils.isNotBlank(tenantId)) {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleFieldValue(QueryWrapper<?> queryWrapper, Field field, Object value) {
|
||||||
|
String fieldName = field.getName();
|
||||||
|
|
||||||
|
if (String.class.isAssignableFrom(field.getType())) {
|
||||||
|
queryWrapper.like(StrUtil.toUnderlineCase(fieldName), value);
|
||||||
|
} else if (Integer.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Long.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Double.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Float.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Boolean.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Short.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Byte.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Character.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Date.class.isAssignableFrom(field.getType()) ||
|
||||||
|
LocalDateTime.class.isAssignableFrom(field.getType()) ||
|
||||||
|
LocalDate.class.isAssignableFrom(field.getType()) ||
|
||||||
|
LocalTime.class.isAssignableFrom(field.getType()) ||
|
||||||
|
ZonedDateTime.class.isAssignableFrom(field.getType()) ||
|
||||||
|
Instant.class.isAssignableFrom(field.getType())) {
|
||||||
|
queryWrapper.eq(StrUtil.toUnderlineCase(fieldName), String.valueOf(value));
|
||||||
|
} else {
|
||||||
|
// 如果是其他类型,可以添加更多条件,或者忽略
|
||||||
|
log.warn("Unsupported field type: {}", field.getType().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 pageReqVO 中所有 LocalDate[]、LocalDateTime[] 和 Date[] 类型字段的名称
|
||||||
|
*/
|
||||||
|
public static String[] getDateRangeFieldNames(Object pageReqVO) {
|
||||||
|
return Arrays.stream(pageReqVO.getClass().getDeclaredFields())
|
||||||
|
.filter(field -> field.getType().equals(LocalDate[].class)
|
||||||
|
|| field.getType().equals(LocalDateTime[].class)
|
||||||
|
|| field.getType().equals(Date[].class))
|
||||||
|
.map(Field::getName)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态处理 pageReqVO 中所有 LocalDate[]、LocalDateTime[] 和 Date[] 类型字段,设置时间段查询条件
|
||||||
|
*/
|
||||||
|
public static void handleDateRangeFields(Object pageReqVO, QueryWrapper<?> queryWrapper) {
|
||||||
|
for (Field field : pageReqVO.getClass().getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
Object fieldValue = field.get(pageReqVO);
|
||||||
|
if (fieldValue == null) {
|
||||||
|
continue; // 跳过值为 null 的字段
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.getType().equals(LocalDate[].class)) {
|
||||||
|
LocalDate[] dateRange = (LocalDate[]) fieldValue;
|
||||||
|
if (dateRange.length == 2) {
|
||||||
|
String columnName = StrUtil.toUnderlineCase(field.getName()); // 字段名转换为数据库列名
|
||||||
|
queryWrapper.between(columnName, dateRange[0], dateRange[1]);
|
||||||
|
}
|
||||||
|
} else if (field.getType().equals(LocalDateTime[].class)) {
|
||||||
|
LocalDateTime[] dateTimeRange = (LocalDateTime[]) fieldValue;
|
||||||
|
if (dateTimeRange.length == 2) {
|
||||||
|
String columnName = StrUtil.toUnderlineCase(field.getName()); // 字段名转换为数据库列名
|
||||||
|
queryWrapper.between(columnName, dateTimeRange[0], dateTimeRange[1]);
|
||||||
|
}
|
||||||
|
} else if (field.getType().equals(Date[].class)) {
|
||||||
|
Date[] range = (Date[]) fieldValue;
|
||||||
|
if (range.length == 2) {
|
||||||
|
String columnName = StrUtil.toUnderlineCase(field.getName()); // 字段名转换为数据库列名
|
||||||
|
queryWrapper.between(columnName, range[0], range[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new ServiceException(500,"Failed to access field " + field.getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String capitalize(String str) {
|
||||||
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,11 +25,13 @@ import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -148,4 +150,21 @@ public class CodegenController {
|
||||||
writeAttachment(response, "codegen.zip", outputStream.toByteArray());
|
writeAttachment(response, "codegen.zip", outputStream.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序前后端代码生成,并解压到配置的路径
|
||||||
|
*
|
||||||
|
* @param tableIdStr 表ID串
|
||||||
|
*/
|
||||||
|
@Operation(summary = "应用程序前后端代码生成")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "tableIdStr", description = "表编号", required = true, example = "1024,1025"),
|
||||||
|
@Parameter(name = "appName", description = "应用名称", required = true, example = "testApp")
|
||||||
|
})
|
||||||
|
@GetMapping("/genAppCode")
|
||||||
|
@PermitAll
|
||||||
|
// @PreAuthorize("@ss.hasPermission('infra:codegen:genappcode')")
|
||||||
|
public CommonResult<String> genAppCode( String tableIdStr, String appName){
|
||||||
|
return success(codegenService.genAppCode(tableIdStr, appName));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,4 +98,12 @@ public interface CodegenService {
|
||||||
*/
|
*/
|
||||||
List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment);
|
List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序前后端代码生成,并解压到配置的路径
|
||||||
|
* @param tableIdStr
|
||||||
|
* @param appName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String genAppCode(String tableIdStr, String appName);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package cn.iocoder.yudao.module.infra.service.codegen;
|
package cn.iocoder.yudao.module.infra.service.codegen;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.core.util.ZipUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
|
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
|
||||||
|
@ -18,29 +23,51 @@ import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
||||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
|
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
|
||||||
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
|
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
|
||||||
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
|
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
|
||||||
|
import cn.iocoder.yudao.module.infra.util.DistributedLock;
|
||||||
|
import cn.iocoder.yudao.module.infra.util.InputStreamConverter;
|
||||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
|
import com.alibaba.excel.util.DateUtils;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||||
|
import static cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代码生成 Service 实现类
|
* 代码生成 Service 实现类
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class CodegenServiceImpl implements CodegenService {
|
public class CodegenServiceImpl implements CodegenService {
|
||||||
|
|
||||||
|
@ -63,6 +90,31 @@ public class CodegenServiceImpl implements CodegenService {
|
||||||
@Resource
|
@Resource
|
||||||
private CodegenProperties codegenProperties;
|
private CodegenProperties codegenProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
@Value("${runsystem.project.basedir.unzip}")
|
||||||
|
private String unzipPath;
|
||||||
|
|
||||||
|
|
||||||
|
@Value("${runsystem.project.basedir.menusql}")
|
||||||
|
private String menuSqlPath;
|
||||||
|
|
||||||
|
@Value("${runsystem.project.basedir.shell}")
|
||||||
|
private String shellPath;
|
||||||
|
|
||||||
|
@Value("${runsystem.project.basedir.config}")
|
||||||
|
private String configPath;
|
||||||
|
|
||||||
|
@Value("${runsystem.project.basedir.nginx}")
|
||||||
|
private String nginxPath;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DistributedLock distributedLock;
|
||||||
|
|
||||||
|
@Value("${runsystem.project.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public List<Long> createCodegenList(Long userId, CodegenCreateListReqVO reqVO) {
|
public List<Long> createCodegenList(Long userId, CodegenCreateListReqVO reqVO) {
|
||||||
|
@ -293,4 +345,243 @@ public class CodegenServiceImpl implements CodegenService {
|
||||||
return BeanUtils.toBean(tables, DatabaseTableRespVO.class);
|
return BeanUtils.toBean(tables, DatabaseTableRespVO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序前后端代码生成,并解压到配置的路径
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String genAppCode(String tableIdStr, String appName) {
|
||||||
|
appName = appName.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
|
||||||
|
log.info("应用程序前后端代码生成开始,tableIdStr:{},appName:{}", tableIdStr, appName);
|
||||||
|
|
||||||
|
String lockKey = "genAppCode:" + ":" + appName;
|
||||||
|
if (!distributedLock.lock(lockKey, 10, TimeUnit.MINUTES)) {
|
||||||
|
log.error("Failed to acquire lock for appName: {}", appName);
|
||||||
|
throw new ServiceException(500,appName + " Failed to acquire lock");
|
||||||
|
}
|
||||||
|
Long[] tableIds = Convert.toLongArray(tableIdStr);
|
||||||
|
try {
|
||||||
|
|
||||||
|
String appDir = "/" + appName + "-" + DateUtils.DATE_FORMAT_10;
|
||||||
|
String savePath = unzipPath + appDir;
|
||||||
|
String url = "";
|
||||||
|
|
||||||
|
Map<String,String> codeResult=new HashMap<String, String>();
|
||||||
|
// 生成代码
|
||||||
|
for (Long tableId : tableIds) {
|
||||||
|
Map<String, String> codes = this.generationCodes(tableId);
|
||||||
|
codeResult.putAll(codes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 zip 包
|
||||||
|
String[] paths = codeResult.keySet().toArray(new String[0]);
|
||||||
|
ByteArrayInputStream[] ins = codeResult.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
|
||||||
|
|
||||||
|
// 解压ZIP文件内容到目标文件夹
|
||||||
|
File targetFolder = new File(savePath);
|
||||||
|
if (!targetFolder.exists()) {
|
||||||
|
targetFolder.mkdirs();
|
||||||
|
} else {
|
||||||
|
FileUtils.deleteDirectory(targetFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream bais = InputStreamConverter.convertByteArrayInputStreamArrayToInputStream(ins);
|
||||||
|
ZipInputStream zis = new ZipInputStream(bais);
|
||||||
|
ZipUtil.unzip(zis, targetFolder);
|
||||||
|
|
||||||
|
//运行系统-执行菜单文件夹中的SQL脚本
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
String menuScriptsDir = savePath + menuSqlPath;
|
||||||
|
if (null != connection) {
|
||||||
|
executeMenuScripts(menuScriptsDir, savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
//运行系统
|
||||||
|
executeRunSystemShell(appName, savePath);
|
||||||
|
|
||||||
|
//构建nginx配置文件
|
||||||
|
url = buildNginxConf(appName, savePath);
|
||||||
|
|
||||||
|
// 返回成功信息
|
||||||
|
log.info("代码生成成功,已保存到: " + savePath, "运行系统url地址:" + url);
|
||||||
|
return StringUtils.isNotBlank(url) ? url : "应用容器启动失败";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to generate app code: {}", e.getMessage());
|
||||||
|
return "应用容器启动失败:" + e.getMessage();
|
||||||
|
} finally {
|
||||||
|
distributedLock.unlock(lockKey);
|
||||||
|
log.info("Lock released for tableIdStr: {}, appName: {}", tableIdStr, appName);
|
||||||
|
//删除gen_table和gen_table_column中table_id相关的数据
|
||||||
|
codegenTableMapper.deleteByIds(Arrays.asList(tableIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行菜单脚本并合并为一个文件
|
||||||
|
*/
|
||||||
|
private static void executeMenuScripts(String menuScriptsDir, String targetDir) throws IOException {
|
||||||
|
File dir = new File(menuScriptsDir);
|
||||||
|
if (!dir.exists() || !dir.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException("指定的目录不存在或不是一个文件夹: " + menuScriptsDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] sqlFiles = dir.listFiles((dir1, name) -> name.toLowerCase().endsWith(".sql"));
|
||||||
|
if (sqlFiles == null || sqlFiles.length == 0) {
|
||||||
|
System.out.println("没有找到 SQL 脚本文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.sort(sqlFiles); // 按文件名排序
|
||||||
|
|
||||||
|
StringBuilder combinedSql = new StringBuilder();
|
||||||
|
|
||||||
|
for (File sqlFile : sqlFiles) {
|
||||||
|
String sqlContent = readFile(sqlFile);
|
||||||
|
combinedSql.append(sqlContent).append(";\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将合并后的 SQL 内容写入目标文件
|
||||||
|
String targetFilePath = targetDir + File.separator + "menu.sql";
|
||||||
|
Path targetPath = Paths.get(targetFilePath);
|
||||||
|
Files.createDirectories(targetPath.getParent());
|
||||||
|
Files.write(targetPath, combinedSql.toString().getBytes());
|
||||||
|
|
||||||
|
System.out.println("合并后的menu SQL脚本已保存到: " + targetFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readFile(File file) throws IOException {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||||
|
return reader.lines().collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行运行系统shell脚本
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String executeRunSystemShell(String appName, String savePath) {
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
// 组合命令和参数
|
||||||
|
// String[] command = {"/bin/sh", "bash "+savePath + shellPath + "/runSystem.sh", appName};
|
||||||
|
|
||||||
|
|
||||||
|
// 执行Shell脚本
|
||||||
|
Process process = Runtime.getRuntime().exec("sh " + savePath + shellPath + "/runSystem.sh " + appName);
|
||||||
|
|
||||||
|
// 读取脚本的输出
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("ls").getInputStream())));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
result += line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待脚本执行完成
|
||||||
|
// process.waitFor();
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
log.info("Script runSystem executed with exit code: " + exitCode);
|
||||||
|
|
||||||
|
// Thread.sleep(15000);
|
||||||
|
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private String buildNginxConf(String appName, String savePath) {
|
||||||
|
String url = "";
|
||||||
|
// 读取nginx.conf文件内容并替换{port}和{host_ip}
|
||||||
|
// 读取文件内容
|
||||||
|
StringBuilder contentBuilder = new StringBuilder();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(savePath + configPath + "/nginx.conf"))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
contentBuilder.append(line).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String hostIp = executeSelectIpShell(appName, savePath);
|
||||||
|
String port = getPortSequence().toString();
|
||||||
|
|
||||||
|
if (ObjectUtil.isAllNotEmpty(hostIp, port)) {
|
||||||
|
String content = contentBuilder.toString();
|
||||||
|
content = content.replace("{port}", port);
|
||||||
|
content = content.replace("{host_ip}", hostIp);
|
||||||
|
|
||||||
|
// 构建输出文件路径
|
||||||
|
File outputDir = new File(nginxPath);
|
||||||
|
if (!outputDir.exists()) {
|
||||||
|
outputDir.mkdirs(); // 如果目录不存在,则创建目录
|
||||||
|
}
|
||||||
|
File outputFile = new File(outputDir, appName + ".conf");
|
||||||
|
|
||||||
|
// 写入替换后的内容到新文件
|
||||||
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
|
||||||
|
writer.write(content);
|
||||||
|
}
|
||||||
|
log.info("文件内容已成功替换并保存到: " + outputFile.getAbsolutePath());
|
||||||
|
url = host + ":" + port;
|
||||||
|
}
|
||||||
|
// 替换 {port} 和 {host_ip}
|
||||||
|
|
||||||
|
|
||||||
|
Runtime.getRuntime().exec("nginx -s reload");
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查找ip shell脚本
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String executeSelectIpShell(String appName, String savePath) {
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
// 执行Shell脚本
|
||||||
|
Process process = Runtime.getRuntime().exec("sh " + savePath + shellPath + "/selectIp.sh " + appName);
|
||||||
|
|
||||||
|
// 读取脚本的输出
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
result += line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待脚本执行完成
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
log.info("Script selectIp executed with exit code: " + exitCode);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取端口号序列值
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
private Long getPortSequence() throws SQLException {
|
||||||
|
Long port = 10000L;
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
CallableStatement callableStatement = connection.prepareCall("{call get_next_sequence()}");
|
||||||
|
boolean hasResult = callableStatement.execute();
|
||||||
|
if (hasResult) {
|
||||||
|
ResultSet resultSet = callableStatement.getResultSet();
|
||||||
|
if (resultSet.next()) {
|
||||||
|
port = resultSet.getLong(1);
|
||||||
|
log.info("Next sequence value: " + port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cn.iocoder.yudao.module.infra.util;
|
||||||
|
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DistributedLock {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
public boolean lock(String key, long timeout, TimeUnit unit) {
|
||||||
|
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, "locked", timeout, unit);
|
||||||
|
return result != null && result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlock(String key) {
|
||||||
|
stringRedisTemplate.delete(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.iocoder.yudao.module.infra.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.SequenceInputStream;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
public class InputStreamConverter {
|
||||||
|
public static InputStream convertByteArrayInputStreamArrayToInputStream(ByteArrayInputStream[] ins) {
|
||||||
|
Vector<InputStream> inputStreams = new Vector<>();
|
||||||
|
for (ByteArrayInputStream byteArrayInputStream : ins) {
|
||||||
|
inputStreams.add(byteArrayInputStream);
|
||||||
|
}
|
||||||
|
return new SequenceInputStream(new Enumeration<InputStream>() {
|
||||||
|
private final Enumeration<InputStream> enumeration = inputStreams.elements();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMoreElements() {
|
||||||
|
return enumeration.hasMoreElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream nextElement() {
|
||||||
|
return enumeration.nextElement();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,3 +179,27 @@ yudao:
|
||||||
- infra_data_source_config
|
- infra_data_source_config
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
#应用运行系统基线项目文件地址
|
||||||
|
runsystem:
|
||||||
|
project:
|
||||||
|
host: 10.31.0.172
|
||||||
|
basedir:
|
||||||
|
# 基线项目根目录 服务器路径:/root/RunSystemBaseLineProject
|
||||||
|
# source: D:/RunSystemBaseLineProject
|
||||||
|
source: /root/RunSystemBaseLineProject
|
||||||
|
# java源文件路径
|
||||||
|
java: /app/icss-xm-app/ruoyi-modules/ruoyi-system/src/
|
||||||
|
# web源文件路径
|
||||||
|
web: /web/icss-xm-app-web/src/
|
||||||
|
# 运行系统解压路径 服务器路径:/root/dockerTest
|
||||||
|
# unzip: D:/RunSystemSavePath
|
||||||
|
unzip: /root/dockerTest
|
||||||
|
# 菜单sql文件路径
|
||||||
|
menusql: /app/icss-xm-app/ruoyi-modules/ruoyi-system/src/main/resources/menusql
|
||||||
|
# shell脚本路径
|
||||||
|
shell:
|
||||||
|
# config文件路径
|
||||||
|
config: /app/icss-xm-app/script/config
|
||||||
|
# nginx配置文件路径 服务器路径:/etc/nginx/conf.d
|
||||||
|
# nginx: D:/code/icss-xm-app/script/nginx
|
||||||
|
nginx: /etc/nginx/conf.d
|
|
@ -163,7 +163,7 @@ yudao:
|
||||||
description: 提供管理员管理的所有功能
|
description: 提供管理员管理的所有功能
|
||||||
version: ${yudao.info.version}
|
version: ${yudao.info.version}
|
||||||
tenant: # 多租户相关配置项
|
tenant: # 多租户相关配置项
|
||||||
enable: true
|
enable: false
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
|
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
|
||||||
- /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
|
- /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
|
||||||
|
|
Loading…
Reference in New Issue