fix: Multiple fields of the same entity refer to the same entity

This commit is contained in:
devezhao 2020-03-21 20:25:57 +08:00
parent 519d62bca8
commit 5eeff3c3f1
8 changed files with 186 additions and 117 deletions

View file

@ -1,19 +1,8 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.api;
@ -108,12 +97,12 @@ public class ApiGateway extends Controll {
* 验证请求并构建请求上下文
*
* @param parameterMap
* @param post
* @param postData
* @param request
* @return
* @throws IOException
*/
protected ApiContext verfiy(Map<String, String[]> parameterMap, String post, HttpServletRequest request) throws IOException {
protected ApiContext verfiy(Map<String, String[]> parameterMap, String postData, HttpServletRequest request) throws IOException {
Map<String, String> sortedMap = new TreeMap<>();
for (Map.Entry<String, String[]> e : parameterMap.entrySet()) {
String[] vv = e.getValue();
@ -162,7 +151,7 @@ public class ApiGateway extends Controll {
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Invalid sign=" + sign);
}
JSON postJson = post != null ? (JSON) JSON.parse(post) : null;
JSON postJson = postData != null ? (JSON) JSON.parse(postData) : null;
ID bindUser = apiConfig.getID("bindUser");
return new ApiContext(sortedMap, postJson, appid, bindUser);
}
@ -201,7 +190,7 @@ public class ApiGateway extends Controll {
* @param result
*/
protected void logRequestAsync(Date requestTime, String remoteIp, String apiName, ApiContext context, JSON result) {
if (context == null || result == null) {
if (context == null || result == null || !isLogRequest()) {
return;
}
@ -220,6 +209,15 @@ public class ApiGateway extends Controll {
});
}
/**
* 是否记录日志
*
* @return
*/
protected boolean isLogRequest() {
return true;
}
// -- 注册 API
private static final Map<String, Class<? extends BaseApi>> API_CLASSES = new HashMap<>();

View file

@ -1,19 +1,8 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.server.configuration.portals;
@ -25,12 +14,17 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.ConfigEntry;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.entity.EasyMeta;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 视图-相关项/新建相关
@ -42,7 +36,13 @@ public class ViewAddonsManager extends BaseLayoutManager {
public static final ViewAddonsManager instance = new ViewAddonsManager();
private ViewAddonsManager() { }
/**
* A实体中可能引用了两次B实体2个引用字段都引用B因此在设置相关项时必须包含哪个字段Entity.Field
* v1.9 之前上述场景存在问题
*/
public static final String EF_SPLIT = ".";
/**
* @param entity
* @param user
@ -82,14 +82,17 @@ public class ViewAddonsManager extends BaseLayoutManager {
final ConfigEntry config = getLayout(user, entity, applyType);
final Permission useAction = TYPE_TAB.equals(applyType) ? BizzPermission.READ : BizzPermission.CREATE;
final Entity entityMeta = MetadataHelper.getEntity(entity);
final Set<Entity> mfRefs = hasMultiFieldsReferenceTo(entityMeta);
// 未配置则使用全部相关项
if (config == null) {
JSONArray refs = new JSONArray();
for (Field field : MetadataHelper.getEntity(entity).getReferenceToFields(true)) {
for (Field field : entityMeta.getReferenceToFields(true)) {
Entity e = field.getOwnEntity();
if (e.getMasterEntity() == null &&
Application.getSecurityManager().allow(user, e.getEntityCode(), useAction)) {
refs.add(EasyMeta.getEntityShow(e));
refs.add(getEntityShow(field, mfRefs, applyType));
}
}
return refs;
@ -97,14 +100,71 @@ public class ViewAddonsManager extends BaseLayoutManager {
JSONArray addons = new JSONArray();
for (Object o : (JSONArray) config.getJSON("config")) {
String e = (String) o;
if (MetadataHelper.containsEntity(e)) {
Entity entityMeta = MetadataHelper.getEntity(e);
if (Application.getSecurityManager().allow(user, entityMeta.getEntityCode(), useAction)) {
addons.add(EasyMeta.getEntityShow(entityMeta));
// Entity.Field (v1.9)
String[] e = ((String) o).split("\\.");
if (!MetadataHelper.containsEntity(e[0])) {
continue;
}
Entity addonEntity = MetadataHelper.getEntity(e[0]);
if (e.length > 1 && !MetadataHelper.checkAndWarnField(addonEntity, e[1])) {
continue;
}
if (Application.getSecurityManager().allow(user, addonEntity.getEntityCode(), useAction)) {
if (e.length > 1) {
addons.add(getEntityShow(addonEntity.getField(e[1]), mfRefs, applyType));
} else {
addons.add(EasyMeta.getEntityShow(addonEntity));
}
}
}
return addons;
}
/**
* 同一实体的多个字段引用同一个实体
*
* @return
*/
public static Set<Entity> hasMultiFieldsReferenceTo(Entity entity) {
Map<Entity, Integer> map = new HashMap<>();
for (Field field : entity.getReferenceToFields(true)) {
Entity e = field.getOwnEntity();
// 排除明细实体
if (e.getMasterEntity() == null) {
int t = map.getOrDefault(e, 0);
map.put(e, t + 1);
}
}
Set<Entity> set = new HashSet<>();
for (Map.Entry<Entity, Integer> e : map.entrySet()) {
if (e.getValue() > 1) {
set.add(e.getKey());
}
}
return set;
}
/**
* @param field
* @param mfRefs
* @param applyType
* @return
* @see EasyMeta#getEntityShow(Entity)
*/
private JSONObject getEntityShow(Field field, Set<Entity> mfRefs, String applyType) {
Entity fieldEntity = field.getOwnEntity();
JSONObject show = (JSONObject) EasyMeta.getEntityShow(fieldEntity);
show.put("entity", fieldEntity.getName() + EF_SPLIT + field.getName());
if (mfRefs.contains(fieldEntity)) {
String entityLabel = TYPE_TAB.equalsIgnoreCase(applyType)
? String.format("%s (%s)", EasyMeta.getLabel(field), show.getString("entityLabel"))
: String.format("%s (%s)", show.getString("entityLabel"), EasyMeta.getLabel(field));
show.put("entityLabel", entityLabel);
}
return show;
}
}

View file

@ -1,19 +1,8 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.web.admin.entityhub;
@ -88,16 +77,24 @@ public class ViewAddonsControll extends BaseControll implements PortalsConfigura
Entity entityMeta = MetadataHelper.getEntity(entity);
ConfigEntry config = ViewAddonsManager.instance.getLayout(user, entity, applyType);
Set<Entity> mfRefs = ViewAddonsManager.hasMultiFieldsReferenceTo(entityMeta);
Set<String[]> refs = new HashSet<>();
for (Field field : entityMeta.getReferenceToFields(true)) {
Entity e = field.getOwnEntity();
if (e.getMasterEntity() != null) {
continue;
}
refs.add(new String[] { e.getName(), EasyMeta.getLabel(e) });
String label = EasyMeta.getLabel(e);
if (mfRefs.contains(e)) {
label = EasyMeta.getLabel(field) + " (" + label + ")";
}
refs.add(new String[] { e.getName() + ViewAddonsManager.EF_SPLIT + field.getName(), label });
}
// refs.add(new String[] { "Feeds", "跟进" }); // 动态-跟进
// TODO 相关项显示 动态-跟进
JSON ret = JSONUtils.toJSONObject(
new String[] { "config", "refs" },

View file

@ -1,19 +1,8 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.web.base.general;
@ -60,17 +49,19 @@ public class RelatedListControll extends BaseControll {
public void relatedList(HttpServletRequest request, HttpServletResponse response) {
ID masterId = getIdParameterNotNull(request, "masterId");
String related = getParameterNotNull(request, "related");
Entity relatedEntity = MetadataHelper.getEntity(related);
String sql = buildMasterSql(masterId, relatedEntity, false);
String sql = buildMasterSql(masterId, related, false);
String[] e = related.split("\\.");
Field nameField = MetadataHelper.getNameField(e[0]);
int pn = NumberUtils.toInt(getParameter(request, "pageNo"), 1);
int ps = NumberUtils.toInt(getParameter(request, "pageSize"), 200);
Object[][] array = Application.createQuery(sql).setLimit(ps, pn * ps - ps).array();
for (Object[] o : array) {
Object nameValue = o[1];
nameValue = FieldValueWrapper.instance.wrapFieldValue(nameValue, MetadataHelper.getNameField(relatedEntity), true);
nameValue = FieldValueWrapper.instance.wrapFieldValue(nameValue, nameField, true);
if (nameValue == null || StringUtils.isEmpty(nameValue.toString())) {
nameValue = FieldValueWrapper.NO_LABEL_PREFIX + o[0].toString().toUpperCase();
}
@ -87,11 +78,11 @@ public class RelatedListControll extends BaseControll {
@RequestMapping("related-counts")
public void relatedCounts(HttpServletRequest request, HttpServletResponse response) {
ID masterId = getIdParameterNotNull(request, "masterId");
String[] relates = getParameterNotNull(request, "relateds").split(",");
String[] relateds = getParameterNotNull(request, "relateds").split(",");
Map<String, Integer> countMap = new HashMap<>();
for (String related : relates) {
String sql = buildMasterSql(masterId, MetadataHelper.getEntity(related), true);
for (String related : relateds) {
String sql = buildMasterSql(masterId, related, true);
if (sql != null) {
Object[] count = Application.createQuery(sql).unique();
countMap.put(related, ObjectUtils.toInt(count[0]));
@ -102,27 +93,37 @@ public class RelatedListControll extends BaseControll {
/**
* @param recordOfMain
* @param relatedEntity
* @param relatedExpr
* @param count
* @return
*/
private String buildMasterSql(ID recordOfMain, Entity relatedEntity, boolean count) {
Entity masterEntity = MetadataHelper.getEntity(recordOfMain.getEntityCode());
private String buildMasterSql(ID recordOfMain, String relatedExpr, boolean count) {
// Entity.Field
String[] e = relatedExpr.split("\\.");
Entity relatedEntity = MetadataHelper.getEntity(e[0]);
Set<String> relatedFields = new HashSet<>();
for (Field field : relatedEntity.getFields()) {
if ((field.getType() == FieldType.REFERENCE || field.getType() == FieldType.ANY_REFERENCE)
&& ArrayUtils.contains(field.getReferenceEntities(), masterEntity)) {
relatedFields.add(field.getName() + " = ''{0}''");
if (e.length > 1) {
relatedFields.add(e[1]);
} else {
// v1.9 之前会把所有相关的查出来
Entity masterEntity = MetadataHelper.getEntity(recordOfMain.getEntityCode());
for (Field field : relatedEntity.getFields()) {
if ((field.getType() == FieldType.REFERENCE || field.getType() == FieldType.ANY_REFERENCE)
&& ArrayUtils.contains(field.getReferenceEntities(), masterEntity)) {
relatedFields.add(field.getName());
}
}
}
if (relatedFields.isEmpty()) {
return null;
}
String masterSql = "(" + StringUtils.join(relatedFields, " or ") + ")";
masterSql = MessageFormat.format(masterSql, recordOfMain);
String baseSql = "select %s from " + relatedEntity.getName() + " where " + masterSql;
String masterWhere = "(" + StringUtils.join(relatedFields, " = ''{0}'' or ") + " = ''{0}'')";
masterWhere = MessageFormat.format(masterWhere, recordOfMain);
String baseSql = "select %s from " + relatedEntity.getName() + " where " + masterWhere;
Field primaryField = relatedEntity.getPrimaryField();
Field namedField = MetadataHelper.getNameField(relatedEntity);

View file

@ -30,27 +30,24 @@
<%@ include file="/_include/Foot.jsp"%>
<script src="${baseUrl}/assets/js/sortable.js"></script>
<script type="text/babel">
$(document).ready(function(){
const entity = $urlp('entity'), type = $urlp('type')
$(document).ready(function () {
const entity = $urlp('entity'),
type = $urlp('type')
const _url = '/admin/entity/' + entity + '/view-addons?type=' + type
$.get(_url, function(res){
$(res.data.refs).each(function(){ render_unset(this) })
$(res.data.config).each(function(){
$('.unset-list li[data-key="' + this + '"]').trigger('click')
})
$.get(_url, function (res) {
$(res.data.refs).each(function () { render_unset(this) })
$(res.data.config).each(function () { $('.unset-list li[data-key="' + this + '"]').trigger('click') })
if (!res.data.refs || res.data.refs.length == 0) $('<li class="dd-item nodata">无可用相关项</li>').appendTo('.unset-list')
})
let _btn = $('.J_save').click(function(){
let config = []
$('.J_config>li').each(function(){
config.push($(this).data('key'))
})
_btn.button('loading')
$.post(_url, JSON.stringify(config), function(res){
_btn.button('reset')
const $btn = $('.J_save').click(function () {
const config = []
$('.J_config>li').each(function () { config.push($(this).data('key')) })
$btn.button('loading')
$.post(_url, JSON.stringify(config), function (res) {
$btn.button('reset')
if (res.error_code == 0) parent.location.reload()
})
})

View file

@ -1,3 +1,10 @@
/*
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
html,
body {
overflow: auto;
@ -88,7 +95,7 @@ body {
}
.nav-tabs>li.nav-item a.nav-link {
padding: 11px 15px;
padding: 11px 10px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
@ -124,6 +131,7 @@ body {
right: -4px;
font-size: .8462rem;
font-weight: normal;
line-height: 1.2;
}
.related-list {

View file

@ -77,7 +77,12 @@ class RbFormModal extends React.Component {
show(state) {
state = state || {}
if (!state.id) state.id = null
if ((state.id !== this.state.id || state.entity !== this.state.entity) || this.state.isDestroy === true) {
//
const stateNew = [state.id, state.entity, state.initialValue]
const stateOld = [this.state.id, this.state.entity, this.state.initialValue]
if (this.state.isDestroy === true || JSON.stringify(stateNew) !== JSON.stringify(stateOld)) {
state = { formComponent: null, initialValue: null, inLoad: true, ...state }
this.setState(state, () => this.showAfter({ isDestroy: false }, true))
} else {

View file

@ -324,11 +324,12 @@ const RbViewPage = {
const that = this
that.__vtabEntities = []
$(config).each(function () {
const entity = this.entity
const tabId = 'tab-' + entity
const entity = this.entity // Entity.Field
that.__vtabEntities.push(entity)
const tabNav = $('<li class="nav-item"><a class="nav-link" href="#' + tabId + '" data-toggle="tab">' + this.entityLabel + '</a></li>').appendTo('.nav-tabs')
const tabPane = $('<div class="tab-pane" id="' + tabId + '"></div>').appendTo('.tab-content')
const tabId = 'tab-' + entity.replace('.', '--') // `.` is JS keyword
const tabNav = $(`<li class="nav-item"><a class="nav-link" href="#${tabId}" data-toggle="tab" title="${this.entityLabel}">${this.entityLabel}</a></li>`).appendTo('.nav-tabs')
const tabPane = $(`<div class="tab-pane" id="${tabId}"></div>`).appendTo('.tab-content')
tabNav.find('a').click(function () {
tabPane.find('.related-list').length === 0 && renderRbcomp(<RelatedList entity={entity} master={that.__id} />, tabPane)
})
@ -351,7 +352,7 @@ const RbViewPage = {
$.get(`/app/entity/related-counts?masterId=${this.__id}&relateds=${specEntities.join(',')}`, function (res) {
for (let k in (res.data || {})) {
if (~~res.data[k] > 0) {
const tabNav = $('.nav-tabs a[href="#tab-' + k + '"]')
const tabNav = $('.nav-tabs a[href="#tab-' + k.replace('.', '--') + '"]')
if (tabNav.find('.badge').length > 0) tabNav.find('.badge').text(res.data[k])
else $('<span class="badge badge-pill badge-primary">' + res.data[k] + '</span>').appendTo(tabNav)
}
@ -367,8 +368,10 @@ const RbViewPage = {
const $item = $(`<a class="dropdown-item"><i class="icon zmdi zmdi-${e.icon}"></i>新建${e.entityLabel}</a>`)
$item.click(function () {
const iv = {}
iv['&' + that.__entity[0]] = that.__id
RbFormModal.create({ title: `新建${e.entityLabel}`, entity: e.entity, icon: e.icon, initialValue: iv })
const entity = e.entity.split('.')
if (entity.length > 1) iv[entity[1]] = that.__id
else iv['&' + that.__entity[0]] = that.__id
RbFormModal.create({ title: `新建${e.entityLabel}`, entity: entity[0], icon: e.icon, initialValue: iv })
})
$('.J_adds .dropdown-divider').before($item)
})