mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 07:25:54 +08:00
Fix 3.1.3 (#548)
* fix 3.1.3 * fix: file share for txt * fix: ShowStyles_Comps * better: DataListCategory * better dash darkmode * fix: 字段聚合 清空字段值以后无法找到记录 * fix: 分组聚合 清空分组字段后无法找到记录 * commercial11 * fix: 名称字段无法脱敏 * better: 无权限不允许使用自有配置 * fix: styles * enh: 触发器列表显示目标实体 * better LOCK
This commit is contained in:
parent
a00d5f1ea8
commit
0e88bfa6f1
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit c3184f8196c0835c70942a2b9723838eebb2ebb6
|
||||
Subproject commit 54904a28b5177b65ed14613d98f92a4ce912be37
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.1.3</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<!-- UNCOMMENT USE TOMCAT -->
|
||||
|
|
|
@ -67,11 +67,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "3.1.2";
|
||||
public static final String VER = "3.1.3";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 3010209;
|
||||
public static final int BUILD = 3010310;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -10,8 +10,9 @@ package com.rebuild.core.configuration.general;
|
|||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.RebuildException;
|
||||
import com.rebuild.core.configuration.ConfigBean;
|
||||
import com.rebuild.core.configuration.ConfigurationException;
|
||||
import com.rebuild.core.privileges.bizz.ZeroEntry;
|
||||
|
||||
/**
|
||||
* 基础布局管理
|
||||
|
@ -36,14 +37,13 @@ public class BaseLayoutManager extends ShareToManager {
|
|||
public static final String TYPE_LISTSTATS = "LISTSTATS";
|
||||
// 列表-查询面板
|
||||
public static final String TYPE_LISTFILTERPANE = "LISTFILTERPANE";
|
||||
// 列表-图表 of Widget
|
||||
// 列表-图表
|
||||
public static final String TYPE_WCHARTS = "WCHARTS";
|
||||
// 视图-相关项
|
||||
public static final String TYPE_TAB = "TAB";
|
||||
// 视图-新建相关
|
||||
public static final String TYPE_ADD = "ADD";
|
||||
|
||||
|
||||
@Override
|
||||
protected String getConfigEntity() {
|
||||
return "LayoutConfig";
|
||||
|
@ -82,13 +82,19 @@ public class BaseLayoutManager extends ShareToManager {
|
|||
* @return
|
||||
*/
|
||||
public ConfigBean getLayout(ID user, String belongEntity, String applyType) {
|
||||
ID detected = detectUseConfig(user, belongEntity, applyType);
|
||||
if (detected == null) {
|
||||
return null;
|
||||
// 221125 无权限不允许使用自有配置
|
||||
boolean firstUseSelf = true;
|
||||
if (TYPE_NAV.equals(applyType)) {
|
||||
firstUseSelf = Application.getPrivilegesManager().allow(user, ZeroEntry.AllowCustomNav);
|
||||
} else if (TYPE_DATALIST.equals(applyType)) {
|
||||
firstUseSelf = Application.getPrivilegesManager().allow(user, ZeroEntry.AllowCustomDataList);
|
||||
}
|
||||
|
||||
ID detected = detectUseConfig(user, belongEntity, applyType, firstUseSelf);
|
||||
if (detected == null) return null;
|
||||
|
||||
Object[][] cached = getAllConfig(belongEntity, applyType);
|
||||
return findEntry(cached, detected);
|
||||
return findConfigBean(cached, detected);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,12 +106,10 @@ public class BaseLayoutManager extends ShareToManager {
|
|||
"select belongEntity,applyType from LayoutConfig where configId = ?")
|
||||
.setParameter(1, cfgid)
|
||||
.unique();
|
||||
if (o == null) {
|
||||
throw new RebuildException("No config found : " + cfgid);
|
||||
}
|
||||
if (o == null) throw new ConfigurationException("No config found : " + cfgid);
|
||||
|
||||
Object[][] cached = getAllConfig((String) o[0], (String) o[1]);
|
||||
return findEntry(cached, cfgid);
|
||||
return findConfigBean(cached, cfgid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +117,7 @@ public class BaseLayoutManager extends ShareToManager {
|
|||
* @param cfgid
|
||||
* @return
|
||||
*/
|
||||
protected ConfigBean findEntry(Object[][] uses, ID cfgid) {
|
||||
protected ConfigBean findConfigBean(Object[][] uses, ID cfgid) {
|
||||
for (Object[] c : uses) {
|
||||
if (c[0].equals(cfgid)) {
|
||||
return new ConfigBean()
|
||||
|
|
|
@ -64,13 +64,18 @@ public class DataListCategory {
|
|||
|
||||
} else {
|
||||
|
||||
String cf = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
|
||||
String[] ff = cf.split(":");
|
||||
String ffField = ff[0];
|
||||
String ffFormat = ff.length > 1 ? ff[1] : null;
|
||||
|
||||
String sql;
|
||||
if (dt == DisplayType.N2NREFERENCE) {
|
||||
sql = MessageFormat.format(
|
||||
"select distinct referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}''",
|
||||
entity.getName(), categoryField.getName());
|
||||
entity.getName(), ffField);
|
||||
} else {
|
||||
String wrapField = categoryField.getName();
|
||||
String wrapField = ffField;
|
||||
if (dt == DisplayType.DATETIME) {
|
||||
wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m-%%d')", wrapField);
|
||||
}
|
||||
|
@ -85,10 +90,6 @@ public class DataListCategory {
|
|||
: Application.getQueryFactory().createQuery(sql, user);
|
||||
Object[][] array = query.array();
|
||||
|
||||
String cf = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
|
||||
String[] ff = cf.split(":");
|
||||
String format = ff.length > 1 ? ff[1] : null;
|
||||
|
||||
Set<Object> unique = new HashSet<>();
|
||||
|
||||
for (Object[] o : array) {
|
||||
|
@ -96,11 +97,11 @@ public class DataListCategory {
|
|||
Object label;
|
||||
|
||||
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
|
||||
format = StringUtils.defaultIfBlank(format, CalendarUtils.UTC_DATE_FORMAT);
|
||||
ffFormat = StringUtils.defaultIfBlank(ffFormat, CalendarUtils.UTC_DATE_FORMAT);
|
||||
if (id instanceof Date) {
|
||||
label = CalendarUtils.format(format, (Date) id);
|
||||
label = CalendarUtils.format(ffFormat, (Date) id);
|
||||
} else {
|
||||
label = id.toString().substring(0, format.length());
|
||||
label = id.toString().substring(0, ffFormat.length());
|
||||
}
|
||||
id = label;
|
||||
|
||||
|
|
|
@ -47,19 +47,21 @@ public class DataListManager extends BaseLayoutManager {
|
|||
* @param entity
|
||||
* @param user
|
||||
* @return
|
||||
* @see #formatListFields(String, ID, boolean, ConfigBean)
|
||||
*/
|
||||
public JSON getFieldsLayout(String entity, ID user) {
|
||||
return getFieldsLayout(entity, user, true);
|
||||
public JSON getListFields(String entity, ID user) {
|
||||
return getListFields(entity, user, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param user
|
||||
* @param filter 过滤无读取权限的字段
|
||||
* @param filter
|
||||
* @return
|
||||
* @see #formatListFields(String, ID, boolean, ConfigBean)
|
||||
*/
|
||||
public JSON getFieldsLayout(String entity, ID user, boolean filter) {
|
||||
return formatFieldsLayout(entity, user, filter, getLayoutOfDatalist(user, entity));
|
||||
public JSON getListFields(String entity, ID user, boolean filter) {
|
||||
return formatListFields(entity, user, filter, getLayoutOfDatalist(user, entity));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,7 +71,7 @@ public class DataListManager extends BaseLayoutManager {
|
|||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public JSON formatFieldsLayout(String entity, ID user, boolean filter, ConfigBean config) {
|
||||
public JSON formatListFields(String entity, ID user, boolean filter, ConfigBean config) {
|
||||
List<Map<String, Object>> columnList = new ArrayList<>();
|
||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||
Field namedField = entityMeta.getNameField();
|
||||
|
@ -259,7 +261,7 @@ public class DataListManager extends BaseLayoutManager {
|
|||
* @return
|
||||
*/
|
||||
public JSON getFieldsLayoutMode2(Entity entity) {
|
||||
JSONObject emptyConfig = (JSONObject) formatFieldsLayout(entity.getName(), null, true, null);
|
||||
JSONObject emptyConfig = (JSONObject) formatListFields(entity.getName(), null, true, null);
|
||||
JSONArray fields = emptyConfig.getJSONArray("fields");
|
||||
|
||||
if (entity.containsField(EntityHelper.ApprovalState)) {
|
||||
|
|
|
@ -73,16 +73,30 @@ public abstract class ShareToManager implements ConfigManager {
|
|||
* @param belongEntity
|
||||
* @param applyType
|
||||
* @return
|
||||
* @see #detectUseConfig(ID, String, String, boolean)
|
||||
*/
|
||||
public ID detectUseConfig(ID user, String belongEntity, String applyType) {
|
||||
return detectUseConfig(user, belongEntity, applyType, Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user
|
||||
* @param belongEntity
|
||||
* @param applyType
|
||||
* @param firstUseSelf
|
||||
* @return
|
||||
*/
|
||||
protected ID detectUseConfig(ID user, String belongEntity, String applyType, boolean firstUseSelf) {
|
||||
final Object[][] alls = getAllConfig(belongEntity, applyType);
|
||||
if (alls.length == 0) return null;
|
||||
|
||||
// 1.优先使用自己的
|
||||
for (Object[] d : alls) {
|
||||
ID createdBy = (ID) d[2];
|
||||
if (UserHelper.isSelf(user, createdBy)) {
|
||||
return (ID) d[0];
|
||||
if (firstUseSelf) {
|
||||
for (Object[] d : alls) {
|
||||
ID createdBy = (ID) d[2];
|
||||
if (UserHelper.isSelf(user, createdBy)) {
|
||||
return (ID) d[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.Date;
|
|||
/**
|
||||
* @author Zhao Fangfang
|
||||
* @see MetadataHelper
|
||||
* @since 1.0, 2013-6-26
|
||||
* @since 1.0, 2018-6-26
|
||||
*/
|
||||
public class EntityHelper {
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.regex.Pattern;
|
|||
* 从 JSON 解析 Record
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-26
|
||||
* @since 1.0, 2018-6-26
|
||||
* @see RecordBuilder
|
||||
*/
|
||||
@Slf4j
|
||||
|
|
|
@ -39,7 +39,7 @@ import java.util.Set;
|
|||
* 基于角色权限的查询过滤器
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-21
|
||||
* @since 1.0, 2018-6-21
|
||||
*/
|
||||
@Slf4j
|
||||
public class RoleBaseQueryFilter implements Filter, QueryFilter {
|
||||
|
|
|
@ -220,7 +220,7 @@ public class DataExporter extends SetUser {
|
|||
@Override
|
||||
protected DataListWrapper createDataListWrapper(int totalRows, Object[][] data, Query query) {
|
||||
DataListWrapper wrapper = super.createDataListWrapper(totalRows, data, query);
|
||||
wrapper.setSecWrapper(false);
|
||||
wrapper.setMixWrapper(false);
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,9 +125,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
|
|||
fields.add(base.getEntity().getPrimaryField().getName());
|
||||
Record snap = Application.getQueryFactory().recordNoFilter(primaryId, fields.toArray(new String[0]));
|
||||
|
||||
if (snap == null) {
|
||||
throw new NoRecordFoundException(primaryId);
|
||||
}
|
||||
if (snap == null) throw new NoRecordFoundException(primaryId);
|
||||
return snap;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,13 @@ import cn.devezhao.commons.ObjectUtils;
|
|||
import cn.devezhao.persist4j.Field;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.support.KVStorage;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 数字自增系列
|
||||
|
@ -23,9 +26,11 @@ import java.util.Map;
|
|||
* @author devezhao
|
||||
* @since 12/24/2018
|
||||
*/
|
||||
@Slf4j
|
||||
public class IncreasingVar extends SeriesVar {
|
||||
|
||||
private static final Map<String, Object> LOCKs = new HashMap<>();
|
||||
private static final Object LOCK = new Object();
|
||||
private static final Map<String, AtomicInteger> INCREASINGS = new ConcurrentHashMap<>();
|
||||
|
||||
private Field field;
|
||||
private String zeroFlag;
|
||||
|
@ -57,51 +62,40 @@ public class IncreasingVar extends SeriesVar {
|
|||
}
|
||||
|
||||
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
|
||||
Object keyLock = null;
|
||||
synchronized (LOCKs) {
|
||||
if (null == keyLock) {
|
||||
keyLock = LOCKs.computeIfAbsent(nameKey, k -> new Object());
|
||||
}
|
||||
}
|
||||
|
||||
int nextValue;
|
||||
synchronized (keyLock) {
|
||||
String val = KVStorage.getCustomValue(nameKey);
|
||||
if (val != null) {
|
||||
nextValue = ObjectUtils.toInt(val);
|
||||
} else {
|
||||
nextValue = countFromDb();
|
||||
}
|
||||
nextValue += 1;
|
||||
synchronized (LOCK) {
|
||||
AtomicInteger incr = INCREASINGS.get(nameKey);
|
||||
if (incr == null) {
|
||||
String val = KVStorage.getCustomValue(nameKey);
|
||||
int init = val == null ? countFromDb() : ObjectUtils.toInt(val);
|
||||
|
||||
// TODO 使用缓存,避免频繁更新数据库
|
||||
KVStorage.setCustomValue(nameKey, nextValue);
|
||||
incr = new AtomicInteger(init);
|
||||
INCREASINGS.put(nameKey, incr);
|
||||
}
|
||||
|
||||
int nextValue = incr.incrementAndGet();
|
||||
RebuildConfiguration.setCustomValue(nameKey, nextValue, Boolean.TRUE);
|
||||
|
||||
return StringUtils.leftPad(nextValue + "", getSymbols().length(), '0');
|
||||
}
|
||||
return StringUtils.leftPad(nextValue + "", getSymbols().length(), '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空序号缓存
|
||||
*/
|
||||
protected void clean() {
|
||||
if (this.field == null) {
|
||||
return;
|
||||
}
|
||||
if (this.field == null) return;
|
||||
|
||||
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
|
||||
Object keyLock = null;
|
||||
synchronized (LOCKs) {
|
||||
if (null == keyLock) {
|
||||
keyLock = LOCKs.computeIfAbsent(nameKey, k -> new Object());
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (keyLock) {
|
||||
KVStorage.setCustomValue(nameKey, 0);
|
||||
synchronized (LOCK) {
|
||||
INCREASINGS.remove(nameKey);
|
||||
RebuildConfiguration.setCustomValue(nameKey, 0, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE
|
||||
* 例如有100条记录,序号也为100。
|
||||
* 但是删除了10条后,调用此方法所生产的序号只有 90(直接采用 count 记录数)
|
||||
*
|
||||
|
@ -123,7 +117,8 @@ public class IncreasingVar extends SeriesVar {
|
|||
dateLimit = "(1=1)";
|
||||
}
|
||||
|
||||
String sql = String.format("select count(%s) from %s where %s", field.getName(), field.getOwnEntity().getName(), dateLimit);
|
||||
String sql = String.format("select count(%s) from %s where %s",
|
||||
field.getName(), field.getOwnEntity().getName(), dateLimit);
|
||||
Object[] count = Application.createQueryNoFilter(sql).unique();
|
||||
return ObjectUtils.toInt(count[0]);
|
||||
}
|
||||
|
|
|
@ -264,6 +264,16 @@ public class FieldAggregation extends TriggerAction {
|
|||
targetRecordId = (ID) o[0];
|
||||
}
|
||||
|
||||
// fix: v3.1.3 清空字段值以后无法找到记录
|
||||
if (o != null && targetRecordId == null
|
||||
&& operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == FieldAggregation.class) {
|
||||
ID beforeValue = operatingContext.getBeforeRecord().getID(followSourceField);
|
||||
ID afterValue = operatingContext.getAfterRecord().getID(followSourceField);
|
||||
if (beforeValue != null && afterValue == null) {
|
||||
targetRecordId = beforeValue;
|
||||
}
|
||||
}
|
||||
|
||||
this.followSourceWhere = String.format("%s = '%s'", followSourceField, targetRecordId);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,14 +45,15 @@ public class FieldAggregationRefresh {
|
|||
|
||||
// FIELD.ENTITY
|
||||
String[] targetFieldEntity = ((JSONObject) parentAc.getActionContent()).getString("targetEntity").split("\\.");
|
||||
String followSourceField = targetFieldEntity[0];
|
||||
|
||||
ID beforeRefreshedId = operatingContext.getBeforeRecord().getID(targetFieldEntity[0]);
|
||||
ID afterRefreshedId = operatingContext.getAfterRecord().getID(targetFieldEntity[0]);
|
||||
final ID beforeValue = operatingContext.getBeforeRecord().getID(followSourceField);
|
||||
final ID afterValue = operatingContext.getAfterRecord().getID(followSourceField);
|
||||
|
||||
// 之前未聚合
|
||||
if (beforeRefreshedId == null) return;
|
||||
if (beforeValue == null) return;
|
||||
// 未更新
|
||||
if (beforeRefreshedId.equals(afterRefreshedId)) return;
|
||||
if (beforeValue.equals(afterValue)) return;
|
||||
|
||||
ActionContext actionContext = new ActionContext(null,
|
||||
parentAc.getSourceEntity(), parentAc.getActionContent(), parentAc.getConfigId());
|
||||
|
@ -60,17 +61,14 @@ public class FieldAggregationRefresh {
|
|||
FieldAggregation fa = new FieldAggregation(actionContext, true);
|
||||
fa.sourceEntity = parent.sourceEntity;
|
||||
fa.targetEntity = parent.targetEntity;
|
||||
fa.targetRecordId = beforeRefreshedId;
|
||||
fa.followSourceWhere = String.format("%s = '%s'", targetFieldEntity[0], beforeRefreshedId);
|
||||
fa.targetRecordId = beforeValue;
|
||||
fa.followSourceWhere = String.format("%s = '%s'", followSourceField, beforeValue);
|
||||
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(beforeRefreshedId, triggerUser, false);
|
||||
Record fakeSourceRecord = EntityHelper.forUpdate(beforeValue, triggerUser, false);
|
||||
OperatingContext oCtx = OperatingContext.create(triggerUser, BizzPermission.NONE, fakeSourceRecord, fakeSourceRecord);
|
||||
|
||||
try {
|
||||
fa.execute(oCtx);
|
||||
// } catch (Throwable ex) {
|
||||
// // v3.1 出现异常可能导致事物回滚执行,因此此处 catch 并无意义
|
||||
// log.error("Error on trigger ({}) refresh", parentAc.getConfigId(), ex);
|
||||
} finally {
|
||||
fa.clean();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import cn.devezhao.bizz.privileges.impl.BizzPermission;
|
|||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.engine.NullValue;
|
||||
import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
|
@ -107,11 +108,11 @@ public class GroupAggregation extends FieldAggregation {
|
|||
|
||||
// 1.源记录数据
|
||||
|
||||
String ql = String.format("select %s from %s where %s = ?",
|
||||
String sql = String.format("select %s from %s where %s = ?",
|
||||
StringUtils.join(groupFieldsMapping.keySet().iterator(), ","),
|
||||
sourceEntity.getName(), sourceEntity.getPrimaryField().getName());
|
||||
|
||||
Record sourceRecord = Application.createQueryNoFilter(ql)
|
||||
final Record sourceRecord = Application.createQueryNoFilter(sql)
|
||||
.setParameter(1, actionContext.getSourceRecord())
|
||||
.record();
|
||||
|
||||
|
@ -122,10 +123,24 @@ public class GroupAggregation extends FieldAggregation {
|
|||
List<String[]> qFieldsRefresh = new ArrayList<>();
|
||||
boolean allNull = true;
|
||||
|
||||
final boolean isGroupUpdate = operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == GroupAggregation.class;
|
||||
|
||||
// 保存前/后值
|
||||
Map<String, Object[]> valueChanged = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, String> e : groupFieldsMapping.entrySet()) {
|
||||
String sourceField = e.getKey();
|
||||
String targetField = e.getValue();
|
||||
|
||||
if (isGroupUpdate) {
|
||||
Object beforeValue = operatingContext.getBeforeRecord().getObjectValue(sourceField);
|
||||
Object afterValue = operatingContext.getAfterRecord().getObjectValue(sourceField);
|
||||
if (NullValue.isNull(beforeValue) && NullValue.isNull(afterValue)) {
|
||||
} else {
|
||||
valueChanged.put(sourceField, new Object[] { beforeValue, afterValue });
|
||||
}
|
||||
}
|
||||
|
||||
Object val = sourceRecord.getObjectValue(sourceField);
|
||||
if (val == null) {
|
||||
qFields.add(String.format("%s is null", targetField));
|
||||
|
@ -211,21 +226,26 @@ public class GroupAggregation extends FieldAggregation {
|
|||
}
|
||||
|
||||
if (allNull) {
|
||||
log.warn("All group-fields are null");
|
||||
// 如果分组字段值全空将会触发全量更新
|
||||
if (!valueChanged.isEmpty()) {
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh);
|
||||
} else {
|
||||
log.warn("All group-fields are null, ignored");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.followSourceWhere = StringUtils.join(qFieldsFollow.iterator(), " and ");
|
||||
|
||||
if (operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == GroupAggregation.class) {
|
||||
if (isGroupUpdate) {
|
||||
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh);
|
||||
}
|
||||
|
||||
ql = String.format("select %s from %s where ( %s )",
|
||||
sql = String.format("select %s from %s where ( %s )",
|
||||
targetEntity.getPrimaryField().getName(), targetEntity.getName(),
|
||||
StringUtils.join(qFields.iterator(), " and "));
|
||||
|
||||
Object[] targetRecord = Application.createQueryNoFilter(ql).unique();
|
||||
Object[] targetRecord = Application.createQueryNoFilter(sql).unique();
|
||||
if (targetRecord != null) {
|
||||
targetRecordId = (ID) targetRecord[0];
|
||||
return;
|
||||
|
|
|
@ -59,7 +59,7 @@ public class GroupAggregationRefresh {
|
|||
}
|
||||
}
|
||||
|
||||
// 全部刷新
|
||||
// 全量刷新,性能较低
|
||||
if (targetWhere.size() <= 1) {
|
||||
targetWhere.clear();
|
||||
targetWhere.add("(1=1)");
|
||||
|
@ -72,6 +72,7 @@ public class GroupAggregationRefresh {
|
|||
entity.getPrimaryField().getName(),
|
||||
entity.getName(),
|
||||
StringUtils.join(targetWhere, " or "));
|
||||
|
||||
Object[][] targetArray = Application.createQueryNoFilter(sql).array();
|
||||
log.info("Maybe refresh target record(s) : {}", targetArray.length);
|
||||
|
||||
|
@ -98,6 +99,7 @@ public class GroupAggregationRefresh {
|
|||
}
|
||||
|
||||
ID fakeUpdateReferenceId = null;
|
||||
|
||||
// 1.尝试获取触发源
|
||||
for (int i = 0; i < o.length - 1; i++) {
|
||||
Object mayId = o[i];
|
||||
|
@ -140,9 +142,6 @@ public class GroupAggregationRefresh {
|
|||
|
||||
try {
|
||||
ga.execute(oCtx);
|
||||
// } catch (Throwable ex) {
|
||||
// // v3.1 出现异常可能导致事物回滚执行,因此此处 catch 并无意义
|
||||
// log.error("Error on trigger ({}) refresh record : {}", parentAc.getConfigId(), targetRecordId, ex);
|
||||
} finally {
|
||||
ga.clean();
|
||||
}
|
||||
|
|
|
@ -13,45 +13,67 @@ import com.rebuild.core.Application;
|
|||
import com.rebuild.core.BootEnvironmentPostProcessor;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* K/V 对存储
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2019/11/22
|
||||
*/
|
||||
@Slf4j
|
||||
public class KVStorage {
|
||||
|
||||
public static final Object SETNULL = new Object();
|
||||
|
||||
private static final String CUSTOM_PREFIX = "custom.";
|
||||
|
||||
/**
|
||||
* 存储
|
||||
*
|
||||
* 取
|
||||
* @param key 会自动加 `custom.` 前缀
|
||||
* @return
|
||||
*/
|
||||
public static String getCustomValue(String key) {
|
||||
return getValue("custom." + key, false, null);
|
||||
return getValue(CUSTOM_PREFIX + key, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取
|
||||
*
|
||||
* @param key 会自动加 `custom.` 前缀
|
||||
* 存
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public static void setCustomValue(String key, Object value) {
|
||||
setValue("custom." + key, value);
|
||||
setValue(CUSTOM_PREFIX + key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存
|
||||
* @param key
|
||||
* @param value
|
||||
* @param throttled 是否节流
|
||||
*/
|
||||
public static void setCustomValue(String key, Object value, boolean throttled) {
|
||||
if (throttled) THROTTLED_QUEUE.put(key, value);
|
||||
else setCustomValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删
|
||||
* @param key
|
||||
*/
|
||||
public static void removeCustomValue(String key) {
|
||||
setCustomValue(key, SETNULL);
|
||||
}
|
||||
|
||||
// -- RAW
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param value
|
||||
|
@ -130,4 +152,26 @@ public class KVStorage {
|
|||
|
||||
return value;
|
||||
}
|
||||
|
||||
// -- ASYNC
|
||||
|
||||
private static final Timer THROTTLED_TIMER = new Timer("KVStorage-Timer");
|
||||
private static final Map<String, Object> THROTTLED_QUEUE = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
THROTTLED_TIMER.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (THROTTLED_QUEUE.isEmpty()) return;
|
||||
|
||||
final Map<String, Object> queue = new HashMap<>(THROTTLED_QUEUE);
|
||||
THROTTLED_QUEUE.clear();
|
||||
|
||||
log.info("Synchronize KV pairs ... {}", queue);
|
||||
for (Map.Entry<String, Object> e : queue.entrySet()) {
|
||||
RebuildConfiguration.setCustomValue(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}, 1000, 1000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author ZHAO
|
||||
* @since 2019-08-23
|
||||
* !!!! 请勿修改或删除本文件
|
||||
* !!!! 请严格遵守《REBUILD 用户服务协议》https://getrebuild.com/legal/service-terms
|
||||
*/
|
||||
@Slf4j
|
||||
public final class License {
|
||||
|
|
|
@ -12,7 +12,7 @@ import com.alibaba.fastjson.JSON;
|
|||
|
||||
/**
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-20
|
||||
* @since 1.0, 2018-6-20
|
||||
*/
|
||||
public interface DataListBuilder {
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ import java.util.Map;
|
|||
* 数据列表数据构建
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-20
|
||||
* @since 1.0, 2018-6-20
|
||||
*/
|
||||
public class DataListBuilderImpl implements DataListBuilder {
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import java.util.Map;
|
|||
* 数据包装
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-20
|
||||
* @since 1.0, 2018-6-20
|
||||
*/
|
||||
public class DataListWrapper {
|
||||
|
||||
|
@ -52,7 +52,7 @@ public class DataListWrapper {
|
|||
// 信息脱敏
|
||||
protected boolean useDesensitized = false;
|
||||
|
||||
private boolean secWrapper = true;
|
||||
private boolean mixWrapper = true;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -93,6 +93,8 @@ public class DataListWrapper {
|
|||
*/
|
||||
public JSON toJson() {
|
||||
final Field nameFiled = entity.getNameField();
|
||||
final EasyField nameFieldEasy = EasyMetaFactory.valueOf(nameFiled);
|
||||
|
||||
final int joinFieldsLen = queryJoinFields == null ? 0 : queryJoinFields.size();
|
||||
final int selectFieldsLen = selectFields.length - joinFieldsLen;
|
||||
|
||||
|
@ -113,32 +115,39 @@ public class DataListWrapper {
|
|||
continue;
|
||||
}
|
||||
|
||||
final Object value = row[colIndex];
|
||||
Object value = row[colIndex];
|
||||
if (value == null) {
|
||||
row[colIndex] = StringUtils.EMPTY;
|
||||
continue;
|
||||
}
|
||||
|
||||
SelectItem fieldItem = selectFields[colIndex];
|
||||
Field field = fieldItem.getField();
|
||||
if (field.equals(nameFiled) && !fieldItem.getFieldPath().contains(".")) {
|
||||
nameValue = value;
|
||||
}
|
||||
Field fieldMeta = fieldItem.getField();
|
||||
|
||||
// 如果最终没能取得名称字段则补充
|
||||
if (field.getType() == FieldType.PRIMARY) {
|
||||
// Last
|
||||
if (fieldMeta.getType() == FieldType.PRIMARY) {
|
||||
// 如果最终没能取得名称字段则补充
|
||||
if (nameValue == null) {
|
||||
nameValue = FieldValueHelper.getLabel((ID) value, StringUtils.EMPTY);
|
||||
} else {
|
||||
nameValue = FieldValueHelper.wrapFieldValue(nameValue, nameFiled, true);
|
||||
if (nameValue == null) {
|
||||
nameValue = StringUtils.EMPTY;
|
||||
if (isUseDesensitized(nameFieldEasy)) {
|
||||
nameValue = FieldValueHelper.desensitized(nameFieldEasy, nameValue);
|
||||
}
|
||||
|
||||
} else {
|
||||
nameValue = FieldValueHelper.wrapFieldValue(nameValue, nameFieldEasy, true);
|
||||
if (nameValue == null) nameValue = StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
((ID) value).setLabel(nameValue);
|
||||
}
|
||||
|
||||
row[colIndex] = wrapFieldValue(value, field);
|
||||
value = wrapFieldValue(value, fieldMeta);
|
||||
row[colIndex] = value;
|
||||
|
||||
// 名称字段值
|
||||
if (fieldMeta.equals(nameFiled) && !fieldItem.getFieldPath().contains(".")) {
|
||||
nameValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +179,7 @@ public class DataListWrapper {
|
|||
}
|
||||
|
||||
// v2.10 Color
|
||||
if (value != null && this.secWrapper) {
|
||||
if (value != null && this.mixWrapper) {
|
||||
if (easyField.getDisplayType() == DisplayType.PICKLIST) {
|
||||
String color = PickListManager.instance.getColor((ID) originValue);
|
||||
if (StringUtils.isNotBlank(color)) {
|
||||
|
@ -237,9 +246,9 @@ public class DataListWrapper {
|
|||
/**
|
||||
* 进一步封装查询结果
|
||||
*
|
||||
* @param secWrapper
|
||||
* @param mixWrapper
|
||||
*/
|
||||
public void setSecWrapper(boolean secWrapper) {
|
||||
this.secWrapper = secWrapper;
|
||||
public void setMixWrapper(boolean mixWrapper) {
|
||||
this.mixWrapper = mixWrapper;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,18 +190,27 @@ public class ProtocolFilterParser {
|
|||
protected String parseCategory(String entity, String value) {
|
||||
Entity rootEntity = MetadataHelper.getEntity(entity);
|
||||
Field categoryField = DataListCategory.instance.getFieldOfCategory(rootEntity);
|
||||
if (categoryField == null) return "(9=9)";
|
||||
if (categoryField == null || StringUtils.isBlank(value)) return "(9=9)";
|
||||
|
||||
DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
|
||||
value = StringEscapeUtils.escapeSql(value);
|
||||
|
||||
if (dt == DisplayType.MULTISELECT) {
|
||||
return String.format("%s && %d", categoryField.getName(), ObjectUtils.toInt(value));
|
||||
} else if (dt == DisplayType.N2NREFERENCE) {
|
||||
return String.format(
|
||||
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')",
|
||||
rootEntity.getPrimaryField().getName(), categoryField.getName(), StringEscapeUtils.escapeSql(value));
|
||||
} else {
|
||||
return String.format("%s = '%s'", categoryField.getName(), StringEscapeUtils.escapeSql(value));
|
||||
rootEntity.getPrimaryField().getName(), categoryField.getName(), value);
|
||||
} else if (dt == DisplayType.DATETIME || dt == DisplayType.DATE) {
|
||||
String s = value + "0000-01-01 00:00:00".substring(value.length());
|
||||
String e = value + "0000-12-31 23:59:59".substring(value.length());
|
||||
if (dt == DisplayType.DATE) {
|
||||
s = s.split(" ")[0];
|
||||
e = e.split(" ")[0];
|
||||
}
|
||||
return MessageFormat.format("({0} >= ''{1}'' and {0} <= ''{2}'')", categoryField.getName(), s, e);
|
||||
} else {
|
||||
return String.format("%s = '%s'", categoryField.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.*;
|
|||
* 列表查询解析
|
||||
*
|
||||
* @author Zhao Fangfang
|
||||
* @since 1.0, 2013-6-20
|
||||
* @since 1.0, 2018-6-20
|
||||
*/
|
||||
public class QueryParser {
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class LoginLogController extends EntityController {
|
|||
public ModelAndView pageList(HttpServletRequest request) {
|
||||
ID user = getRequestUser(request);
|
||||
ModelAndView mv = createModelAndView("/admin/audit/login-logs", "LoginLog", user);
|
||||
JSON config = DataListManager.instance.getFieldsLayout("LoginLog", user);
|
||||
JSON config = DataListManager.instance.getListFields("LoginLog", user);
|
||||
mv.getModel().put("DataListConfig", JSON.toJSONString(config));
|
||||
return mv;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class DepartmentController extends EntityController {
|
|||
final ID user = getRequestUser(request);
|
||||
ModelAndView mv = createModelAndView("/admin/bizuser/dept-list", "Department", user);
|
||||
|
||||
JSON config = DataListManager.instance.getFieldsLayout("Department", user);
|
||||
JSON config = DataListManager.instance.getListFields("Department", user);
|
||||
mv.getModel().put("DataListConfig", JSON.toJSONString(config));
|
||||
return mv;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class TeamController extends EntityController {
|
|||
final ID user = getRequestUser(request);
|
||||
ModelAndView mv = createModelAndView("/admin/bizuser/team-list", "Team", user);
|
||||
|
||||
JSON config = DataListManager.instance.getFieldsLayout("Team", user);
|
||||
JSON config = DataListManager.instance.getListFields("Team", user);
|
||||
mv.getModel().put("DataListConfig", JSON.toJSONString(config));
|
||||
return mv;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class UserController extends EntityController {
|
|||
final ID user = getRequestUser(request);
|
||||
ModelAndView mv = createModelAndView("/admin/bizuser/user-list", "User", user);
|
||||
|
||||
JSON config = DataListManager.instance.getFieldsLayout("User", user);
|
||||
JSON config = DataListManager.instance.getListFields("User", user);
|
||||
mv.getModel().put("DataListConfig", JSON.toJSONString(config));
|
||||
mv.getModel().put("serviceMail", SMSender.availableMail());
|
||||
return mv;
|
||||
|
|
|
@ -15,10 +15,7 @@ import com.rebuild.core.Application;
|
|||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.core.support.integration.QiniuCloud;
|
||||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.utils.ImageView2;
|
||||
import com.rebuild.utils.OkHttpUtils;
|
||||
import com.rebuild.utils.RbAssert;
|
||||
import com.rebuild.utils.*;
|
||||
import com.rebuild.web.BaseController;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
@ -129,7 +126,7 @@ public class FileDownloader extends BaseController {
|
|||
// 共享查看
|
||||
if (request.getRequestURI().contains("/filex/access/")) {
|
||||
String e = getParameter(request, "e");
|
||||
if (StringUtils.isBlank(e) || Application.getCommonsCache().get(e) == null) {
|
||||
if (!checkEsign(e)) {
|
||||
response.sendError(HttpStatus.FORBIDDEN.value(), Language.L("分享的文件已过期"));
|
||||
return;
|
||||
}
|
||||
|
@ -161,16 +158,22 @@ public class FileDownloader extends BaseController {
|
|||
|
||||
@GetMapping(value = "read-raw")
|
||||
public void readRaw(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
RbAssert.isAllow(checkUser(request), "Unauthorized access");
|
||||
String filePath = getParameterNotNull(request, "url");
|
||||
boolean fullUrl = CommonsUtils.isExternalUrl(filePath);
|
||||
String charset = getParameter(request, "charset", AppUtils.UTF8);
|
||||
|
||||
String content;
|
||||
if (QiniuCloud.instance().available()) {
|
||||
String privateUrl = QiniuCloud.instance().makeUrl(filePath);
|
||||
String privateUrl = fullUrl ? filePath : QiniuCloud.instance().makeUrl(filePath);
|
||||
content = OkHttpUtils.get(privateUrl, null, charset);
|
||||
} else {
|
||||
|
||||
if (fullUrl) {
|
||||
String e = filePath.split("\\?e=")[1];
|
||||
RbAssert.is(checkEsign(e), "Unauthorized access");
|
||||
filePath = filePath.split("/filex/access/")[1].split("\\?")[0];
|
||||
}
|
||||
|
||||
// Local storage
|
||||
filePath = checkFilePath(filePath);
|
||||
File file = RebuildConfiguration.getFileOfData(filePath);
|
||||
|
@ -209,6 +212,11 @@ public class FileDownloader extends BaseController {
|
|||
return user != null;
|
||||
}
|
||||
|
||||
private static boolean checkEsign(String e) {
|
||||
String check = e == null ? null : Application.getCommonsCache().get(e);
|
||||
return "rb".equalsIgnoreCase(check);
|
||||
}
|
||||
|
||||
private static String checkFilePath(String filepath) {
|
||||
filepath = CodecUtils.urlDecode(filepath);
|
||||
filepath = filepath.replace("\\", "/");
|
||||
|
|
|
@ -139,7 +139,7 @@ public class ShowFieldsController extends BaseController implements ShareTo {
|
|||
raw = DataListManager.instance.getLayoutOfDatalist(user, entity);
|
||||
}
|
||||
|
||||
JSONObject config = (JSONObject) DataListManager.instance.formatFieldsLayout(entity, user, false, raw);
|
||||
JSONObject config = (JSONObject) DataListManager.instance.formatListFields(entity, user, false, raw);
|
||||
|
||||
Map<String, Object> ret = new HashMap<>();
|
||||
ret.put("fieldList", fieldList);
|
||||
|
|
|
@ -27,9 +27,7 @@ import com.rebuild.core.service.query.ParseHelper;
|
|||
import com.rebuild.core.support.general.DataListBuilder;
|
||||
import com.rebuild.core.support.general.DataListBuilderImpl;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.EntityController;
|
||||
import org.apache.commons.codec.language.bm.Lang;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -81,7 +79,7 @@ public class GeneralListController extends EntityController {
|
|||
JSON listConfig = null;
|
||||
|
||||
if (listMode == 1) {
|
||||
listConfig = DataListManager.instance.getFieldsLayout(entity, user);
|
||||
listConfig = DataListManager.instance.getListFields(entity, user);
|
||||
|
||||
// 扩展配置
|
||||
String advListHideFilters = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS);
|
||||
|
|
|
@ -284,7 +284,7 @@ public class ReferenceSearchController extends EntityController {
|
|||
ModelAndView mv = createModelAndView("/general/reference-search");
|
||||
putEntityMeta(mv, searchEntity);
|
||||
|
||||
JSON config = DataListManager.instance.getFieldsLayout(searchEntity.getName(), user);
|
||||
JSON config = DataListManager.instance.getListFields(searchEntity.getName(), user);
|
||||
mv.getModel().put("DataListConfig", JSON.toJSONString(config));
|
||||
|
||||
// 可新建
|
||||
|
|
|
@ -170,7 +170,7 @@ public class RelatedListController extends BaseController {
|
|||
@GetMapping("related-list-config")
|
||||
public RespBody getDataListConfig(HttpServletRequest req, @EntityParam Entity listEntity) {
|
||||
final ID user = getRequestUser(req);
|
||||
JSON config = DataListManager.instance.getFieldsLayout(listEntity.getName(), user);
|
||||
JSON config = DataListManager.instance.getListFields(listEntity.getName(), user);
|
||||
return RespBody.ok(config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ package com.rebuild.web.robot.trigger;
|
|||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.MetadataSorter;
|
||||
|
@ -20,6 +21,7 @@ import com.rebuild.core.service.trigger.TriggerAction;
|
|||
import com.rebuild.core.support.CommonsLock;
|
||||
import com.rebuild.core.support.License;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BaseController;
|
||||
import com.rebuild.web.admin.ConfigCommons;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
@ -113,7 +115,7 @@ public class TriggerAdminController extends BaseController {
|
|||
public Object[][] triggerList(HttpServletRequest request) {
|
||||
String belongEntity = getParameter(request, "entity");
|
||||
String q = getParameter(request, "q");
|
||||
String sql = "select configId,belongEntity,belongEntity,name,isDisabled,modifiedOn,when,actionType,configId,priority from RobotTriggerConfig" +
|
||||
String sql = "select configId,belongEntity,belongEntity,name,isDisabled,modifiedOn,when,actionType,configId,priority,actionContent from RobotTriggerConfig" +
|
||||
" where (1=1) and (2=2)" +
|
||||
" order by modifiedOn desc, name";
|
||||
|
||||
|
@ -121,7 +123,27 @@ public class TriggerAdminController extends BaseController {
|
|||
for (Object[] o : array) {
|
||||
o[7] = Language.L(ActionType.valueOf((String) o[7]));
|
||||
o[8] = CommonsLock.getLockedUserFormat((ID) o[8]);
|
||||
|
||||
// 目标实体
|
||||
o[10] = parseTargetEntity((String) o[10], (String) o[1]);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private String parseTargetEntity(String config, String sourceEntity) {
|
||||
if (!JSONUtils.wellFormat(config)) return null;
|
||||
|
||||
JSONObject configJson = JSON.parseObject(config);
|
||||
String targetEntity = configJson.getString("targetEntity");
|
||||
if (StringUtils.isBlank(targetEntity)) return null;
|
||||
|
||||
if (targetEntity.startsWith(TriggerAction.SOURCE_SELF)) targetEntity = sourceEntity;
|
||||
else if (targetEntity.contains(".")) targetEntity = targetEntity.split("\\.")[1];
|
||||
|
||||
if (MetadataHelper.containsEntity(targetEntity)) {
|
||||
return EasyMetaFactory.getLabel(targetEntity);
|
||||
} else {
|
||||
return String.format("[%s]", targetEntity.toUpperCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
<meta name="page-help" content="https://getrebuild.com/docs/admin/approval" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/approvals.css}" />
|
||||
<title>[[${bundle.L('审批流程')}]]</title>
|
||||
<style>
|
||||
tr.ui-sortable-handle.ui-sortable-helper td:first-child {
|
||||
width: 339px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header" th:classappend="${sideCollapsedClazz}">
|
||||
|
|
|
@ -47,8 +47,8 @@
|
|||
<tr>
|
||||
<th>[[${bundle.L('名称')}]]</th>
|
||||
<th>[[${bundle.L('源实体')}]]</th>
|
||||
<th>[[${bundle.L('触发类型')}]]</th>
|
||||
<th width="26%" class="no-sort">[[${bundle.L('触发动作')}]]</th>
|
||||
<th width="20%">[[${bundle.L('触发类型')}]]</th>
|
||||
<th width="20%" class="no-sort">[[${bundle.L('触发动作')}]]</th>
|
||||
<th width="80" class="int-sort">[[${bundle.L('优先级')}]]</th>
|
||||
<th width="80" class="no-sort">[[${bundle.L('启用')}]]</th>
|
||||
<th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th>
|
||||
|
|
|
@ -449,44 +449,3 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
.chart-list > div .chart-icon > i.SUNBURST {
|
||||
background-position: -1372px -1464px;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen {
|
||||
position: absolute;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen .ui-resizable-handle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
|
||||
.grid-stack-item.bgcolor .grid-stack-item-content,
|
||||
.grid-stack-item.bgcolor .chart.index > .data-item strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid-stack-item.bgcolor .rb-loading:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.grid-stack-item.bgcolor .rb-spinner svg {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
body.fullscreen {
|
||||
background: #0a1b3b url(../img/datav-bg.png) no-repeat 0 0;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
body.fullscreen .grid-stack .grid-stack-item .grid-stack-item-content {
|
||||
background-color: rgba(6, 30, 93, 0.5);
|
||||
}
|
||||
|
||||
body.fullscreen .grid-stack .grid-stack-item.fullscreen .grid-stack-item-content {
|
||||
background-color: #0a1b3b;
|
||||
}
|
|
@ -174,3 +174,66 @@ body.fullscreen .J_dash-fullscreen .zmdi-fullscreen::before {
|
|||
.chart-grid.uneditable .chart-box .chart-head .chart-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dash-right .J_darkmode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.fullscreen .dash-right .J_darkmode {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
body.fullscreen.darkmode {
|
||||
background: #0a1b3b url(../img/datav-bg.png) no-repeat 0 0;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
body.fullscreen.darkmode .grid-stack .grid-stack-item .grid-stack-item-content {
|
||||
background-color: rgba(6, 30, 93, 0.5);
|
||||
}
|
||||
|
||||
body.fullscreen.darkmode .grid-stack .grid-stack-item.fullscreen .grid-stack-item-content {
|
||||
background-color: #0a1b3b;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen {
|
||||
position: absolute;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen .ui-resizable-handle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.fullscreen.darkmode .chart-title {
|
||||
color: rgba(255, 255, 255, 0.68);
|
||||
}
|
||||
|
||||
body.fullscreen.darkmode .chart.index > .data-item p,
|
||||
body.fullscreen.darkmode .chart.index > .data-item strong,
|
||||
body.fullscreen.darkmode .tools-bar h4 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body.fullscreen .tools-bar h4 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* bgcolor */
|
||||
|
||||
.grid-stack-item.bgcolor .grid-stack-item-content,
|
||||
.grid-stack-item.bgcolor .chart.index > .data-item strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid-stack-item.bgcolor .rb-loading::after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.grid-stack-item.bgcolor .rb-spinner svg {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
|
|
@ -3295,6 +3295,12 @@ form {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.preview-modal .container.fp-content.fullwidth {
|
||||
max-width: 100%;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.file-share {
|
||||
padding: 10px 40px;
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ $(document).ready(() => {
|
|||
}
|
||||
|
||||
const option = wpc.chartConfig.option || {}
|
||||
if (typeof option['mergeCell'] === undefined) option.mergeCell = true // fix: 3.1.3
|
||||
for (let k in option) {
|
||||
const opt = $(`.chart-option input[data-name=${k}]`)
|
||||
if (opt.length > 0) {
|
||||
|
|
|
@ -132,6 +132,8 @@ $(document).ready(function () {
|
|||
// eslint-disable-next-line no-undef
|
||||
BaseChart.currentFullscreen && BaseChart.currentFullscreen.toggleFullscreen(true)
|
||||
})
|
||||
|
||||
$('.J_darkmode button').on('click', () => $(document.body).toggleClass('darkmode'))
|
||||
})
|
||||
|
||||
// 全屏工具
|
||||
|
@ -234,7 +236,9 @@ const add_widget = function (item) {
|
|||
const chart_add = $('#chart-add')
|
||||
if (chart_add.length > 0) gridstack.removeWidget(chart_add.parent())
|
||||
|
||||
const gsi = `<div class="grid-stack-item ${item.bgcolor && 'bgcolor'}"><div id="${chid}" class="grid-stack-item-content" ${item.bgcolor ? `style="background-color:${item.bgcolor}` : ''}"></div></div>`
|
||||
const gsi = `<div class="grid-stack-item ${item.bgcolor && 'bgcolor'}"><div id="${chid}" class="grid-stack-item-content" ${
|
||||
item.bgcolor ? `style="background-color:${item.bgcolor}` : ''
|
||||
}"></div></div>`
|
||||
// Use gridstar
|
||||
if (item.size_x || item.size_y) {
|
||||
gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, true, 2, 12, 2, 24)
|
||||
|
|
|
@ -120,7 +120,7 @@ class RbPreview extends React.Component {
|
|||
|
||||
renderDoc() {
|
||||
return (
|
||||
<div className="container fp-content">
|
||||
<div className={`container fp-content ${this.props.fullwidth && 'fullwidth'}`}>
|
||||
<div className="iframe">
|
||||
{!this.state.docRendered && (
|
||||
<div className="must-center">
|
||||
|
@ -135,10 +135,10 @@ class RbPreview extends React.Component {
|
|||
|
||||
renderText() {
|
||||
return (
|
||||
<div className="container fp-content">
|
||||
<div className={`container fp-content ${this.props.fullwidth && 'fullwidth'}`}>
|
||||
<div className="iframe text">
|
||||
{this.state.previewText || this.state.previewText === '' ? (
|
||||
<pre>{this.state.previewText || <i className="text-muted">{$L('无')}</i>}</pre>
|
||||
<pre className="mb-0">{this.state.previewText || <i className="text-muted">{$L('无')}</i>}</pre>
|
||||
) : (
|
||||
<div className="must-center">
|
||||
<RbSpinner fully={true} />
|
||||
|
|
|
@ -70,7 +70,7 @@ class GridList extends React.Component {
|
|||
setTimeout(() => location.reload(), 500)
|
||||
} else {
|
||||
this.disabled(false)
|
||||
RbHighbar.error(res.error_msg)
|
||||
RbHighbar.error(WrapHtml(res.error_msg))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
@ -99,8 +99,7 @@ function modeSave(newOption, next) {
|
|||
})
|
||||
}
|
||||
|
||||
// const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'DATE', 'DATETIME', 'REFERENCE', 'N2NREFERENCE']
|
||||
const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
|
||||
const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'DATE', 'DATETIME', 'REFERENCE', 'N2NREFERENCE']
|
||||
|
||||
// 模式选项
|
||||
class DlgMode1Option extends RbFormHandler {
|
||||
|
@ -146,7 +145,7 @@ class DlgMode1Option extends RbFormHandler {
|
|||
</div>
|
||||
<div className={`col-4 pl-0 ${this.state.advListShowCategoryFormats ? '' : 'hide'}`}>
|
||||
<label className="mb-1">{$L('字段格式')}</label>
|
||||
<select className="form-control form-control-sm" disabled>
|
||||
<select className="form-control form-control-sm">
|
||||
{this.state.advListShowCategoryFormats &&
|
||||
this.state.advListShowCategoryFormats.map((item) => {
|
||||
return (
|
||||
|
@ -213,11 +212,11 @@ class DlgMode1Option extends RbFormHandler {
|
|||
$catFields = $('.advListShowCategory-set select:eq(0)')
|
||||
$catFormats = $('.advListShowCategory-set select:eq(1)')
|
||||
|
||||
$.get(`/commons/metadata/fields?entity=${wpc.entityName}&deep=2`, (res) => {
|
||||
$.get(`/commons/metadata/fields?entity=${wpc.entityName}`, (res) => {
|
||||
const _data = []
|
||||
res.data &&
|
||||
res.data.forEach((item) => {
|
||||
if (CATE_TYPES.includes(item.type) && !(item.name.includes('owningDept.') || item.name.includes('owningUser.'))) {
|
||||
if (CATE_TYPES.includes(item.type)) {
|
||||
_data.push(item)
|
||||
}
|
||||
})
|
||||
|
@ -240,10 +239,10 @@ class DlgMode1Option extends RbFormHandler {
|
|||
let formats
|
||||
if (found && found.type === 'CLASSIFICATION') {
|
||||
formats = [
|
||||
[0, $L('%d 级分类', 1)],
|
||||
[1, $L('%d 级分类', 2)],
|
||||
[2, $L('%d 级分类', 3)],
|
||||
[3, $L('%d 级分类', 4)],
|
||||
// [0, $L('%d 级分类', 1)],
|
||||
// [1, $L('%d 级分类', 2)],
|
||||
// [2, $L('%d 级分类', 3)],
|
||||
// [3, $L('%d 级分类', 4)],
|
||||
]
|
||||
} else if (found && (found.type === 'DATE' || found.type === 'DATETIME')) {
|
||||
formats = [
|
||||
|
|
|
@ -5,7 +5,7 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
|||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
const entity = $urlp('entity')
|
||||
const settingsUrl = `/admin/entity/${entity}/list-stats`
|
||||
|
||||
|
@ -87,6 +87,9 @@ const render_set = function (item) {
|
|||
return
|
||||
}
|
||||
|
||||
// 唯一
|
||||
if (!item.key2) item.key2 = $random('stat-')
|
||||
|
||||
const $to = $('.set-items')
|
||||
|
||||
const calc = item.calc || 'SUM'
|
||||
|
@ -108,10 +111,10 @@ const render_set = function (item) {
|
|||
$(`<li class="dropdown-item" data-calc='_LABEL'>${$L('显示样式')}</li>`).appendTo($ul)
|
||||
|
||||
$ul.find('.dropdown-item').on('click', function () {
|
||||
const calc = $(this).data('calc')
|
||||
if (calc === '_LABEL') {
|
||||
if (ShowStyles_Comps[item.name]) {
|
||||
ShowStyles_Comps[item.name].show()
|
||||
const c = $(this).data('calc')
|
||||
if (c === '_LABEL') {
|
||||
if (ShowStyles_Comps[item.key2]) {
|
||||
ShowStyles_Comps[item.key2].show()
|
||||
} else {
|
||||
renderRbcomp(
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
|
@ -126,12 +129,12 @@ const render_set = function (item) {
|
|||
/>,
|
||||
null,
|
||||
function () {
|
||||
ShowStyles_Comps[item.name] = this
|
||||
ShowStyles_Comps[item.key2] = this
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
$item.attr('data-calc', calc).find('.item > span').text(`${item.label} (${CALC_TYPES[calc]})`)
|
||||
$item.attr('data-calc', c).find('.item > span').text(`${item.label} (${CALC_TYPES[c]})`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
$.ajaxSetup({
|
||||
headers: {
|
||||
'Content-Type': 'text/plain;charset=utf-8',
|
||||
'X-Client': 'RB/WEB-2.9',
|
||||
'X-Client': 'RB/WEB',
|
||||
'X-CsrfToken': rb.csrfToken || '',
|
||||
'X-AuthToken': rb.authToken || '',
|
||||
},
|
||||
|
|
|
@ -178,11 +178,12 @@ $(function () {
|
|||
})
|
||||
})
|
||||
|
||||
if (rb.commercial === 11) {
|
||||
$('a[target="_blank"]').each(function () {
|
||||
if (($(this).attr('href') || '').indexOf('getrebuild.com') > -1) $(this).removeAttr('href')
|
||||
})
|
||||
}
|
||||
// if (rb.commercial === 11) {
|
||||
// $('a[target="_blank"]').each(function () {
|
||||
// if (($(this).attr('href') || '').indexOf('getrebuild.com') > -1) $(this).removeAttr('href')
|
||||
// })
|
||||
// $('.commercial11').addClass('hide')
|
||||
// }
|
||||
})
|
||||
|
||||
var $addResizeHandler__calls = []
|
||||
|
|
|
@ -67,7 +67,14 @@ class TriggerList extends ConfigList {
|
|||
<a href={`trigger/${item[0]}`}>{item[3] || item[2] + ' · ' + item[7]}</a>
|
||||
</td>
|
||||
<td>{item[2] || item[1]}</td>
|
||||
<td>{item[7]}</td>
|
||||
<td>
|
||||
{item[7]}
|
||||
{item[10] && (
|
||||
<span title={$L('目标实体')} className="ml-1">
|
||||
({item[10]})
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-wrap">{item[6] > 0 ? $L('当 %s 时', formatWhen(item[6])) : <span className="text-warning">({$L('无触发动作')})</span>}</td>
|
||||
<td>
|
||||
<span className="badge badge-light">{item[9]}</span>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</script>
|
||||
<script type="text/babel">
|
||||
$(document).ready(() => {
|
||||
renderRbcomp(<RbPreview urls={[window.__PageConfig.publicUrl]} unclose />)
|
||||
renderRbcomp(<RbPreview urls={[window.__PageConfig.publicUrl]} unclose fullwidth />)
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
<span class="custom-control-label">[[${bundle.L('显示汇总')}]]</span>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="mergeCell" checked />
|
||||
<input class="custom-control-input" type="checkbox" data-name="mergeCell" />
|
||||
<span class="custom-control-label">[[${bundle.L('自动合并单元格')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 text-right d-none d-md-block dash-right">
|
||||
<div class="btn-group J_darkmode">
|
||||
<button type="button" class="btn btn-link pr-0 text-right">
|
||||
<i class="mdi mdi-weather-sunny icon up-1" style="font-size: 1.45rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-link pr-0 text-right J_dash-fullscreen" th:title="${bundle.L('全屏')}">
|
||||
<i class="zmdi zmdi-fullscreen icon" style="font-size: 1.45rem"></i>
|
||||
|
|
|
@ -43,7 +43,7 @@ public class SeriesGeneratorTest extends TestSupport {
|
|||
void testIncrementVarNThreads() {
|
||||
final IncreasingVar var = new IncreasingVar("0000", getSeriesField(), "Y");
|
||||
final Set<String> set = Collections.synchronizedSet(new HashSet<>());
|
||||
final int N = 200;
|
||||
final int N = 1000;
|
||||
for (int i = 0; i < N; i++) {
|
||||
new Thread(() -> {
|
||||
String s = var.generate();
|
||||
|
@ -51,7 +51,7 @@ public class SeriesGeneratorTest extends TestSupport {
|
|||
System.out.println(s + " << " + Thread.currentThread().getName());
|
||||
}).start();
|
||||
}
|
||||
ThreadPool.waitFor(2000);
|
||||
ThreadPool.waitFor(1200);
|
||||
Assertions.assertEquals(set.size(), N);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ public class DataListBuilderTest extends TestSupport {
|
|||
|
||||
@Test
|
||||
public void testColumnLayout() {
|
||||
JSON layout = DataListManager.instance.getFieldsLayout(Account, SIMPLE_USER);
|
||||
JSON layout = DataListManager.instance.getListFields(Account, SIMPLE_USER);
|
||||
System.out.println(layout);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue