Merge branch 'dev-v2' into pr@dev-v2@refactor_market

This commit is contained in:
王嘉豪 2023-11-27 16:40:09 +08:00 committed by GitHub
commit b563b81a56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1308 additions and 2257 deletions

View File

@ -21,12 +21,16 @@ public class DeMvcConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String workDir = FILE_PROTOCOL + ensureSuffix(WORK_DIR, FILE_SEPARATOR);
String uploadUrlPattern = ensureBoth(URL_SEPARATOR + UPLOAD_URL_PREFIX, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(uploadUrlPattern)
.addResourceLocations(workDir);
registry.addResourceHandler(uploadUrlPattern).addResourceLocations(workDir);
// map
String mapDir = FILE_PROTOCOL + ensureSuffix(MAP_DIR, FILE_SEPARATOR);
String mapUrlPattern = ensureBoth(MAP_URL, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(mapUrlPattern)
.addResourceLocations(mapDir);
registry.addResourceHandler(mapUrlPattern).addResourceLocations(mapDir);
String geoDir = FILE_PROTOCOL + ensureSuffix(CUSTOM_MAP_DIR, FILE_SEPARATOR);
String geoUrlPattern = ensureBoth(GEO_URL, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(geoUrlPattern).addResourceLocations(geoDir);
}
}

View File

@ -16,7 +16,6 @@ import io.dataease.datasource.request.DatasourceRequest;
import io.dataease.datasource.server.EngineServer;
import io.dataease.datasource.type.*;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.utils.BeanUtils;
@ -26,6 +25,7 @@ import io.dataease.utils.LogUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;

View File

@ -1,194 +0,0 @@
package io.dataease.engine.func.scalar;
import io.dataease.engine.utils.Utils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScalarFunctions {
public static String format = "yyyy-MM-dd HH:mm:ss";
public static String minuteFormat = "yyyy-MM-dd HH:mm";
public static String hourFormat = "yyyy-MM-dd HH";
public static String dateOnly = "yyyy-MM-dd";
public static String monthOnly = "yyyy-MM";
public static String yearOnly = "yyyy";
public static String timeOnly = "HH:mm:ss";
public static String date_format(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
format = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_date_format(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String str_to_date(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
format = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_str_to_date(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String cast_date_format(String date, String sourceFormat, String targetFormat) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
sourceFormat = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sourceFormat);
Date parse = simpleDateFormat.parse(date);
SimpleDateFormat s = new SimpleDateFormat(targetFormat);
return s.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_cast_date_format(String date, String sourceFormat, String targetFormat) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sourceFormat);
Date parse = simpleDateFormat.parse(date);
SimpleDateFormat s = new SimpleDateFormat(targetFormat);
return s.format(parse);
} catch (Exception e) {
return null;
}
}
public static Long unix_timestamp(String date) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
return Utils.allDateFormat2Long(date);
} catch (Exception e) {
return null;
}
}
public static String get_date_format(String date) {
// check date split '-' or '/'
String format1 = format;
String minuteFormat1 = minuteFormat;
String hourFormat1 = hourFormat;
String timeOnly1 = timeOnly;
String dateOnly1 = dateOnly;
String monthOnly1 = monthOnly;
String yearOnly1 = yearOnly;
if (date != null && date.contains("/")) {
format1 = format1.replaceAll("-", "/");
minuteFormat1 = minuteFormat1.replaceAll("-", "/");
hourFormat1 = hourFormat1.replaceAll("-", "/");
timeOnly1 = timeOnly1.replaceAll("-", "/");
dateOnly1 = dateOnly1.replaceAll("-", "/");
monthOnly1 = monthOnly1.replaceAll("-", "/");
yearOnly1 = yearOnly1.replaceAll("-", "/");
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format1);
simpleDateFormat.parse(date);
return format1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(minuteFormat1);
simpleDateFormat.parse(date);
return minuteFormat1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(hourFormat1);
simpleDateFormat.parse(date);
return hourFormat1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(timeOnly1);
simpleDateFormat.parse(date);
return timeOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateOnly1);
simpleDateFormat.parse(date);
return dateOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(monthOnly1);
simpleDateFormat.parse(date);
return monthOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(yearOnly1);
simpleDateFormat.parse(date);
return yearOnly1;
} catch (Exception e) {
}
return format1;
}
public static String from_unixtime(Long timestamp, String format) {
try {
if (ObjectUtils.isEmpty(timestamp)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date date = new Date(timestamp);
return simpleDateFormat.format(date);
} catch (Exception e) {
return null;
}
}
public static String concat(String str1, String str2) {
return str1 + str2;
}
}

View File

@ -6,8 +6,8 @@ import io.dataease.api.dataset.union.model.SQLMeta;
import io.dataease.api.dataset.union.model.SQLObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

View File

