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:
RB 2022-11-29 22:01:41 +08:00 committed by GitHub
parent a00d5f1ea8
commit 0e88bfa6f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 407 additions and 223 deletions

2
@rbv

@ -1 +1 @@
Subproject commit c3184f8196c0835c70942a2b9723838eebb2ebb6 Subproject commit 54904a28b5177b65ed14613d98f92a4ce912be37

View file

@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.rebuild</groupId> <groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId> <artifactId>rebuild</artifactId>
<version>3.1.2</version> <version>3.1.3</version>
<name>rebuild</name> <name>rebuild</name>
<description>Building your business-systems freely!</description> <description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT --> <!-- UNCOMMENT USE TOMCAT -->

View file

@ -67,11 +67,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/** /**
* Rebuild Version * 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} * Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/ */
public static final int BUILD = 3010209; public static final int BUILD = 3010310;
static { static {
// Driver for DB // Driver for DB

View file

@ -10,8 +10,9 @@ package com.rebuild.core.configuration.general;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.RebuildException;
import com.rebuild.core.configuration.ConfigBean; 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_LISTSTATS = "LISTSTATS";
// 列表-查询面板 // 列表-查询面板
public static final String TYPE_LISTFILTERPANE = "LISTFILTERPANE"; public static final String TYPE_LISTFILTERPANE = "LISTFILTERPANE";
// 列表-图表 of Widget // 列表-图表
public static final String TYPE_WCHARTS = "WCHARTS"; public static final String TYPE_WCHARTS = "WCHARTS";
// 视图-相关项 // 视图-相关项
public static final String TYPE_TAB = "TAB"; public static final String TYPE_TAB = "TAB";
// 视图-新建相关 // 视图-新建相关
public static final String TYPE_ADD = "ADD"; public static final String TYPE_ADD = "ADD";
@Override @Override
protected String getConfigEntity() { protected String getConfigEntity() {
return "LayoutConfig"; return "LayoutConfig";
@ -82,13 +82,19 @@ public class BaseLayoutManager extends ShareToManager {
* @return * @return
*/ */
public ConfigBean getLayout(ID user, String belongEntity, String applyType) { public ConfigBean getLayout(ID user, String belongEntity, String applyType) {
ID detected = detectUseConfig(user, belongEntity, applyType); // 221125 无权限不允许使用自有配置
if (detected == null) { boolean firstUseSelf = true;
return null; 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); 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 = ?") "select belongEntity,applyType from LayoutConfig where configId = ?")
.setParameter(1, cfgid) .setParameter(1, cfgid)
.unique(); .unique();
if (o == null) { if (o == null) throw new ConfigurationException("No config found : " + cfgid);
throw new RebuildException("No config found : " + cfgid);
}
Object[][] cached = getAllConfig((String) o[0], (String) o[1]); 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 * @param cfgid
* @return * @return
*/ */
protected ConfigBean findEntry(Object[][] uses, ID cfgid) { protected ConfigBean findConfigBean(Object[][] uses, ID cfgid) {
for (Object[] c : uses) { for (Object[] c : uses) {
if (c[0].equals(cfgid)) { if (c[0].equals(cfgid)) {
return new ConfigBean() return new ConfigBean()

View file

@ -64,13 +64,18 @@ public class DataListCategory {
} else { } 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; String sql;
if (dt == DisplayType.N2NREFERENCE) { if (dt == DisplayType.N2NREFERENCE) {
sql = MessageFormat.format( sql = MessageFormat.format(
"select distinct referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}''", "select distinct referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}''",
entity.getName(), categoryField.getName()); entity.getName(), ffField);
} else { } else {
String wrapField = categoryField.getName(); String wrapField = ffField;
if (dt == DisplayType.DATETIME) { if (dt == DisplayType.DATETIME) {
wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m-%%d')", wrapField); wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m-%%d')", wrapField);
} }
@ -85,10 +90,6 @@ public class DataListCategory {
: Application.getQueryFactory().createQuery(sql, user); : Application.getQueryFactory().createQuery(sql, user);
Object[][] array = query.array(); 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<>(); Set<Object> unique = new HashSet<>();
for (Object[] o : array) { for (Object[] o : array) {
@ -96,11 +97,11 @@ public class DataListCategory {
Object label; Object label;
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) { 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) { if (id instanceof Date) {
label = CalendarUtils.format(format, (Date) id); label = CalendarUtils.format(ffFormat, (Date) id);
} else { } else {
label = id.toString().substring(0, format.length()); label = id.toString().substring(0, ffFormat.length());
} }
id = label; id = label;

View file

@ -47,19 +47,21 @@ public class DataListManager extends BaseLayoutManager {
* @param entity * @param entity
* @param user * @param user
* @return * @return
* @see #formatListFields(String, ID, boolean, ConfigBean)
*/ */
public JSON getFieldsLayout(String entity, ID user) { public JSON getListFields(String entity, ID user) {
return getFieldsLayout(entity, user, true); return getListFields(entity, user, true);
} }
/** /**
* @param entity * @param entity
* @param user * @param user
* @param filter 过滤无读取权限的字段 * @param filter
* @return * @return
* @see #formatListFields(String, ID, boolean, ConfigBean)
*/ */
public JSON getFieldsLayout(String entity, ID user, boolean filter) { public JSON getListFields(String entity, ID user, boolean filter) {
return formatFieldsLayout(entity, user, filter, getLayoutOfDatalist(user, entity)); return formatListFields(entity, user, filter, getLayoutOfDatalist(user, entity));
} }
/** /**
@ -69,7 +71,7 @@ public class DataListManager extends BaseLayoutManager {
* @param config * @param config
* @return * @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<>(); List<Map<String, Object>> columnList = new ArrayList<>();
Entity entityMeta = MetadataHelper.getEntity(entity); Entity entityMeta = MetadataHelper.getEntity(entity);
Field namedField = entityMeta.getNameField(); Field namedField = entityMeta.getNameField();
@ -259,7 +261,7 @@ public class DataListManager extends BaseLayoutManager {
* @return * @return
*/ */
public JSON getFieldsLayoutMode2(Entity entity) { 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"); JSONArray fields = emptyConfig.getJSONArray("fields");
if (entity.containsField(EntityHelper.ApprovalState)) { if (entity.containsField(EntityHelper.ApprovalState)) {

View file

@ -73,16 +73,30 @@ public abstract class ShareToManager implements ConfigManager {
* @param belongEntity * @param belongEntity
* @param applyType * @param applyType
* @return * @return
* @see #detectUseConfig(ID, String, String, boolean)
*/ */
public ID detectUseConfig(ID user, String belongEntity, String applyType) { 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); final Object[][] alls = getAllConfig(belongEntity, applyType);
if (alls.length == 0) return null; if (alls.length == 0) return null;
// 1.优先使用自己的 // 1.优先使用自己的
for (Object[] d : alls) { if (firstUseSelf) {
ID createdBy = (ID) d[2]; for (Object[] d : alls) {
if (UserHelper.isSelf(user, createdBy)) { ID createdBy = (ID) d[2];
return (ID) d[0]; if (UserHelper.isSelf(user, createdBy)) {
return (ID) d[0];
}
} }
} }

View file

@ -23,7 +23,7 @@ import java.util.Date;
/** /**
* @author Zhao Fangfang * @author Zhao Fangfang
* @see MetadataHelper * @see MetadataHelper
* @since 1.0, 2013-6-26 * @since 1.0, 2018-6-26
*/ */
public class EntityHelper { public class EntityHelper {

View file

@ -35,7 +35,7 @@ import java.util.regex.Pattern;
* JSON 解析 Record * JSON 解析 Record
* *
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-26 * @since 1.0, 2018-6-26
* @see RecordBuilder * @see RecordBuilder
*/ */
@Slf4j @Slf4j

View file

@ -39,7 +39,7 @@ import java.util.Set;
* 基于角色权限的查询过滤器 * 基于角色权限的查询过滤器
* *
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-21 * @since 1.0, 2018-6-21
*/ */
@Slf4j @Slf4j
public class RoleBaseQueryFilter implements Filter, QueryFilter { public class RoleBaseQueryFilter implements Filter, QueryFilter {

View file

@ -220,7 +220,7 @@ public class DataExporter extends SetUser {
@Override @Override
protected DataListWrapper createDataListWrapper(int totalRows, Object[][] data, Query query) { protected DataListWrapper createDataListWrapper(int totalRows, Object[][] data, Query query) {
DataListWrapper wrapper = super.createDataListWrapper(totalRows, data, query); DataListWrapper wrapper = super.createDataListWrapper(totalRows, data, query);
wrapper.setSecWrapper(false); wrapper.setMixWrapper(false);
return wrapper; return wrapper;
} }
} }

View file

@ -125,9 +125,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
fields.add(base.getEntity().getPrimaryField().getName()); fields.add(base.getEntity().getPrimaryField().getName());
Record snap = Application.getQueryFactory().recordNoFilter(primaryId, fields.toArray(new String[0])); Record snap = Application.getQueryFactory().recordNoFilter(primaryId, fields.toArray(new String[0]));
if (snap == null) { if (snap == null) throw new NoRecordFoundException(primaryId);
throw new NoRecordFoundException(primaryId);
}
return snap; return snap;
} }

View file

@ -12,10 +12,13 @@ import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.support.KVStorage; import com.rebuild.core.support.KVStorage;
import com.rebuild.core.support.RebuildConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map; 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 * @author devezhao
* @since 12/24/2018 * @since 12/24/2018
*/ */
@Slf4j
public class IncreasingVar extends SeriesVar { 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 Field field;
private String zeroFlag; 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()); 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 (LOCK) {
synchronized (keyLock) { AtomicInteger incr = INCREASINGS.get(nameKey);
String val = KVStorage.getCustomValue(nameKey); if (incr == null) {
if (val != null) { String val = KVStorage.getCustomValue(nameKey);
nextValue = ObjectUtils.toInt(val); int init = val == null ? countFromDb() : ObjectUtils.toInt(val);
} else {
nextValue = countFromDb();
}
nextValue += 1;
// TODO 使用缓存避免频繁更新数据库 incr = new AtomicInteger(init);
KVStorage.setCustomValue(nameKey, nextValue); 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() { protected void clean() {
if (this.field == null) { if (this.field == null) return;
return;
}
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName()); 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) { synchronized (LOCK) {
KVStorage.setCustomValue(nameKey, 0); INCREASINGS.remove(nameKey);
RebuildConfiguration.setCustomValue(nameKey, 0, Boolean.TRUE);
} }
} }
/** /**
* NOTE
* 例如有100条记录序号也为100 * 例如有100条记录序号也为100
* 但是删除了10条后调用此方法所生产的序号只有 90直接采用 count 记录数 * 但是删除了10条后调用此方法所生产的序号只有 90直接采用 count 记录数
* *
@ -123,7 +117,8 @@ public class IncreasingVar extends SeriesVar {
dateLimit = "(1=1)"; 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(); Object[] count = Application.createQueryNoFilter(sql).unique();
return ObjectUtils.toInt(count[0]); return ObjectUtils.toInt(count[0]);
} }

View file

@ -264,6 +264,16 @@ public class FieldAggregation extends TriggerAction {
targetRecordId = (ID) o[0]; 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); this.followSourceWhere = String.format("%s = '%s'", followSourceField, targetRecordId);
} }

View file

@ -45,14 +45,15 @@ public class FieldAggregationRefresh {
// FIELD.ENTITY // FIELD.ENTITY
String[] targetFieldEntity = ((JSONObject) parentAc.getActionContent()).getString("targetEntity").split("\\."); String[] targetFieldEntity = ((JSONObject) parentAc.getActionContent()).getString("targetEntity").split("\\.");
String followSourceField = targetFieldEntity[0];
ID beforeRefreshedId = operatingContext.getBeforeRecord().getID(targetFieldEntity[0]); final ID beforeValue = operatingContext.getBeforeRecord().getID(followSourceField);
ID afterRefreshedId = operatingContext.getAfterRecord().getID(targetFieldEntity[0]); 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, ActionContext actionContext = new ActionContext(null,
parentAc.getSourceEntity(), parentAc.getActionContent(), parentAc.getConfigId()); parentAc.getSourceEntity(), parentAc.getActionContent(), parentAc.getConfigId());
@ -60,17 +61,14 @@ public class FieldAggregationRefresh {
FieldAggregation fa = new FieldAggregation(actionContext, true); FieldAggregation fa = new FieldAggregation(actionContext, true);
fa.sourceEntity = parent.sourceEntity; fa.sourceEntity = parent.sourceEntity;
fa.targetEntity = parent.targetEntity; fa.targetEntity = parent.targetEntity;
fa.targetRecordId = beforeRefreshedId; fa.targetRecordId = beforeValue;
fa.followSourceWhere = String.format("%s = '%s'", targetFieldEntity[0], beforeRefreshedId); 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); OperatingContext oCtx = OperatingContext.create(triggerUser, BizzPermission.NONE, fakeSourceRecord, fakeSourceRecord);
try { try {
fa.execute(oCtx); fa.execute(oCtx);
// } catch (Throwable ex) {
// // v3.1 出现异常可能导致事物回滚执行因此此处 catch 并无意义
// log.error("Error on trigger ({}) refresh", parentAc.getConfigId(), ex);
} finally { } finally {
fa.clean(); fa.clean();
} }

View file

@ -11,6 +11,7 @@ import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.commons.CalendarUtils; import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.engine.NullValue;
import cn.devezhao.persist4j.metadata.MissingMetaExcetion; import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
@ -107,11 +108,11 @@ public class GroupAggregation extends FieldAggregation {
// 1.源记录数据 // 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(), ","), StringUtils.join(groupFieldsMapping.keySet().iterator(), ","),
sourceEntity.getName(), sourceEntity.getPrimaryField().getName()); sourceEntity.getName(), sourceEntity.getPrimaryField().getName());
Record sourceRecord = Application.createQueryNoFilter(ql) final Record sourceRecord = Application.createQueryNoFilter(sql)
.setParameter(1, actionContext.getSourceRecord()) .setParameter(1, actionContext.getSourceRecord())
.record(); .record();
@ -122,10 +123,24 @@ public class GroupAggregation extends FieldAggregation {
List<String[]> qFieldsRefresh = new ArrayList<>(); List<String[]> qFieldsRefresh = new ArrayList<>();
boolean allNull = true; 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()) { for (Map.Entry<String, String> e : groupFieldsMapping.entrySet()) {
String sourceField = e.getKey(); String sourceField = e.getKey();
String targetField = e.getValue(); 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); Object val = sourceRecord.getObjectValue(sourceField);
if (val == null) { if (val == null) {
qFields.add(String.format("%s is null", targetField)); qFields.add(String.format("%s is null", targetField));
@ -211,21 +226,26 @@ public class GroupAggregation extends FieldAggregation {
} }
if (allNull) { 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; return;
} }
this.followSourceWhere = StringUtils.join(qFieldsFollow.iterator(), " and "); this.followSourceWhere = StringUtils.join(qFieldsFollow.iterator(), " and ");
if (operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == GroupAggregation.class) { if (isGroupUpdate) {
this.groupAggregationRefresh = new GroupAggregationRefresh(this, qFieldsRefresh); 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(), targetEntity.getPrimaryField().getName(), targetEntity.getName(),
StringUtils.join(qFields.iterator(), " and ")); StringUtils.join(qFields.iterator(), " and "));
Object[] targetRecord = Application.createQueryNoFilter(ql).unique(); Object[] targetRecord = Application.createQueryNoFilter(sql).unique();
if (targetRecord != null) { if (targetRecord != null) {
targetRecordId = (ID) targetRecord[0]; targetRecordId = (ID) targetRecord[0];
return; return;

View file

@ -59,7 +59,7 @@ public class GroupAggregationRefresh {
} }
} }
// 部刷新 // 量刷新性能较低
if (targetWhere.size() <= 1) { if (targetWhere.size() <= 1) {
targetWhere.clear(); targetWhere.clear();
targetWhere.add("(1=1)"); targetWhere.add("(1=1)");
@ -72,6 +72,7 @@ public class GroupAggregationRefresh {
entity.getPrimaryField().getName(), entity.getPrimaryField().getName(),
entity.getName(), entity.getName(),
StringUtils.join(targetWhere, " or ")); StringUtils.join(targetWhere, " or "));
Object[][] targetArray = Application.createQueryNoFilter(sql).array(); Object[][] targetArray = Application.createQueryNoFilter(sql).array();
log.info("Maybe refresh target record(s) : {}", targetArray.length); log.info("Maybe refresh target record(s) : {}", targetArray.length);
@ -98,6 +99,7 @@ public class GroupAggregationRefresh {
} }
ID fakeUpdateReferenceId = null; ID fakeUpdateReferenceId = null;
// 1.尝试获取触发源 // 1.尝试获取触发源
for (int i = 0; i < o.length - 1; i++) { for (int i = 0; i < o.length - 1; i++) {
Object mayId = o[i]; Object mayId = o[i];
@ -140,9 +142,6 @@ public class GroupAggregationRefresh {
try { try {
ga.execute(oCtx); ga.execute(oCtx);
// } catch (Throwable ex) {
// // v3.1 出现异常可能导致事物回滚执行因此此处 catch 并无意义
// log.error("Error on trigger ({}) refresh record : {}", parentAc.getConfigId(), targetRecordId, ex);
} finally { } finally {
ga.clean(); ga.clean();
} }

View file

@ -13,45 +13,67 @@ import com.rebuild.core.Application;
import com.rebuild.core.BootEnvironmentPostProcessor; import com.rebuild.core.BootEnvironmentPostProcessor;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; 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 对存储 * K/V 对存储
* *
* @author devezhao * @author devezhao
* @since 2019/11/22 * @since 2019/11/22
*/ */
@Slf4j
public class KVStorage { public class KVStorage {
public static final Object SETNULL = new Object(); public static final Object SETNULL = new Object();
private static final String CUSTOM_PREFIX = "custom.";
/** /**
* 存储 *
*
* @param key 会自动加 `custom.` 前缀 * @param key 会自动加 `custom.` 前缀
* @return * @return
*/ */
public static String getCustomValue(String key) { public static String getCustomValue(String key) {
return getValue("custom." + key, false, null); return getValue(CUSTOM_PREFIX + key, false, null);
} }
/** /**
* 获取 *
* * @param key
* @param key 会自动加 `custom.` 前缀
* @param value * @param value
*/ */
public static void setCustomValue(String key, Object 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 * @param key
*/ */
public static void removeCustomValue(String key) { public static void removeCustomValue(String key) {
setCustomValue(key, SETNULL); setCustomValue(key, SETNULL);
} }
// -- RAW
/** /**
* @param key * @param key
* @param value * @param value
@ -130,4 +152,26 @@ public class KVStorage {
return value; 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);
}
} }

View file

@ -24,8 +24,8 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
/** /**
* @author ZHAO * !!!! 请勿修改或删除本文件
* @since 2019-08-23 * !!!! 请严格遵守REBUILD 用户服务协议https://getrebuild.com/legal/service-terms
*/ */
@Slf4j @Slf4j
public final class License { public final class License {

View file

@ -12,7 +12,7 @@ import com.alibaba.fastjson.JSON;
/** /**
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-20 * @since 1.0, 2018-6-20
*/ */
public interface DataListBuilder { public interface DataListBuilder {

View file

@ -34,7 +34,7 @@ import java.util.Map;
* 数据列表数据构建 * 数据列表数据构建
* *
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-20 * @since 1.0, 2018-6-20
*/ */
public class DataListBuilderImpl implements DataListBuilder { public class DataListBuilderImpl implements DataListBuilder {

View file

@ -36,7 +36,7 @@ import java.util.Map;
* 数据包装 * 数据包装
* *
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-20 * @since 1.0, 2018-6-20
*/ */
public class DataListWrapper { public class DataListWrapper {
@ -52,7 +52,7 @@ public class DataListWrapper {
// 信息脱敏 // 信息脱敏
protected boolean useDesensitized = false; protected boolean useDesensitized = false;
private boolean secWrapper = true; private boolean mixWrapper = true;
/** /**
@ -93,6 +93,8 @@ public class DataListWrapper {
*/ */
public JSON toJson() { public JSON toJson() {
final Field nameFiled = entity.getNameField(); final Field nameFiled = entity.getNameField();
final EasyField nameFieldEasy = EasyMetaFactory.valueOf(nameFiled);
final int joinFieldsLen = queryJoinFields == null ? 0 : queryJoinFields.size(); final int joinFieldsLen = queryJoinFields == null ? 0 : queryJoinFields.size();
final int selectFieldsLen = selectFields.length - joinFieldsLen; final int selectFieldsLen = selectFields.length - joinFieldsLen;
@ -113,32 +115,39 @@ public class DataListWrapper {
continue; continue;
} }
final Object value = row[colIndex]; Object value = row[colIndex];
if (value == null) { if (value == null) {
row[colIndex] = StringUtils.EMPTY; row[colIndex] = StringUtils.EMPTY;
continue; continue;
} }
SelectItem fieldItem = selectFields[colIndex]; SelectItem fieldItem = selectFields[colIndex];
Field field = fieldItem.getField(); Field fieldMeta = fieldItem.getField();
if (field.equals(nameFiled) && !fieldItem.getFieldPath().contains(".")) {
nameValue = value;
}
// 如果最终没能取得名称字段则补充 // Last
if (field.getType() == FieldType.PRIMARY) { if (fieldMeta.getType() == FieldType.PRIMARY) {
// 如果最终没能取得名称字段则补充
if (nameValue == null) { if (nameValue == null) {
nameValue = FieldValueHelper.getLabel((ID) value, StringUtils.EMPTY); nameValue = FieldValueHelper.getLabel((ID) value, StringUtils.EMPTY);
} else { if (isUseDesensitized(nameFieldEasy)) {
nameValue = FieldValueHelper.wrapFieldValue(nameValue, nameFiled, true); nameValue = FieldValueHelper.desensitized(nameFieldEasy, nameValue);
if (nameValue == null) {
nameValue = StringUtils.EMPTY;
} }
} else {
nameValue = FieldValueHelper.wrapFieldValue(nameValue, nameFieldEasy, true);
if (nameValue == null) nameValue = StringUtils.EMPTY;
} }
((ID) value).setLabel(nameValue); ((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 // v2.10 Color
if (value != null && this.secWrapper) { if (value != null && this.mixWrapper) {
if (easyField.getDisplayType() == DisplayType.PICKLIST) { if (easyField.getDisplayType() == DisplayType.PICKLIST) {
String color = PickListManager.instance.getColor((ID) originValue); String color = PickListManager.instance.getColor((ID) originValue);
if (StringUtils.isNotBlank(color)) { if (StringUtils.isNotBlank(color)) {
@ -237,9 +246,9 @@ public class DataListWrapper {
/** /**
* 进一步封装查询结果 * 进一步封装查询结果
* *
* @param secWrapper * @param mixWrapper
*/ */
public void setSecWrapper(boolean secWrapper) { public void setMixWrapper(boolean mixWrapper) {
this.secWrapper = secWrapper; this.mixWrapper = mixWrapper;
} }
} }

View file

@ -190,18 +190,27 @@ public class ProtocolFilterParser {
protected String parseCategory(String entity, String value) { protected String parseCategory(String entity, String value) {
Entity rootEntity = MetadataHelper.getEntity(entity); Entity rootEntity = MetadataHelper.getEntity(entity);
Field categoryField = DataListCategory.instance.getFieldOfCategory(rootEntity); 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); DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
value = StringEscapeUtils.escapeSql(value);
if (dt == DisplayType.MULTISELECT) { if (dt == DisplayType.MULTISELECT) {
return String.format("%s && %d", categoryField.getName(), ObjectUtils.toInt(value)); return String.format("%s && %d", categoryField.getName(), ObjectUtils.toInt(value));
} else if (dt == DisplayType.N2NREFERENCE) { } else if (dt == DisplayType.N2NREFERENCE) {
return String.format( return String.format(
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')", "exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')",
rootEntity.getPrimaryField().getName(), categoryField.getName(), StringEscapeUtils.escapeSql(value)); rootEntity.getPrimaryField().getName(), categoryField.getName(), value);
} else { } else if (dt == DisplayType.DATETIME || dt == DisplayType.DATE) {
return String.format("%s = '%s'", categoryField.getName(), StringEscapeUtils.escapeSql(value)); 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);
} }
} }

View file

@ -28,7 +28,7 @@ import java.util.*;
* 列表查询解析 * 列表查询解析
* *
* @author Zhao Fangfang * @author Zhao Fangfang
* @since 1.0, 2013-6-20 * @since 1.0, 2018-6-20
*/ */
public class QueryParser { public class QueryParser {

View file

@ -43,7 +43,7 @@ public class LoginLogController extends EntityController {
public ModelAndView pageList(HttpServletRequest request) { public ModelAndView pageList(HttpServletRequest request) {
ID user = getRequestUser(request); ID user = getRequestUser(request);
ModelAndView mv = createModelAndView("/admin/audit/login-logs", "LoginLog", user); 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)); mv.getModel().put("DataListConfig", JSON.toJSONString(config));
return mv; return mv;
} }

View file

@ -41,7 +41,7 @@ public class DepartmentController extends EntityController {
final ID user = getRequestUser(request); final ID user = getRequestUser(request);
ModelAndView mv = createModelAndView("/admin/bizuser/dept-list", "Department", user); 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)); mv.getModel().put("DataListConfig", JSON.toJSONString(config));
return mv; return mv;
} }

View file

@ -46,7 +46,7 @@ public class TeamController extends EntityController {
final ID user = getRequestUser(request); final ID user = getRequestUser(request);
ModelAndView mv = createModelAndView("/admin/bizuser/team-list", "Team", user); 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)); mv.getModel().put("DataListConfig", JSON.toJSONString(config));
return mv; return mv;
} }

View file

@ -48,7 +48,7 @@ public class UserController extends EntityController {
final ID user = getRequestUser(request); final ID user = getRequestUser(request);
ModelAndView mv = createModelAndView("/admin/bizuser/user-list", "User", user); 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("DataListConfig", JSON.toJSONString(config));
mv.getModel().put("serviceMail", SMSender.availableMail()); mv.getModel().put("serviceMail", SMSender.availableMail());
return mv; return mv;

View file

@ -15,10 +15,7 @@ import com.rebuild.core.Application;
import com.rebuild.core.support.RebuildConfiguration; import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.QiniuCloud; import com.rebuild.core.support.integration.QiniuCloud;
import com.rebuild.utils.AppUtils; import com.rebuild.utils.*;
import com.rebuild.utils.ImageView2;
import com.rebuild.utils.OkHttpUtils;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController; import com.rebuild.web.BaseController;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -129,7 +126,7 @@ public class FileDownloader extends BaseController {
// 共享查看 // 共享查看
if (request.getRequestURI().contains("/filex/access/")) { if (request.getRequestURI().contains("/filex/access/")) {
String e = getParameter(request, "e"); String e = getParameter(request, "e");
if (StringUtils.isBlank(e) || Application.getCommonsCache().get(e) == null) { if (!checkEsign(e)) {
response.sendError(HttpStatus.FORBIDDEN.value(), Language.L("分享的文件已过期")); response.sendError(HttpStatus.FORBIDDEN.value(), Language.L("分享的文件已过期"));
return; return;
} }
@ -161,16 +158,22 @@ public class FileDownloader extends BaseController {
@GetMapping(value = "read-raw") @GetMapping(value = "read-raw")
public void readRaw(HttpServletRequest request, HttpServletResponse response) throws IOException { public void readRaw(HttpServletRequest request, HttpServletResponse response) throws IOException {
RbAssert.isAllow(checkUser(request), "Unauthorized access");
String filePath = getParameterNotNull(request, "url"); String filePath = getParameterNotNull(request, "url");
boolean fullUrl = CommonsUtils.isExternalUrl(filePath);
String charset = getParameter(request, "charset", AppUtils.UTF8); String charset = getParameter(request, "charset", AppUtils.UTF8);
String content; String content;
if (QiniuCloud.instance().available()) { if (QiniuCloud.instance().available()) {
String privateUrl = QiniuCloud.instance().makeUrl(filePath); String privateUrl = fullUrl ? filePath : QiniuCloud.instance().makeUrl(filePath);
content = OkHttpUtils.get(privateUrl, null, charset); content = OkHttpUtils.get(privateUrl, null, charset);
} else { } 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 // Local storage
filePath = checkFilePath(filePath); filePath = checkFilePath(filePath);
File file = RebuildConfiguration.getFileOfData(filePath); File file = RebuildConfiguration.getFileOfData(filePath);
@ -209,6 +212,11 @@ public class FileDownloader extends BaseController {
return user != null; 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) { private static String checkFilePath(String filepath) {
filepath = CodecUtils.urlDecode(filepath); filepath = CodecUtils.urlDecode(filepath);
filepath = filepath.replace("\\", "/"); filepath = filepath.replace("\\", "/");

View file

@ -139,7 +139,7 @@ public class ShowFieldsController extends BaseController implements ShareTo {
raw = DataListManager.instance.getLayoutOfDatalist(user, entity); 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<>(); Map<String, Object> ret = new HashMap<>();
ret.put("fieldList", fieldList); ret.put("fieldList", fieldList);

View file

@ -27,9 +27,7 @@ import com.rebuild.core.service.query.ParseHelper;
import com.rebuild.core.support.general.DataListBuilder; import com.rebuild.core.support.general.DataListBuilder;
import com.rebuild.core.support.general.DataListBuilderImpl; import com.rebuild.core.support.general.DataListBuilderImpl;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.EntityController; import com.rebuild.web.EntityController;
import org.apache.commons.codec.language.bm.Lang;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -81,7 +79,7 @@ public class GeneralListController extends EntityController {
JSON listConfig = null; JSON listConfig = null;
if (listMode == 1) { if (listMode == 1) {
listConfig = DataListManager.instance.getFieldsLayout(entity, user); listConfig = DataListManager.instance.getListFields(entity, user);
// 扩展配置 // 扩展配置
String advListHideFilters = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS); String advListHideFilters = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS);

View file

@ -284,7 +284,7 @@ public class ReferenceSearchController extends EntityController {
ModelAndView mv = createModelAndView("/general/reference-search"); ModelAndView mv = createModelAndView("/general/reference-search");
putEntityMeta(mv, searchEntity); 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)); mv.getModel().put("DataListConfig", JSON.toJSONString(config));
// 可新建 // 可新建

View file

@ -170,7 +170,7 @@ public class RelatedListController extends BaseController {
@GetMapping("related-list-config") @GetMapping("related-list-config")
public RespBody getDataListConfig(HttpServletRequest req, @EntityParam Entity listEntity) { public RespBody getDataListConfig(HttpServletRequest req, @EntityParam Entity listEntity) {
final ID user = getRequestUser(req); 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); return RespBody.ok(config);
} }
} }

View file

@ -10,6 +10,7 @@ package com.rebuild.web.robot.trigger;
import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.MetadataSorter; 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.CommonsLock;
import com.rebuild.core.support.License; import com.rebuild.core.support.License;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseController; import com.rebuild.web.BaseController;
import com.rebuild.web.admin.ConfigCommons; import com.rebuild.web.admin.ConfigCommons;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -113,7 +115,7 @@ public class TriggerAdminController extends BaseController {
public Object[][] triggerList(HttpServletRequest request) { public Object[][] triggerList(HttpServletRequest request) {
String belongEntity = getParameter(request, "entity"); String belongEntity = getParameter(request, "entity");
String q = getParameter(request, "q"); 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)" + " where (1=1) and (2=2)" +
" order by modifiedOn desc, name"; " order by modifiedOn desc, name";
@ -121,7 +123,27 @@ public class TriggerAdminController extends BaseController {
for (Object[] o : array) { for (Object[] o : array) {
o[7] = Language.L(ActionType.valueOf((String) o[7])); o[7] = Language.L(ActionType.valueOf((String) o[7]));
o[8] = CommonsLock.getLockedUserFormat((ID) o[8]); o[8] = CommonsLock.getLockedUserFormat((ID) o[8]);
// 目标实体
o[10] = parseTargetEntity((String) o[10], (String) o[1]);
} }
return array; 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());
}
}
} }

View file

@ -5,6 +5,11 @@
<meta name="page-help" content="https://getrebuild.com/docs/admin/approval" /> <meta name="page-help" content="https://getrebuild.com/docs/admin/approval" />
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/approvals.css}" /> <link rel="stylesheet" type="text/css" th:href="@{/assets/css/approvals.css}" />
<title>[[${bundle.L('审批流程')}]]</title> <title>[[${bundle.L('审批流程')}]]</title>
<style>
tr.ui-sortable-handle.ui-sortable-helper td:first-child {
width: 339px;
}
</style>
</head> </head>
<body> <body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header" th:classappend="${sideCollapsedClazz}"> <div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header" th:classappend="${sideCollapsedClazz}">

View file

@ -47,8 +47,8 @@
<tr> <tr>
<th>[[${bundle.L('名称')}]]</th> <th>[[${bundle.L('名称')}]]</th>
<th>[[${bundle.L('源实体')}]]</th> <th>[[${bundle.L('源实体')}]]</th>
<th>[[${bundle.L('触发类型')}]]</th> <th width="20%">[[${bundle.L('触发类型')}]]</th>
<th width="26%" class="no-sort">[[${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="int-sort">[[${bundle.L('优先级')}]]</th>
<th width="80" class="no-sort">[[${bundle.L('启用')}]]</th> <th width="80" class="no-sort">[[${bundle.L('启用')}]]</th>
<th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th> <th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th>

View file

@ -449,44 +449,3 @@ See LICENSE and COMMERCIAL in the project root for license information.
.chart-list > div .chart-icon > i.SUNBURST { .chart-list > div .chart-icon > i.SUNBURST {
background-position: -1372px -1464px; 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;
}

View file

@ -174,3 +174,66 @@ body.fullscreen .J_dash-fullscreen .zmdi-fullscreen::before {
.chart-grid.uneditable .chart-box .chart-head .chart-title { .chart-grid.uneditable .chart-box .chart-head .chart-title {
cursor: default; 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;
}

View file

@ -3295,6 +3295,12 @@ form {
height: auto; height: auto;
} }
.preview-modal .container.fp-content.fullwidth {
max-width: 100%;
padding-left: 0;
padding-right: 0;
}
.file-share { .file-share {
padding: 10px 40px; padding: 10px 40px;
} }

View file

@ -140,6 +140,7 @@ $(document).ready(() => {
} }
const option = wpc.chartConfig.option || {} const option = wpc.chartConfig.option || {}
if (typeof option['mergeCell'] === undefined) option.mergeCell = true // fix: 3.1.3
for (let k in option) { for (let k in option) {
const opt = $(`.chart-option input[data-name=${k}]`) const opt = $(`.chart-option input[data-name=${k}]`)
if (opt.length > 0) { if (opt.length > 0) {

View file

@ -132,6 +132,8 @@ $(document).ready(function () {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
BaseChart.currentFullscreen && BaseChart.currentFullscreen.toggleFullscreen(true) 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') const chart_add = $('#chart-add')
if (chart_add.length > 0) gridstack.removeWidget(chart_add.parent()) 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 // Use gridstar
if (item.size_x || item.size_y) { 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) gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, true, 2, 12, 2, 24)

View file

@ -120,7 +120,7 @@ class RbPreview extends React.Component {
renderDoc() { renderDoc() {
return ( return (
<div className="container fp-content"> <div className={`container fp-content ${this.props.fullwidth && 'fullwidth'}`}>
<div className="iframe"> <div className="iframe">
{!this.state.docRendered && ( {!this.state.docRendered && (
<div className="must-center"> <div className="must-center">
@ -135,10 +135,10 @@ class RbPreview extends React.Component {
renderText() { renderText() {
return ( return (
<div className="container fp-content"> <div className={`container fp-content ${this.props.fullwidth && 'fullwidth'}`}>
<div className="iframe text"> <div className="iframe text">
{this.state.previewText || this.state.previewText === '' ? ( {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"> <div className="must-center">
<RbSpinner fully={true} /> <RbSpinner fully={true} />

View file

@ -70,7 +70,7 @@ class GridList extends React.Component {
setTimeout(() => location.reload(), 500) setTimeout(() => location.reload(), 500)
} else { } else {
this.disabled(false) this.disabled(false)
RbHighbar.error(res.error_msg) RbHighbar.error(WrapHtml(res.error_msg))
} }
}) })
}, },

View file

@ -99,8 +99,7 @@ function modeSave(newOption, next) {
}) })
} }
// const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'DATE', 'DATETIME', 'REFERENCE', 'N2NREFERENCE'] const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'DATE', 'DATETIME', 'REFERENCE', 'N2NREFERENCE']
const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
// 模式选项 // 模式选项
class DlgMode1Option extends RbFormHandler { class DlgMode1Option extends RbFormHandler {
@ -146,7 +145,7 @@ class DlgMode1Option extends RbFormHandler {
</div> </div>
<div className={`col-4 pl-0 ${this.state.advListShowCategoryFormats ? '' : 'hide'}`}> <div className={`col-4 pl-0 ${this.state.advListShowCategoryFormats ? '' : 'hide'}`}>
<label className="mb-1">{$L('字段格式')}</label> <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 &&
this.state.advListShowCategoryFormats.map((item) => { this.state.advListShowCategoryFormats.map((item) => {
return ( return (
@ -213,11 +212,11 @@ class DlgMode1Option extends RbFormHandler {
$catFields = $('.advListShowCategory-set select:eq(0)') $catFields = $('.advListShowCategory-set select:eq(0)')
$catFormats = $('.advListShowCategory-set select:eq(1)') $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 = [] const _data = []
res.data && res.data &&
res.data.forEach((item) => { 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) _data.push(item)
} }
}) })
@ -240,10 +239,10 @@ class DlgMode1Option extends RbFormHandler {
let formats let formats
if (found && found.type === 'CLASSIFICATION') { if (found && found.type === 'CLASSIFICATION') {
formats = [ formats = [
[0, $L('%d 级分类', 1)], // [0, $L('%d 级分类', 1)],
[1, $L('%d 级分类', 2)], // [1, $L('%d 级分类', 2)],
[2, $L('%d 级分类', 3)], // [2, $L('%d 级分类', 3)],
[3, $L('%d 级分类', 4)], // [3, $L('%d 级分类', 4)],
] ]
} else if (found && (found.type === 'DATE' || found.type === 'DATETIME')) { } else if (found && (found.type === 'DATE' || found.type === 'DATETIME')) {
formats = [ formats = [

View file

@ -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. See LICENSE and COMMERCIAL in the project root for license information.
*/ */
$(document).ready(function () { $(document).ready(() => {
const entity = $urlp('entity') const entity = $urlp('entity')
const settingsUrl = `/admin/entity/${entity}/list-stats` const settingsUrl = `/admin/entity/${entity}/list-stats`
@ -87,6 +87,9 @@ const render_set = function (item) {
return return
} }
// 唯一
if (!item.key2) item.key2 = $random('stat-')
const $to = $('.set-items') const $to = $('.set-items')
const calc = item.calc || 'SUM' 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) $(`<li class="dropdown-item" data-calc='_LABEL'>${$L('显示样式')}</li>`).appendTo($ul)
$ul.find('.dropdown-item').on('click', function () { $ul.find('.dropdown-item').on('click', function () {
const calc = $(this).data('calc') const c = $(this).data('calc')
if (calc === '_LABEL') { if (c === '_LABEL') {
if (ShowStyles_Comps[item.name]) { if (ShowStyles_Comps[item.key2]) {
ShowStyles_Comps[item.name].show() ShowStyles_Comps[item.key2].show()
} else { } else {
renderRbcomp( renderRbcomp(
// eslint-disable-next-line react/jsx-no-undef // eslint-disable-next-line react/jsx-no-undef
@ -126,12 +129,12 @@ const render_set = function (item) {
/>, />,
null, null,
function () { function () {
ShowStyles_Comps[item.name] = this ShowStyles_Comps[item.key2] = this
} }
) )
} }
} else { } 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]})`)
} }
}) })
} }

View file

@ -70,7 +70,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
$.ajaxSetup({ $.ajaxSetup({
headers: { headers: {
'Content-Type': 'text/plain;charset=utf-8', 'Content-Type': 'text/plain;charset=utf-8',
'X-Client': 'RB/WEB-2.9', 'X-Client': 'RB/WEB',
'X-CsrfToken': rb.csrfToken || '', 'X-CsrfToken': rb.csrfToken || '',
'X-AuthToken': rb.authToken || '', 'X-AuthToken': rb.authToken || '',
}, },

View file

@ -178,11 +178,12 @@ $(function () {
}) })
}) })
if (rb.commercial === 11) { // if (rb.commercial === 11) {
$('a[target="_blank"]').each(function () { // $('a[target="_blank"]').each(function () {
if (($(this).attr('href') || '').indexOf('getrebuild.com') > -1) $(this).removeAttr('href') // if (($(this).attr('href') || '').indexOf('getrebuild.com') > -1) $(this).removeAttr('href')
}) // })
} // $('.commercial11').addClass('hide')
// }
}) })
var $addResizeHandler__calls = [] var $addResizeHandler__calls = []

View file

@ -67,7 +67,14 @@ class TriggerList extends ConfigList {
<a href={`trigger/${item[0]}`}>{item[3] || item[2] + ' · ' + item[7]}</a> <a href={`trigger/${item[0]}`}>{item[3] || item[2] + ' · ' + item[7]}</a>
</td> </td>
<td>{item[2] || item[1]}</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 className="text-wrap">{item[6] > 0 ? $L(' %s ', formatWhen(item[6])) : <span className="text-warning">({$L('无触发动作')})</span>}</td>
<td> <td>
<span className="badge badge-light">{item[9]}</span> <span className="badge badge-light">{item[9]}</span>

View file

@ -34,7 +34,7 @@
</script> </script>
<script type="text/babel"> <script type="text/babel">
$(document).ready(() => { $(document).ready(() => {
renderRbcomp(<RbPreview urls={[window.__PageConfig.publicUrl]} unclose />) renderRbcomp(<RbPreview urls={[window.__PageConfig.publicUrl]} unclose fullwidth />)
}) })
</script> </script>
</body> </body>

View file

@ -98,7 +98,7 @@
<span class="custom-control-label">[[${bundle.L('显示汇总')}]]</span> <span class="custom-control-label">[[${bundle.L('显示汇总')}]]</span>
</label> </label>
<label class="custom-control custom-control-sm custom-checkbox"> <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> <span class="custom-control-label">[[${bundle.L('自动合并单元格')}]]</span>
</label> </label>
</div> </div>

View file

@ -30,6 +30,11 @@
</div> </div>
</div> </div>
<div class="col-sm-6 text-right d-none d-md-block dash-right"> <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"> <div class="btn-group">
<button type="button" class="btn btn-link pr-0 text-right J_dash-fullscreen" th:title="${bundle.L('全屏')}"> <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> <i class="zmdi zmdi-fullscreen icon" style="font-size: 1.45rem"></i>

View file

@ -43,7 +43,7 @@ public class SeriesGeneratorTest extends TestSupport {
void testIncrementVarNThreads() { void testIncrementVarNThreads() {
final IncreasingVar var = new IncreasingVar("0000", getSeriesField(), "Y"); final IncreasingVar var = new IncreasingVar("0000", getSeriesField(), "Y");
final Set<String> set = Collections.synchronizedSet(new HashSet<>()); final Set<String> set = Collections.synchronizedSet(new HashSet<>());
final int N = 200; final int N = 1000;
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
new Thread(() -> { new Thread(() -> {
String s = var.generate(); String s = var.generate();
@ -51,7 +51,7 @@ public class SeriesGeneratorTest extends TestSupport {
System.out.println(s + " << " + Thread.currentThread().getName()); System.out.println(s + " << " + Thread.currentThread().getName());
}).start(); }).start();
} }
ThreadPool.waitFor(2000); ThreadPool.waitFor(1200);
Assertions.assertEquals(set.size(), N); Assertions.assertEquals(set.size(), N);
} }

View file

@ -54,7 +54,7 @@ public class DataListBuilderTest extends TestSupport {
@Test @Test
public void testColumnLayout() { public void testColumnLayout() {
JSON layout = DataListManager.instance.getFieldsLayout(Account, SIMPLE_USER); JSON layout = DataListManager.instance.getListFields(Account, SIMPLE_USER);
System.out.println(layout); System.out.println(layout);
} }
} }