@ -5,8 +5,8 @@ import io.dataease.api.dataset.union.model.SQLMeta;
import io.dataease.api.dataset.union.model.SQLObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -55,7 +55,7 @@ public class ExtWhere2Str {
if (field.getDeType() == 1) {
String date_format;
if (StringUtils.containsIgnoreCase(request.getOperator(), "between")) {
date_format = ScalarFunctions.format;
date_format = "yyyy-MM-dd HH:mm:ss";
} else {
date_format = ScalarFunctions.get_date_format(value.get(0));
}

View File

@ -8,8 +8,8 @@ import io.dataease.api.permissions.dataset.dto.DatasetRowPermissionsTreeObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.ExtFieldConstant;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

View File

@ -0,0 +1,13 @@
package io.dataease.map.bo;
import io.dataease.map.dao.auto.entity.Area;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class AreaBO extends Area implements Serializable {
private boolean custom = false;
}

View File

@ -0,0 +1,15 @@
package io.dataease.map.dao.ext.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class CoreAreaCustom implements Serializable {
private String id;
private String pid;
private String name;
}

View File

@ -0,0 +1,9 @@
package io.dataease.map.dao.ext.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.dataease.map.dao.ext.entity.CoreAreaCustom;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CoreAreaCustomMapper extends BaseMapper<CoreAreaCustom> {
}

View File

@ -1,17 +1,34 @@
package io.dataease.map.manage;
import cn.hutool.core.collection.CollectionUtil;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.dataease.api.map.vo.AreaNode;
import io.dataease.constant.StaticResourceConstants;
import io.dataease.exception.DEException;
import io.dataease.map.bo.AreaBO;
import io.dataease.map.dao.auto.entity.Area;
import io.dataease.map.dao.auto.mapper.AreaMapper;
import io.dataease.map.dao.ext.entity.CoreAreaCustom;
import io.dataease.map.dao.ext.mapper.CoreAreaCustomMapper;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.LogUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static io.dataease.constant.CacheConstant.CommonCacheConstant.WORLD_MAP_CACHE;
@ -19,6 +36,8 @@ import static io.dataease.constant.CacheConstant.CommonCacheConstant.WORLD_MAP_C
public class MapManage {
private final static AreaNode WORLD;
private static final String GEO_PREFIX = "geo_";
static {
WORLD = AreaNode.builder()
.id("000")
@ -30,13 +49,34 @@ public class MapManage {
@Resource
private AreaMapper areaMapper;
@Resource
private CoreAreaCustomMapper coreAreaCustomMapper;
public List<Area> defaultArea() {
return areaMapper.selectList(null);
}
private MapManage proxy() {
return CommonBeanFactory.getBean(MapManage.class);
}
@Cacheable(value = WORLD_MAP_CACHE, key = "'world_map'")
public AreaNode getWorldTree() {
List<Area> areas = areaMapper.selectList(null);
List<Area> areas = proxy().defaultArea();
List<AreaBO> areaBOS = areas.stream().map(item -> BeanUtils.copyBean(new AreaBO(), item)).collect(Collectors.toList());
List<CoreAreaCustom> coreAreaCustoms = coreAreaCustomMapper.selectList(null);
if (CollectionUtils.isNotEmpty(coreAreaCustoms)) {
List<AreaBO> customBoList = coreAreaCustoms.stream().map(item -> {
AreaBO areaBO = BeanUtils.copyBean(new AreaBO(), item);
areaBO.setCustom(true);
return areaBO;
}).toList();
areaBOS.addAll(customBoList);
}
WORLD.setChildren(new ArrayList<>());
var areaNodeMap = new HashMap<String, AreaNode>();
areaNodeMap.put(WORLD.getId(), WORLD);
areas.forEach(area -> {
areaBOS.forEach(area -> {
var node = areaNodeMap.get(area.getId());
if (node == null) {
node = AreaNode.builder().build();
@ -64,5 +104,80 @@ public class MapManage {
return WORLD;
}
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional
public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) {
List<Area> areas = proxy().defaultArea();
String code = getBusiGeoCode(request.getCode());
AtomicReference<String> atomicReference = new AtomicReference<>();
if (areas.stream().anyMatch(area -> {
boolean exist = area.getId().equals(code);
if (exist) {
atomicReference.set(area.getName());
}
return exist;
})) {
DEException.throwException(String.format("Area code [%s] is already exists for [%s]", code, atomicReference.get()));
}
CoreAreaCustom originData = null;
if (ObjectUtils.isNotEmpty(originData = coreAreaCustomMapper.selectById(getDaoGeoCode(code)))) {
DEException.throwException(String.format("Area code [%s] is already exists for [%s]", code, originData.getName()));
}
CoreAreaCustom coreAreaCustom = new CoreAreaCustom();
coreAreaCustom.setId(getDaoGeoCode(code));
coreAreaCustom.setPid(request.getPid());
coreAreaCustom.setName(request.getName());
coreAreaCustomMapper.insert(coreAreaCustom);
File geoFile = buildGeoFile(code);
try {
file.transferTo(geoFile);
} catch (IOException e) {
LogUtil.error(e.getMessage());
DEException.throwException(e);
}
}
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional
public void deleteGeo(String code) {
if (!StringUtils.startsWith(code, GEO_PREFIX)) {
DEException.throwException("内置Geometry禁止删除");
}
coreAreaCustomMapper.deleteById(code);
File file = buildGeoFile(code);
if (file.exists()) {
file.delete();
}
}
private String getDaoGeoCode(String code) {
return StringUtils.startsWith(code, GEO_PREFIX) ? code : (GEO_PREFIX + code);
}
private String getBusiGeoCode(String code) {
return StringUtils.startsWith(code, GEO_PREFIX) ? code.substring(GEO_PREFIX.length()) : code;
}
private File buildGeoFile(String code) {
String id = getBusiGeoCode(code);
String customMapDir = StaticResourceConstants.CUSTOM_MAP_DIR;
String countryCode = countryCode(id);
String fileDirPath = customMapDir + "/" + countryCode + "/";
File dir = new File(fileDirPath);
if (!dir.exists()) {
dir.mkdirs();
}
String filePath = fileDirPath + id + ".json";
return new File(filePath);
}
private String countryCode(String code) {
return code.substring(0, 3);
}
}

View File

@ -0,0 +1,26 @@
package io.dataease.map.server;
import io.dataease.api.map.GeoApi;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.dataease.map.manage.MapManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/geometry")
public class GeoServer implements GeoApi {
@Resource
private MapManage mapManage;
@Override
public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) {
mapManage.saveMapGeo(request, file);
}
@Override
public void deleteGeo(String id) {
mapManage.deleteGeo(id);
}
}

View File

@ -95,6 +95,7 @@ public class MenuManage {
|| coreMenu.getId().equals(18L)
|| coreMenu.getId().equals(21L)
|| coreMenu.getPid().equals(21L)
|| coreMenu.getId().equals(25L);
|| coreMenu.getId().equals(25L)
|| coreMenu.getId().equals(26L);
}
}

View File

@ -0,0 +1,10 @@
package io.dataease.system.dao.ext.mapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.dao.auto.mapper.CoreSysSettingMapper;
import org.springframework.stereotype.Component;
@Component("extCoreSysSettingMapper")
public class ExtCoreSysSettingMapper extends ServiceImpl<CoreSysSettingMapper, CoreSysSetting> {
}

View File

@ -1,15 +1,22 @@
package io.dataease.system.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.license.config.XpackInteract;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.dao.auto.mapper.CoreSysSettingMapper;
import io.dataease.system.dao.ext.mapper.ExtCoreSysSettingMapper;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.IDUtils;
import io.dataease.utils.SystemSettingUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -22,6 +29,9 @@ public class SysParameterManage {
@Resource
private CoreSysSettingMapper coreSysSettingMapper;
@Resource
private ExtCoreSysSettingMapper extCoreSysSettingMapper;
public String singleVal(String key) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("pkey", key);
@ -53,22 +63,54 @@ public class SysParameterManage {
sysSetting.setPval(val);
coreSysSettingMapper.updateById(sysSetting);
}
void save(List<CoreSysSetting> boList) {
List<CoreSysSetting> all = all();
}
private List<CoreSysSetting> all() {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
return coreSysSettingMapper.selectList(queryWrapper);
}
public Map<String,String> groupVal(String groupKey) {
public Map<String, String> groupVal(String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.like("pkey", groupKey);
queryWrapper.likeRight("pkey", groupKey);
queryWrapper.orderByAsc("sort");
List<CoreSysSetting> sysSettings = coreSysSettingMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(sysSettings)) {
return sysSettings.stream().collect(Collectors.toMap(CoreSysSetting::getPkey, CoreSysSetting::getPval));
}
return null;
}
public List<CoreSysSetting> groupList(String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("pkey", groupKey);
queryWrapper.orderByAsc("sort");
return coreSysSettingMapper.selectList(queryWrapper);
}
@XpackInteract(value = "perSetting")
public List<SettingItemVO> convert(List<CoreSysSetting> sysSettings) {
return sysSettings.stream().sorted(Comparator.comparing(CoreSysSetting::getSort)).map(item -> BeanUtils.copyBean(new SettingItemVO(), item)).toList();
}
@Transactional
public void saveGroup(List<SettingItemVO> vos, String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("pkey", groupKey);
coreSysSettingMapper.delete(queryWrapper);
List<CoreSysSetting> sysSettings = vos.stream().filter(vo -> !SystemSettingUtils.xpackSetting(vo.getPkey())).map(item -> {
CoreSysSetting sysSetting = BeanUtils.copyBean(new CoreSysSetting(), item);
sysSetting.setId(IDUtils.snowID());
return sysSetting;
}).collect(Collectors.toList());
extCoreSysSettingMapper.saveBatch(sysSettings);
}
@XpackInteract(value = "perSetting", before = false)
@Transactional
public void saveBasic(List<SettingItemVO> vos) {
String key = "basic.";
proxy().saveGroup(vos, key);
}
private SysParameterManage proxy() {
return CommonBeanFactory.getBean(SysParameterManage.class);
}
}

View File

@ -2,12 +2,16 @@ package io.dataease.system.server;
import io.dataease.api.system.SysParameterApi;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.manage.SysParameterManage;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/sysParameter")
public class SysParameterServer implements SysParameterApi {
@ -29,4 +33,16 @@ public class SysParameterServer implements SysParameterApi {
String key = sysParameterManage.queryOnlineMap();
return StringUtils.isNotBlank(key) ? key : "";
}
@Override
public List<SettingItemVO> queryBasicSetting() {
String key = "basic.";
List<CoreSysSetting> coreSysSettings = sysParameterManage.groupList(key);
return sysParameterManage.convert(coreSysSettings);
}
@Override
public void saveBasicSetting(List<SettingItemVO> settingItemVOS) {
sysParameterManage.saveBasic(settingItemVOS);
}
}

View File

@ -26,17 +26,32 @@ VALUES (20, 15, 2, 'template-setting', 'system/template-setting', 4, 'icon_templ
COMMIT;
DROP TABLE IF EXISTS `visualization_template_extend_data`;
CREATE TABLE `visualization_template_extend_data` (
`id` bigint NOT NULL,
`dv_id` bigint DEFAULT NULL,
`view_id` bigint DEFAULT NULL,
`view_details` longtext,
`copy_from` varchar(255) DEFAULT NULL,
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
CREATE TABLE `visualization_template_extend_data`
(
`id` bigint NOT NULL,
`dv_id` bigint DEFAULT NULL,
`view_id` bigint DEFAULT NULL,
`view_details` longtext,
`copy_from` varchar(255) DEFAULT NULL,
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_id` bigint NULL COMMENT '资源ID' AFTER `id`,
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;
DROP TABLE IF EXISTS `core_area_custom`;
CREATE TABLE `core_area_custom`
(
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`pid` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
BEGIN;
INSERT INTO `core_sys_setting` VALUES (1, 'basic.dsIntervalTime', '6', 'text', 2);
INSERT INTO `core_sys_setting` VALUES (2, 'basic.dsExecuteTime', 'minute', 'text', 3);
COMMIT;

View File

@ -23,6 +23,7 @@ i18n_menu.summary=\u6982\u89C8
i18n_menu.ds=\u6570\u636E\u6E90\u7BA1\u7406
i18n_menu.task=\u4EFB\u52A1\u7BA1\u7406
i18n_menu.embedded=\u5D4C\u5165\u5F0F\u7BA1\u7406
i18n_menu.platform=\u5E73\u53F0\u5BF9\u63A5
i18n_field_name_repeat=\u6709\u91CD\u590D\u5B57\u6BB5\u540D\uFF1A
i18n_pid_not_eq_id=\u79FB\u52A8\u76EE\u6807\u4E0D\u80FD\u662F\u81EA\u5DF1\u6216\u5B50\u76EE\u5F55
i18n_ds_name_exists=\u8BE5\u5206\u7EC4\u4E0B\u540D\u79F0\u91CD\u590D

View File

@ -5,9 +5,21 @@ export const getWorldTree = (): Promise<IResponse<AreaNode>> => {
return request.get({ url: '/map/worldTree' })
}
export const getGeoJson = (
country: string,
areaId: string
): Promise<IResponse<FeatureCollection>> => {
return request.get({ url: `/map/${country}/${areaId}.json` })
export const getGeoJson = (areaId: string): Promise<IResponse<FeatureCollection>> => {
let prefix = '/map'
let areaCode = areaId
if (isCustomGeo(areaId)) {
prefix = '/geo'
areaCode = getBusiGeoCode(areaId)
}
const realCountry = areaCode.substring(0, 3)
const url = `${prefix}/${realCountry}/${areaCode}.json`
return request.get({ url })
}
const isCustomGeo = (id: string) => {
return id.startsWith('geo_')
}
const getBusiGeoCode = (id: string) => {
return id.substring(4)
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M314.49 3.082H157.692C70.793 3.082 0 74.073 0 161.271V864.32c0 87.198 70.792 158.189 157.592 158.189H314.49c86.9 0 157.592-70.991 157.592-158.189V702.652h-72.98V864.32c0 46.93-37.98 84.91-84.612 84.91H157.693c-46.731 0-84.613-38.18-84.613-84.91V161.27c0-46.929 37.981-84.91 84.613-84.91h156.896c46.73 0 84.612 38.18 84.612 84.91v159.68h72.98V161.27c-0.1-87.197-70.793-158.188-157.692-158.188z m546.252 0H703.746c-86.899 0-157.592 70.991-157.592 158.189v159.68h72.98V161.27c0-46.93 37.981-84.911 84.612-84.911h156.896c46.731 0 84.613 38.18 84.613 84.91v703.05c0 46.93-37.981 84.91-84.613 84.91H703.746c-46.73 0-84.612-38.18-84.612-84.91V702.652h-72.98V864.32c0 87.198 70.792 158.189 157.592 158.189h156.896c86.9 0 157.592-70.991 157.592-158.189V161.27c0.1-87.197-70.692-158.188-157.492-158.188z m0 0" /><path d="M191.995 485.602H461.84c-0.596-27.74-23.465-50.31-51.304-50.31H186.129c-28.238 0-51.305 23.166-51.305 51.503v105.492c0 28.337 23.067 51.504 51.305 51.504h224.506c28.237 0 51.305-23.167 51.305-51.504v-1.093H191.995V485.602z m0 0" /><path d="M395.423 512.447h230.074v46.035H395.423z" /><path d="M556.395 485.602H826.34v105.492H556.395v1.094c0 28.337 23.067 51.503 51.305 51.503h224.506c28.238 0 51.305-23.166 51.305-51.503V486.696c0-28.337-23.067-51.503-51.305-51.503H607.7c-27.84 0.099-50.708 22.669-51.305 50.41z m0 0" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -58,8 +58,7 @@ const loadComponent = () => {
storeCacheProxy(byteArray)
importProxy(byteArray)
})
.catch(e => {
console.error(e)
.catch(() => {
showNolic()
})
.finally(() => {

View File

@ -130,7 +130,7 @@ service.interceptors.response.use(
return response
} else if (response.data.code === result_code || response.data.code === 50002) {
return response.data
} else if (response.config.url.match(/^\/map\/\d{3}\/\d+\.json$/)) {
} else if (response.config.url.match(/^\/map|geo\/\d{3}\/\d+\.json$/)) {
// TODO 处理静态文件
return response
} else {

View File

@ -3,6 +3,7 @@
</template>
<script setup lang="ts">
import { formatDataEaseBi } from '@/utils/url'
//JS
//js
import tinymce from 'tinymce/tinymce'
@ -58,9 +59,9 @@ const tinymceId = ref('vue-tinymce-' + +new Date() + ((Math.random() * 1000).toF
const init = reactive({
// inline: true, //
selector: '#' + tinymceId.value, //id,
language_url: '/tinymce-dataease-private/langs/zh_CN.js', // js
language_url: formatDataEaseBi('/tinymce-dataease-private/langs/zh_CN.js'), // js
language: 'zh_CN', //
skin_url: '/tinymce-dataease-private/skins/ui/oxide', // skin
skin_url: formatDataEaseBi('/tinymce-dataease-private/skins/ui/oxide'), // skin
height: 400, //
branding: false, //Powered by TinyMCE
menubar: true, //
@ -75,7 +76,7 @@ const init = reactive({
nonbreaking_force_tab: false,
paste_auto_cleanup_on_paste: false,
file_picker_types: 'file',
content_css: '/tinymce-dataease-private/skins/content/default/content.css', //csscsscss
content_css: formatDataEaseBi('/tinymce-dataease-private/skins/content/default/content.css'), //csscsscss
//
images_upload_handler: blobInfo =>
new Promise((resolve, reject) => {

View File

@ -28,6 +28,7 @@
</template>
<script setup lang="ts">
import { formatDataEaseBi } from '@/utils/url'
import tinymce from 'tinymce/tinymce' // tinymcehidden
import Editor from '@tinymce/tinymce-vue' //
import 'tinymce/themes/silver/theme' //
@ -114,10 +115,10 @@ const myValue = ref('')
const init = ref({
selector: '#' + tinymceId,
toolbar_items_size: 'small',
language_url: '/tinymce-dataease-private/langs/zh_CN.js', // publicstatic
language_url: formatDataEaseBi('/tinymce-dataease-private/langs/zh_CN.js'), // publicstatic
language: 'zh_CN',
skin_url: '/tinymce-dataease-private/skins/ui/oxide', //
content_css: '/tinymce-dataease-private/skins/content/default/content.css',
skin_url: formatDataEaseBi('/tinymce-dataease-private/skins/ui/oxide'), //
content_css: formatDataEaseBi('/tinymce-dataease-private/skins/content/default/content.css'),
plugins:
'advlist autolink link image lists charmap media wordcount table contextmenu directionality pagebreak', //
//

View File

@ -17,6 +17,7 @@
</template>
<script setup lang="ts">
import { formatDataEaseBi } from '@/utils/url'
import tinymce from 'tinymce/tinymce' // tinymcehidden
import Editor from '@tinymce/tinymce-vue' //
import 'tinymce/themes/silver/theme' //
@ -80,10 +81,10 @@ const myValue = ref(element.value.propValue.textValue)
const init = ref({
selector: '#' + tinymceId,
toolbar_items_size: 'small',
language_url: '/tinymce-dataease-private/langs/zh_CN.js', // publicstatic
language_url: formatDataEaseBi('/tinymce-dataease-private/langs/zh_CN.js'), // publicstatic
language: 'zh_CN',
skin_url: '/tinymce-dataease-private/skins/ui/oxide', //
content_css: '/tinymce-dataease-private/skins/content/default/content.css',
skin_url: formatDataEaseBi('/tinymce-dataease-private/skins/ui/oxide'), //
content_css: formatDataEaseBi('/tinymce-dataease-private/skins/content/default/content.css'),
plugins:
'advlist autolink link image lists charmap media wordcount table contextmenu directionality pagebreak', //
//

View File

@ -2122,5 +2122,10 @@ export default {
geometry: '地理信息',
onlinemap: '在线地图',
empty_desc: '请在左侧输入信息然后保存'
},
setting_basic: {
autoCreateUser: '禁止第三方自动创建用户',
dsIntervalTime: '数据源检测时间间隔',
dsExecuteTime: '数据源检测频率'
}
}

View File

@ -12,7 +12,7 @@ const formatterUrl = <T extends Node>(node: T, prefix: string) => {
url = node.src
}
if (url.includes(suffix)) {
if (url.includes(suffix) || url.includes('dataease-private')) {
const currentUrlprefix = new URL(url).origin
const newUrl = url.replace(currentUrlprefix, prefix)
if (node instanceof HTMLLinkElement) {

View File

@ -0,0 +1,22 @@
export const loadScript = (url: string, jsId?: string) => {
return new Promise(function (resolve, reject) {
const scriptId = jsId || 'de-fit2cloud-script-id'
let dom = document.getElementById(scriptId)
if (dom) {
dom.parentElement?.removeChild(dom)
dom = null
}
const script = document.createElement('script')
script.id = scriptId
script.onload = function () {
return resolve(null)
}
script.onerror = function () {
return reject(new Error('Load script from '.concat(url, ' failed')))
}
script.src = url
const head = document.head || document.getElementsByTagName('head')[0]
;(document.body || head).appendChild(script)
})
}

View File

@ -10,19 +10,24 @@ const { canvasStyleData, componentData, canvasViewInfo, canvasViewDataInfo, dvIn
storeToRefs(dvMainStore)
const basePath = import.meta.env.VITE_API_BASEPATH
export function formatterUrl(url: string) {
return url.replace('//de2api', '/de2api')
}
export function imgUrlTrans(url) {
if (url) {
if (typeof url === 'string' && url.indexOf('static-resource') > -1) {
const rawUrl = url
? (basePath.endsWith('/') ? basePath.substring(0, basePath.length - 1) : basePath) + url
: null
return window.DataEaseBi
? `${window.DataEaseBi.baseUrl}${
rawUrl.startsWith('/api') ? rawUrl.slice(5) : rawUrl
}`.replace('com//', 'com/')
: rawUrl
return formatterUrl(
window.DataEaseBi
? `${window.DataEaseBi.baseUrl}${
rawUrl.startsWith('/api') ? rawUrl.slice(5) : rawUrl
}`.replace('com//', 'com/')
: rawUrl
)
} else {
return url.replace('com//', 'com/')
return formatterUrl(url.replace('com//', 'com/'))
}
}
}

View File

@ -0,0 +1,3 @@
export const formatDataEaseBi = (url: string) => {
return window.DataEaseBi?.baseUrl ? `${window.DataEaseBi.baseUrl}${url}` : url
}

View File

@ -67,3 +67,16 @@ export const setColorName = (obj, keyword: string, key?: string, colorKey?: stri
}
obj[colorKey] = null
}
export const getQueryString = (name: string) => {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i')
const r = window.location.search.substr(1).match(reg)
if (r != null) {
return unescape(r[2])
}
return null
}
export const isLarkPlatform = () => {
return !!getQueryString('state') && !!getQueryString('code')
}

View File

@ -21,6 +21,7 @@ const license: F2CLicense = reactive({
remark: '',
isv: ''
})
const tipsSuffix = ref('')
const build = ref('')
const isAdmin = ref(false)
const fileList = reactive([])
@ -88,11 +89,14 @@ const validateHandler = (param, success) => {
validateApi(param).then(success)
}
const getLicense = result => {
if (result.status === 'valid') {
tipsSuffix.value = result.edition === 'Embedded' ? '套' : '个账号'
}
return {
status: result.status,
corporation: result.license ? result.license.corporation : '',
expired: result.license ? result.license.expired : '',
count: result.license ? result.license.count : '',
count: result.license ? result.license.count : 0,
version: result.license ? result.license.version : '',
edition: result.license ? result.license.edition : '',
serialNo: result.license ? result.license.serialNo : '',
@ -145,7 +149,9 @@ const update = (licKey: string) => {
</div>
<div class="item">
<div class="label">{{ $t('about.auth_num') }}</div>
<div class="value">{{ license.count }}</div>
<div class="value">
{{ license.status === 'valid' ? `${license.count} ${tipsSuffix}` : '' }}
</div>
</div>
<div class="item">
<div class="label">{{ $t('about.version') }}</div>

View File

@ -18,9 +18,8 @@ import { storeToRefs } from 'pinia'
import { BASE_VIEW_CONFIG } from '../util/chart'
import { cloneDeep, defaultsDeep } from 'lodash-es'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
const { nowPanelTrackInfo, nowPanelJumpInfo } = storeToRefs(dvMainStore)
const { nowPanelTrackInfo, nowPanelJumpInfo, dvInfo } = storeToRefs(dvMainStore)
const { t } = useI18n()
const linkJumpRef = ref(null)
@ -154,7 +153,7 @@ const noSenior = computed(() => {
const linkJumpActiveChange = () => {
//
const params = {
sourceDvId: chart.value.sceneId,
sourceDvId: dvInfo.value.id,
sourceViewId: chart.value.id,
activeStatus: chart.value.jumpActive
}
@ -164,7 +163,7 @@ const linkJumpActiveChange = () => {
}
const linkageActiveChange = () => {
const params = {
dvId: chart.value.sceneId,
dvId: dvInfo.value.id,
sourceViewId: chart.value.id,
activeStatus: chart.value.linkageActive
}

View File

@ -421,10 +421,8 @@ export const getGeoJsonFile = async (areaId: string): Promise<FeatureCollection>
const mapStore = useMapStoreWithOut()
let geoJson = mapStore.mapCache[areaId]
if (!geoJson) {
const country = areaId.slice(0, 3)
geoJson = await getGeoJson(country, areaId).then(result => {
return result.data
})
const res = await getGeoJson(areaId)
geoJson = res.data
mapStore.setMap({ id: areaId, geoJson })
}
return toRaw(geoJson)

View File

@ -16,6 +16,7 @@ import { logoutHandler } from '@/utils/logout'
import DeImage from '@/assets/login-desc-de.png'
import elementResizeDetectorMaker from 'element-resize-detector'
import PreheatImage from '@/assets/preheat.png'
import { isLarkPlatform } from '@/utils/utils'
const { wsCache } = useCache()
const appStore = useAppStoreWithOut()
const userStore = useUserStoreWithOut()
@ -129,7 +130,7 @@ const showLoginImage = computed<boolean>(() => {
const checkPlatform = () => {
const flagArray = ['/casbi', 'oidcbi']
const pathname = window.location.pathname
if (!flagArray.some(flag => pathname.includes(flag))) {
if (!flagArray.some(flag => pathname.includes(flag)) && !isLarkPlatform()) {
cleanPlatformFlag()
}
}

View File

@ -2,7 +2,7 @@
<div class="info-template-container">
<div class="info-template-header">
<div class="info-template-title">
<span>基础设置</span>
<span>{{ curTitle }}</span>
</div>
<div>
<el-button type="primary" @click="edit">{{ t('commons.edit') }}</el-button>
@ -11,7 +11,7 @@
<div class="info-template-content">
<div class="info-content-item" v-for="item in settingList" :key="item.pkey">
<div class="info-item-label">
<span>{{ item.pkey }}</span>
<span>{{ t(item.pkey) }}</span>
<el-tooltip
v-if="tooltipItem[item.pkey]"
effect="dark"
@ -33,6 +33,9 @@
</el-icon>
</el-tooltip>
</div>
<span v-else-if="item.pkey.includes('basic.dsIntervalTime')">
<span>{{ item.pval + ' ' + executeTime + '执行一次' }}</span>
</span>
<span v-else>{{ item.pval }}</span>
</div>
</div>
@ -40,7 +43,7 @@
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, PropType } from 'vue'
import { ref, defineProps, PropType, computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { SettingRecord, ToolTipRecord } from './SettingTemplate'
const { t } = useI18n()
@ -52,89 +55,47 @@ const props = defineProps({
labelTooltips: {
type: Array as PropType<ToolTipRecord[]>,
default: () => []
},
settingData: {
type: Array as PropType<SettingRecord[]>,
default: () => []
},
settingTitle: {
type: String,
default: '基础设置'
}
})
const executeTime = ref('0分0秒')
const curTitle = computed(() => {
return props.settingTitle
})
const loadList = () => {
settingList.value = []
if (props.settingData?.length) {
props.settingData.forEach(item => {
if (item.pkey.includes('basic.dsExecuteTime')) {
executeTime.value = getExecuteTime(item.pval)
} else {
settingList.value.push(item)
}
})
}
}
const getExecuteTime = val => {
const options = [
{ value: 'minute', label: '分钟执行时间0秒' },
{ value: 'hour', label: '小时执行时间0分0秒' }
]
return options.filter(item => item.value === val)[0].label
}
const settingList = ref([] as SettingRecord[])
const loadBasic = () => {
settingList.value.push({
pkey: '请求超时时间',
pval: '100',
type: 'text',
sort: 1
})
settingList.value.push({
pkey: '数据源检测时间间隔',
pval: '100',
type: 'text',
sort: 2
})
settingList.value.push({
pkey: '默认登录方式',
pval: '普通登录',
type: 'text',
sort: 3
})
settingList.value.push({
pkey: '默认密码',
pval: 'DataEase@123456',
type: 'pwd',
sort: 4
})
}
const loadEmail = () => {
settingList.value.push({
pkey: 'SMTP主机',
pval: 'smtp.exmail.qq.com',
type: 'text',
sort: 1
})
settingList.value.push({
pkey: 'SMTP端口',
pval: '465',
type: 'text',
sort: 2
})
settingList.value.push({
pkey: 'SMTP账户',
pval: 'test@fit2cloud.com',
type: 'text',
sort: 3
})
settingList.value.push({
pkey: 'SMTP密码',
pval: 'DataEase@123456',
type: 'pwd',
sort: 4
})
settingList.value.push({
pkey: '测试收件人',
pval: 'yawen.chen@fit2cloud.com',
type: 'pwd',
sort: 5
})
settingList.value.push({
pkey: 'SSL',
pval: '开启',
type: 'text',
sort: 6
})
settingList.value.push({
pkey: 'TSL',
pval: '未开启',
type: 'text',
sort: 7
})
}
const init = () => {
if (props.settingKey === 'basic') {
loadBasic()
}
if (props.settingKey === 'email') {
loadEmail()
if (props.settingData?.length) {
loadList()
}
}
const pwdItem = ref({})
@ -163,6 +124,9 @@ const emits = defineEmits(['edit'])
const edit = () => {
emits('edit')
}
defineExpose({
init
})
init()
formatPwd()
formatLabel()

View File

@ -3,51 +3,57 @@ import { ref, reactive } from 'vue'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import request from '@/config/axios'
const { t } = useI18n()
const dialogVisible = ref(false)
const loadingInstance = ref(null)
const createUserForm = ref<FormInstance>()
const basicForm = ref<FormInstance>()
const options = [
{ value: 'minute', label: '分钟执行时间0秒' },
{ value: 'hour', label: '小时执行时间0分0秒' }
]
const state = reactive({
roleList: [],
form: reactive<UserForm>({
id: null,
account: null,
name: null,
email: null,
enable: true,
phone: null,
phonePrefix: '+86',
roleIds: []
})
form: reactive({
dsIntervalTime: '30',
dsExecuteTime: 'minute'
}),
settingList: []
})
const rule = reactive<FormRules>({})
const rule = reactive<FormRules>({
dsIntervalTime: [
{
required: true,
message: t('common.require'),
trigger: 'blur'
}
]
})
const edit = () => {
dialogVisible.value = true
// queryForm()
}
/* const queryForm = () => {
showLoading()
personInfoApi().then(res => {
state.form = reactive<UserForm>(res.data)
closeLoading()
const buildSettingList = () => {
return state.settingList.map(item => {
const pkey = item.pkey.startsWith('basic.') ? item.pkey : `basic.${item.pkey}`
const sort = item.sort
const type = item.type
const pval = state.form[item.pkey]
return { pkey, pval, type, sort }
})
} */
}
const emits = defineEmits(['saved'])
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const param = { ...state.form }
const method = null
const param = buildSettingList()
if (param.length < 2) {
return
}
showLoading()
method(param)
request
.post({ url: '/sysParameter/basic/save', data: param })
.then(res => {
if (!res.msg) {
ElMessage.success(t('common.save_success'))
emits('saved')
reset()
}
@ -63,13 +69,14 @@ const submitForm = async (formEl: FormInstance | undefined) => {
}
const resetForm = (formEl: FormInstance | undefined) => {
state.settingList = []
if (!formEl) return
formEl.resetFields()
dialogVisible.value = false
}
const reset = () => {
resetForm(createUserForm.value)
resetForm(basicForm.value)
}
const showLoading = () => {
@ -78,6 +85,19 @@ const showLoading = () => {
const closeLoading = () => {
loadingInstance.value?.close()
}
const edit = list => {
state.settingList = list.map(item => {
const pkey = item.pkey
item['label'] = `setting_${pkey}`
item['pkey'] = pkey.split('.')[1]
let pval = item.pval
state.form[item['pkey']] = pval || state.form[item['pkey']]
return item
})
console.log(state.settingList)
dialogVisible.value = true
}
defineExpose({
edit
})
@ -92,32 +112,83 @@ defineExpose({
direction="rtl"
>
<el-form
ref="createUserForm"
ref="basicForm"
require-asterisk-position="right"
:model="state.form"
:rules="rule"
label-width="80px"
label-position="top"
>
<el-form-item label="请求超时时间" prop="name">
<el-input
v-model="state.form.name"
:placeholder="t('common.please_input') + t('user.name')"
<el-form-item
v-for="item in state.settingList"
:key="item.pkey"
:prop="item.pkey"
:class="{ 'setting-hidden-item': item.pkey === 'dsExecuteTime' }"
:label="t(item.label)"
>
<el-switch
v-if="item.pkey === 'autoCreateUser'"
active-value="true"
inactive-value="false"
v-model="state.form[item.pkey]"
/>
<div v-else-if="item.pkey === 'dsIntervalTime'" class="ds-task-form-inline">
<span></span>
<el-input-number
v-model="state.form.dsIntervalTime"
autocomplete="off"
step-strictly
class="text-left"
:min="1"
:placeholder="t('common.inputText')"
controls-position="right"
type="number"
/>
<el-select v-model="state.form.dsExecuteTime">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span class="ds-span">执行一次</span>
</div>
<div v-else />
</el-form-item>
<!-- <el-form-item label="禁止扫码创建用户" prop="autoCreateUser">
<el-switch v-model="state.form.autoCreateUser" />
</el-form-item> -->
<el-form-item label="数据源检测时间间隔" prop="account">
<el-input
v-model="state.form.account"
:placeholder="t('common.please_input') + t('common.account')"
disabled
/>
</el-form-item>
<!-- <el-form-item label="数据源检测时间间隔" prop="dsIntervalTime">
<div class="ds-task-form-inline">
<span></span>
<el-input-number
v-model="state.form.dsIntervalTime"
autocomplete="off"
step-strictly
class="text-left"
:min="1"
:placeholder="t('common.inputText')"
controls-position="right"
type="number"
/>
<el-select v-model="state.form.dsExecuteTime">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span class="ds-span">执行一次</span>
</div>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="resetForm(createUserForm)">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="submitForm(createUserForm)">
<el-button @click="resetForm(basicForm)">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="submitForm(basicForm)">
{{ t('commons.save') }}
</el-button>
</span>
@ -125,47 +196,25 @@ defineExpose({
</el-drawer>
</template>
<style lang="less">
.basic-info-drawer {
.editer-form-title {
width: 100%;
border-radius: 4px;
background: #e1eaff;
margin: -8px 0 16px 0;
height: 40px;
padding-left: 16px;
i {
color: #3370ff;
font-size: 14.666666030883789px;
}
.pwd {
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 22px;
text-align: left;
}
.pwd {
margin: 0 8px;
color: #1f2329;
<style scoped lang="less">
.setting-hidden-item {
display: none !important;
}
.ds-task-form-inline {
width: 100%;
display: flex;
.ed-input-number {
width: 140px;
margin: 0 6px;
}
.ed-select {
width: 240px;
:deep(.ed-input) {
width: 100% !important;
}
}
.input-with-select {
.ed-input-group__prepend {
width: 72px;
background-color: #fff;
padding: 0 20px;
color: #1f2329;
text-align: center;
font-family: PingFang SC;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
span.ds-span {
margin-left: 6px;
}
}
</style>

View File

@ -1,22 +1,63 @@
<template>
<InfoTemplate :label-tooltips="tooltips" setting-key="basic" @edit="edit" />
<basic-edit ref="editor" />
<InfoTemplate
ref="infoTemplate"
:label-tooltips="tooltips"
setting-key="basic"
setting-title="基础设置"
:setting-data="state.templateList"
@edit="edit"
/>
<basic-edit ref="editor" @saved="refresh" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import InfoTemplate from '../../common/InfoTemplate.vue'
import BasicEdit from './BasicEdit.vue'
import request from '@/config/axios'
import { SettingRecord } from '@/views/system/common/SettingTemplate'
import { reactive } from 'vue'
import { cloneDeep } from 'lodash-es'
const editor = ref()
const infoTemplate = ref()
const tooltips = [
{
key: '请求超时时间',
val: '请求超时时间(单位:秒,注意:保存后刷新浏览器生效)'
}
]
const state = reactive({
templateList: [] as SettingRecord[]
})
let originData = []
const search = cb => {
const url = '/sysParameter/basic/query'
originData = []
state.templateList = []
request.get({ url }).then(res => {
originData = cloneDeep(res.data)
const data = res.data
for (let index = 0; index < data.length; index++) {
const item = data[index]
if (item.pkey === 'basic.autoCreateUser') {
item.pval = item.pval === 'true' ? '开启' : '未开启'
} else {
item.pval = item.pval
}
item.pkey = 'setting_' + item.pkey
state.templateList.push(item)
}
cb && cb()
})
}
const refresh = () => {
search(() => {
infoTemplate?.value.init()
})
}
refresh()
const edit = () => {
editor?.value.edit()
editor?.value.edit(cloneDeep(originData))
}
</script>

View File

@ -1,5 +1,10 @@
<template>
<InfoTemplate :label-tooltips="tooltips" setting-key="email" @edit="edit" />
<InfoTemplate
:label-tooltips="tooltips"
setting-title="邮件设置"
setting-key="email"
@edit="edit"
/>
<email-edit ref="editor" />
</template>

View File

@ -7,7 +7,7 @@
<div class="container-sys-param">
<map-setting v-if="activeName === 'map'" />
<basic-info v-if="activeName === 'basic'" />
<email-info v-if="activeName === 'email'" />
<!-- <email-info v-if="activeName === 'email'" /> -->
</div>
</div>
</template>
@ -17,12 +17,12 @@ import { ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import MapSetting from './map/MapSetting.vue'
import BasicInfo from './basic/BasicInfo.vue'
import EmailInfo from './email/EmailInfo.vue'
/* import EmailInfo from './email/EmailInfo.vue' */
const { t } = useI18n()
const tabArray = [
{ label: '基础设置', name: 'basic' },
{ label: '邮件设置', name: 'email' },
/* { label: '邮件设置', name: 'email' }, */
{ label: '地图设置', name: 'map' }
/* {label: '引擎设置', name: 'engine'}, */
]

View File

@ -3,7 +3,7 @@
<el-aside class="geonetry-aside">
<div class="geo-title">
<span>{{ t('online_map.geometry') }}</span>
<span class="add-icon-span">
<span class="add-icon-span" @click="add()">
<el-icon>
<Icon name="icon_add_outlined"></Icon>
</el-icon>
@ -43,6 +43,19 @@
:title="data.name"
v-html="data.colorName && keyword ? data.colorName : data.name"
/>
<span class="geo-operate-container">
<el-tooltip
v-if="data.custom"
class="box-item"
effect="dark"
:content="t('common.delete')"
placement="top"
>
<el-icon @click.stop="delHandler(data)" class="hover-icon">
<Icon name="icon_delete-trash_outlined"></Icon>
</el-icon>
</el-tooltip>
</span>
</span>
</template>
</el-tree>
@ -83,6 +96,7 @@
</div>
</el-main>
</el-container>
<geometry-edit ref="editor" @saved="loadTreeData(false)" />
</template>
<script lang="ts" setup>
@ -93,15 +107,21 @@ import EmptyBackground from '@/components/empty-background/src/EmptyBackground.v
import { getGeoJsonFile } from '@/views/chart/components/js/util'
import { cloneDeep } from 'lodash-es'
import { setColorName } from '@/utils/utils'
import GeometryEdit from './GeometryEdit.vue'
import { useCache } from '@/hooks/web/useCache'
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import request from '@/config/axios'
const { wsCache } = useCache()
const { t } = useI18n()
const keyword = ref('')
const treeData = ref([])
const editor = ref()
interface Tree {
label: string
children?: Tree[]
}
const areaTreeRef = ref(null)
const loading = ref(false)
const selectedData = ref(null)
const handleNodeClick = async (data: Tree) => {
@ -116,6 +136,29 @@ const handleNodeClick = async (data: Tree) => {
}
}
}
const delHandler = data => {
ElMessageBox.confirm('确定删除此节点吗', {
confirmButtonType: 'danger',
type: 'warning',
confirmButtonText: t('common.delete'),
cancelButtonText: t('dataset.cancel'),
autofocus: false,
showClose: false
})
.then(() => {
const url = '/geometry/delete/' + data.id
request.post({ url }).then(() => {
if (selectedData.value?.id === data.id) {
selectedData.value = null
}
ElMessage.success(t('common.delete_success'))
loadTreeData(false)
})
})
.catch(() => {
loading.value = false
})
}
const filterResource = val => {
areaTreeRef.value?.filter(val)
}
@ -125,18 +168,29 @@ const filterResourceNode = (value: string, data) => {
return data.name.toLocaleLowerCase().includes(value.toLocaleLowerCase())
}
const loadTreeData = () => {
const loadTreeData = (cache?: boolean) => {
const key = 'de-area-tree'
const result = wsCache.get(key)
if (result && cache) {
treeData.value = result
return
}
getWorldTree()
.then(res => {
const root = res.data
treeData.value = [root]
wsCache.set(key, treeData.value)
})
.catch(e => {
console.error(e)
})
}
loadTreeData()
const add = (pid?: string) => {
editor?.value.edit(pid)
}
loadTreeData(true)
</script>
<style lang="less" scoped>
@ -158,7 +212,6 @@ loadTreeData()
line-height: 24px;
}
.add-icon-span {
display: none;
color: #3370ff;
height: 20px;
width: 20px;
@ -250,5 +303,16 @@ loadTreeData()
box-sizing: content-box;
padding-right: 4px;
overflow: hidden;
justify-content: space-between;
.geo-operate-container {
display: none;
}
&:hover {
.geo-operate-container {
display: contents;
}
}
}
</style>

View File

@ -0,0 +1,248 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import type {
FormInstance,
FormRules,
UploadRequestOptions,
UploadProps
} from 'element-plus-secondary'
import request from '@/config/axios'
import { GeometryFrom } from './interface'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const { t } = useI18n()
const dialogVisible = ref(false)
const loadingInstance = ref(null)
const geoForm = ref<FormInstance>()
const geoFile = ref()
const fileName = ref()
const state = reactive({
form: reactive<GeometryFrom>({
pid: null,
code: null,
name: null
}),
treeData: []
})
const treeProps = {
value: 'id',
label: 'name',
disabled: 'readOnly'
}
const rule = reactive<FormRules>({
pid: [
{
required: true,
message: t('common.require'),
trigger: 'blur'
}
],
code: [
{
required: true,
message: t('common.require'),
trigger: 'blur'
}
],
name: [
{
required: true,
message: t('common.require'),
trigger: 'blur'
}
]
})
const edit = (pid?: string) => {
const key = 'de-area-tree'
state.treeData = wsCache.get(key)
state.form.pid = pid
state.form.code = null
state.form.name = null
geoFile.value = null
fileName.value = null
dialogVisible.value = true
}
const emits = defineEmits(['saved'])
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const param = { ...state.form }
const formData = buildFormData(geoFile.value, param)
showLoading()
request
.post({ url: '/geometry/save', data: formData, headersType: 'multipart/form-data;' })
.then(res => {
if (!res.msg) {
ElMessage.success(t('common.save_success'))
emits('saved')
reset()
}
closeLoading()
})
.catch(() => {
closeLoading()
})
} else {
console.log('error submit!', fields)
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
geoFile.value = null
fileName.value = null
formEl.resetFields()
dialogVisible.value = false
}
const reset = () => {
resetForm(geoForm.value)
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({ target: '.basic-info-drawer' })
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning(t('userimport.exceedMsg'))
}
const handleError = () => {
ElMessage.warning('执行失败请联系管理员')
}
const setFile = (options: UploadRequestOptions) => {
geoFile.value = options.file
fileName.value = options.file.name
}
const uploadValidate = file => {
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
if (suffix !== 'json') {
ElMessage.warning('只能上传json文件')
return false
}
if (file.size / 1024 / 1024 > 200) {
ElMessage.warning('最大上传200M')
return false
}
return true
}
const buildFormData = (file, param) => {
const formData = new FormData()
if (file) {
formData.append('file', file)
}
formData.append('request', new Blob([JSON.stringify(param)], { type: 'application/json' }))
return formData
}
defineExpose({
edit
})
</script>
<template>
<el-drawer
title="地理信息"
v-model="dialogVisible"
custom-class="basic-info-drawer"
size="600px"
direction="rtl"
>
<el-form
ref="geoForm"
require-asterisk-position="right"
:model="state.form"
:rules="rule"
label-width="80px"
label-position="top"
>
<el-form-item label="上级区域" prop="pid">
<el-tree-select
class="map-tree-selector"
node-key="id"
v-model="state.form.pid"
:props="treeProps"
:data="state.treeData"
check-strictly
:render-after-expand="false"
:placeholder="t('common.please_select')"
/>
</el-form-item>
<el-form-item label="区域代码" prop="code">
<el-input v-model="state.form.code" />
</el-form-item>
<el-form-item label="区域名称" prop="name">
<el-input v-model="state.form.name" />
</el-form-item>
<div class="geo-label-mask" />
<el-form-item label="坐标文件">
<el-upload
class="upload-geo"
action=""
accept=".json"
:on-exceed="handleExceed"
:before-upload="uploadValidate"
:on-error="handleError"
:show-file-list="false"
:http-request="setFile"
>
<el-input :placeholder="t('userimport.placeholder')" readonly v-model="fileName">
<template #suffix>
<el-icon>
<Icon name="icon_upload_outlined" />
</el-icon>
</template>
<template #prefix>
<el-icon v-if="!!fileName">
<Icon name="de-json" />
</el-icon>
</template>
</el-input>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="resetForm(geoForm)">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="submitForm(geoForm)">
{{ t('commons.save') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<style scoped lang="less">
.map-tree-selector {
width: 100%;
}
.geo-btn-container {
position: absolute;
bottom: 33px;
right: 0px;
}
.upload-geo {
width: 100%;
height: 32px;
:deep(.ed-upload) {
width: 100% !important;
}
}
.geo-label-mask {
position: absolute;
width: calc(100% - 48px);
height: 30px;
}
</style>

View File

@ -0,0 +1,5 @@
export interface GeometryFrom {
pid?: string
code?: string
name?: string
}

View File

@ -517,7 +517,7 @@ initMarketTemplate()
}
.template-market-dashboard {
width: calc(100% - 384px);
width: calc(100% - 376px);
height: 100%;
.template-market {

@ -1 +1 @@
Subproject commit a0ed462213c8f274e09305cdc2ab0b238e9a2547
Subproject commit e31c9212056eae3656aa534e3cc778d00001fe98

View File

@ -66,6 +66,7 @@ curl http://127.0.0.1:9180/apisix/admin/services/10 -X PUT -H "X-API-KEY: $DE_AP
"X-DE-TOKEN",
"X-DE-LINK-TOKEN",
"X-EMBEDDED-TOKEN",
"X-DE-ASK-TOKEN",
"Content-Type"
],
"request_method": "POST",

View File

@ -11,6 +11,7 @@ services:
- ${DE_BASE}/dataease2.0/logs:/opt/dataease2.0/logs
- ${DE_BASE}/dataease2.0/data/static-resource:/opt/dataease2.0/data/static-resource
- ${DE_BASE}/dataease2.0/cache:/opt/dataease2.0/cache
- ${DE_BASE}/dataease2.0/data/geo:/opt/dataease2.0/data/geo
depends_on:
DE_MYSQL_HOST:
condition: service_healthy

View File

@ -11,7 +11,6 @@ spring:
username: ${DE_MYSQL_USER}
password: ${DE_MYSQL_PASSWORD}
dataease:
origin-list: localhost:8080,localhost:8100,localhost:9080
apisix-api:
domain: http://apisix:9180
key: DE_APISIX_KEY

View File

@ -25,7 +25,7 @@
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<h2.version>1.4.199</h2.version>
<knife4j.version>4.1.0</knife4j.version>
<calcite-core.version>1.35.0</calcite-core.version>
<calcite-core.version>1.36.0</calcite-core.version>
<commons-dbcp2.version>2.6.0</commons-dbcp2.version>
<antlr.version>3.5.2</antlr.version>
<java-jwt.version>3.12.1</java-jwt.version>

View File

@ -0,0 +1,21 @@
package io.dataease.api.lark.api;
import io.dataease.api.lark.dto.LarkTokenRequest;
import io.dataease.api.lark.vo.LarkInfoVO;
import io.dataease.api.lark.dto.LarkSettingCreator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public interface LarkApi {
@GetMapping("/info")
LarkInfoVO info();
@PostMapping("/create")
void save(@RequestBody LarkSettingCreator creator);
@PostMapping("/token")
String larkToken(@RequestBody LarkTokenRequest request);
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.lark.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkSettingCreator implements Serializable {
private String appId;
private String appSecret;
private String callBack;
private Boolean enable;
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.lark.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkTokenRequest implements Serializable {
private String code;
private String state;
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.lark.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkInfoVO implements Serializable {
private String appId;
private String appSecret;
private String callBack;
private Boolean enable = false;
}

View File

@ -0,0 +1,18 @@
package io.dataease.api.map;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
@Tag(name = "地理信息")
public interface GeoApi {
@PostMapping(value = "/save", consumes = {"multipart/form-data"})
void saveMapGeo(@RequestPart("request") GeometryNodeCreator request, @RequestPart(value = "file") MultipartFile file);
@PostMapping("/delete/{id}")
void deleteGeo(@PathVariable("id") String id);
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.map.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class GeometryNodeCreator implements Serializable {
private String code;
private String name;
private String pid;
}

View File

@ -18,6 +18,7 @@ public class AreaNode implements Serializable {
private String level;
private String name;
private String pid;
private boolean custom = false;
/**
* 国家代码
*/

View File

@ -1,11 +1,14 @@
package io.dataease.api.system;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface SysParameterApi {
@GetMapping("/singleVal/{key}")
@ -17,4 +20,10 @@ public interface SysParameterApi {
@GetMapping("/queryOnlineMap")
String queryOnlineMap();
@GetMapping("basic/query")
List<SettingItemVO> queryBasicSetting();
@PostMapping("/basic/save")
void saveBasicSetting(@RequestBody List<SettingItemVO> settingItemVOS);
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.system.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class SettingItemVO implements Serializable {
private String pkey;
private String pval;
private String type;
private Integer sort;
}

View File

@ -0,0 +1,25 @@
package io.dataease.api.permissions.apikey.api;
import io.dataease.api.permissions.apikey.dto.ApikeyEnableEditor;
import io.dataease.api.permissions.apikey.vo.ApiKeyVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface ApiKeyApi {
@PostMapping("/generate")
void generate();
@GetMapping("/query")
List<ApiKeyVO> query();
@PostMapping("/switch")
void switchEnable(@RequestBody ApikeyEnableEditor editor);
@PostMapping("/delete/{id}")
void delete(@PathVariable("id") Long id);
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.permissions.apikey.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApikeyEnableEditor implements Serializable {
private Long id;
private Boolean enable = false;
}

View File

@ -0,0 +1,22 @@
package io.dataease.api.permissions.apikey.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiKeyVO implements Serializable {
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
private String accessKey;
private String accessSecret;
private Boolean enable;
private Long createTime;
}

View File

@ -27,4 +27,7 @@ public interface EmbeddedApi {
@PostMapping("/reset")
void reset(@RequestBody EmbeddedResetRequest request);
@GetMapping("/domainList")
List<String> domainList();
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.permissions.setting.api;
import io.dataease.api.permissions.setting.vo.PerSettingItemVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface PerSettingApi {
@GetMapping("/basic/query")
List<PerSettingItemVO> basicSetting();
@PostMapping("/baisc/save")
void saveBasic(@RequestBody List<Object> settings);
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.permissions.setting.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class PerSettingItemVO implements Serializable {
private String pkey;
private String pval;
private String type;
private Integer sort;
}

View File

@ -1,6 +1,7 @@
package io.dataease.auth.interceptor;
import io.dataease.constant.AuthConstant;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
@ -13,12 +14,16 @@ import java.util.List;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Value("#{'${dataease.origin-list}'.split(',')}")
@Resource(name = "deCorsInterceptor")
private CorsInterceptor corsInterceptor;
@Value("#{'${dataease.origin-list:http://127.0.0.1:8100}'.split(',')}")
private List<String> originList;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CorsInterceptor(originList)).addPathPatterns("/**");
corsInterceptor.addOriginList(originList);
registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
}
@Override

View File

@ -1,27 +1,73 @@
package io.dataease.auth.interceptor;
import cn.hutool.core.util.ReflectUtil;
import io.dataease.utils.CommonBeanFactory;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.ArrayList;
import java.util.List;
@Component("deCorsInterceptor")
public class CorsInterceptor implements HandlerInterceptor {
private List<String> originList;
private final List<String> originList;
private final List<String> busiOriginList = new ArrayList<>();
private Class<?> aClass;
private Object bean;
public CorsInterceptor(List<String> originList) {
this.originList = originList;
}
public void addOriginList(List<String> list) {
List<String> strings = list.stream().filter(item -> !originList.contains(item)).toList();
originList.addAll(strings);
}
public void addOriginList() {
String className = "io.dataease.api.permissions.embedded.api.EmbeddedApi";
String methodName = "domainList";
if (ObjectUtils.isEmpty(aClass)) {
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
return;
}
}
if (ObjectUtils.isEmpty(bean)) {
bean = CommonBeanFactory.getBean(aClass);
}
if (ObjectUtils.isNotEmpty(bean)) {
Object result = ReflectUtil.invoke(bean, methodName);
if (ObjectUtils.isNotEmpty(result)) {
List<String> list = (List<String>) result;
if (CollectionUtils.isNotEmpty(list)) {
List<String> strings = list.stream().filter(item -> !busiOriginList.contains(item)).toList();
busiOriginList.addAll(strings);
}
}
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
addOriginList();
String origin = request.getHeader("Origin");
boolean embedded = StringUtils.startsWithAny(request.getRequestURI(), "/assets/", "/js/");
if ((StringUtils.isNotBlank(origin) && originList.contains(origin)) || embedded) {
if ((StringUtils.isNotBlank(origin) && originList.contains(origin)) || busiOriginList.contains(origin) || embedded) {
response.setHeader("Access-Control-Allow-Origin", embedded ? "*" : origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS");

View File

@ -18,6 +18,7 @@ public class AuthConstant {
public final static String USER_IMPORT_ERROR_KEY = "USER-IMPORT-ERROR-KEY";
public final static String LINK_TOKEN_KEY = "X-DE-LINK-TOKEN";
public final static String ASK_TOKEN_KEY = "X-DE-ASK-TOKEN";
public final static String DE_EXECUTE_VERSION = "X-DE-EXECUTE-VERSION";

View File

@ -16,8 +16,10 @@ public class StaticResourceConstants {
public static String WORK_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "static-resource" + FILE_SEPARATOR;
public static String MAP_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "map";
public static String CUSTOM_MAP_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "geo";
public static String MAP_URL = "/map";
public static String GEO_URL = "/geo";
/**
* Upload prefix.

View File

@ -0,0 +1,6 @@
package io.dataease.constant;
public class XpackSettingConstants {
public static final String AUTO_CREATE_USER = "basic.autoCreateUser";
}

View File

@ -0,0 +1,36 @@
package io.dataease.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import static java.nio.charset.StandardCharsets.UTF_8;
public class AesUtils {
public static String aesDecrypt(String src, String secretKey, String iv) {
if (StringUtils.isBlank(secretKey)) {
throw new RuntimeException("secretKey is empty");
}
try {
byte[] raw = secretKey.getBytes(UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv1);
byte[] encrypted1 = Base64.decodeBase64(src);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, UTF_8);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// 解密的原字符串为非加密字符串则直接返回原字符串
return src;
} catch (Exception e) {
throw new RuntimeException("decrypt errorplease check parameters", e);
}
}
}

View File

@ -148,7 +148,7 @@ public class HttpClientUtil {
public static String post(String url, String json, HttpClientConfig config) {
CloseableHttpClient httpClient = null;
try {
buildHttpClient(url);
httpClient = buildHttpClient(url);
HttpPost httpPost = new HttpPost(url);
if (config == null) {
config = new HttpClientConfig();

View File

@ -0,0 +1,14 @@
package io.dataease.utils;
import cn.hutool.core.collection.ListUtil;
import io.dataease.constant.XpackSettingConstants;
import java.util.List;
public class SystemSettingUtils {
public static boolean xpackSetting(String pkey) {
List<String> xpackSettingList = ListUtil.toList(XpackSettingConstants.AUTO_CREATE_USER);
return xpackSettingList.contains(pkey);
}
}

View File

@ -19,6 +19,8 @@ public class WhitelistUtils {
"/swagger-resources",
"/doc.html",
"/panel.html",
"/lark/info",
"/lark/token",
"/setting/authentication/status",
"/");
@ -34,6 +36,7 @@ public class WhitelistUtils {
|| StringUtils.startsWithAny(requestURI, "/static-resource/")
|| StringUtils.startsWithAny(requestURI, "/share/proxyInfo")
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content/")
|| StringUtils.startsWithAny(requestURI, "/geo/")
|| StringUtils.startsWithAny(requestURI, "/map/");
}
}