分配/共享

This commit is contained in:
devezhao-corp 2018-10-18 00:42:06 +08:00
parent f9d15cbd21
commit d5eec8fee4
43 changed files with 1264 additions and 331 deletions

View file

@ -35,6 +35,7 @@ import com.rebuild.server.query.QueryFactory;
import com.rebuild.server.service.CommonService;
import com.rebuild.server.service.SQLExecutor;
import com.rebuild.server.service.base.GeneralEntityService;
import com.rebuild.server.service.notification.NotificationService;
import com.rebuild.web.OnlineSessionStore;
import cn.devezhao.persist4j.PersistManagerFactory;
@ -158,11 +159,11 @@ public final class Application {
public static SQLExecutor getSQLExecutor() {
return getBean(SQLExecutor.class);
}
public static NotificationService getNotifications() {
return getBean(NotificationService.class);
}
/**
* @return
* @see Application#getGeneralEntityService(int)
*/
public static CommonService getCommonService() {
return getBean(CommonService.class);
}

View file

@ -50,7 +50,7 @@ public class DepartmentService extends GeneralEntityService {
@Override
public Record createOrUpdate(Record record) {
record = super.createOrUpdate(record);
Application.getUserStore().refreshDept(record.getPrimary());
Application.getUserStore().refreshDepartment(record.getPrimary());
return record;
}
}

View file

@ -23,11 +23,11 @@ import java.security.Guard;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.Assert;
import com.rebuild.server.Application;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.base.BulkContext;
import com.rebuild.server.service.base.GeneralEntityService;
import cn.devezhao.bizz.privileges.Permission;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
@ -59,62 +59,55 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
return;
}
Object idOrRecord = invocation.getArguments()[0];
ID recordId = null;
Entity entity = null;
ID caller = null;
if (idOrRecord instanceof Record) {
recordId = ((Record) idOrRecord).getPrimary();
entity = ((Record) idOrRecord).getEntity();
caller = ((Record) idOrRecord).getEditor();
} else if (idOrRecord instanceof ID) {
recordId = (ID) idOrRecord;
entity = MetadataHelper.getEntity(recordId.getEntityCode());
} else if (idOrRecord instanceof ID[]) {
Assert.isTrue(((ID[]) idOrRecord).length > 1, "Bulk must have at least 2 IDs");
entity = MetadataHelper.getEntity(((ID[]) idOrRecord)[0].getEntityCode());
} else {
throw new IllegalArgumentException("First arguments must be Record/ID/ID[]");
}
// 无权限字段的不检查
if (!EntityHelper.hasPrivilegesField(entity)) {
return;
}
if (caller == null) {
// 当前会话用户
caller = Application.currentCallerUser();
}
boolean isBulk = invocation.getMethod().getName().startsWith("bulkDelete");
boolean isBulk = invocation.getMethod().getName().startsWith("bulk");
if (isBulk) {
boolean deleteAllowed = Application.getSecurityManager().allowed(caller, entity.getEntityCode(), BizzPermission.DELETE);
if (!deleteAllowed) {
Object first = invocation.getArguments()[0];
if (!(first instanceof BulkContext)) {
throw new IllegalArgumentException("First argument must be BulkContext");
}
BulkContext context = (BulkContext) first;
ID caller = Application.currentCallerUser();
if (!Application.getSecurityManager().allowed(caller, context.getMainEntity().getEntityCode(), context.getAction())) {
throw new AccessDeniedException(
"User [ " + caller + " ] not allowed execute action [ " + BizzPermission.DELETE + " ]. Entity : " + entity.getName());
"User [ " + caller + " ] not allowed execute action [ " + context.getAction() + " ]. Entity : " + context.getMainEntity());
}
return;
}
final Permission permission = getPermissionByMethod(invocation.getMethod(), recordId == null);
Object idOrRecord = invocation.getArguments()[0];
ID recordId = null;
Entity entity = null;
if (idOrRecord instanceof Record) {
recordId = ((Record) idOrRecord).getPrimary();
entity = ((Record) idOrRecord).getEntity();
} else if (idOrRecord instanceof ID) {
recordId = (ID) idOrRecord;
entity = MetadataHelper.getEntity(recordId.getEntityCode());
} else {
throw new IllegalArgumentException("First argument must be Record/ID");
}
ID caller = Application.currentCallerUser();
Permission action = getPermissionByMethod(invocation.getMethod(), recordId == null);
boolean isAllowed = false;
if (permission == BizzPermission.CREATE) {
isAllowed = Application.getSecurityManager().allowed(caller, entity.getEntityCode(), permission);
if (action == BizzPermission.CREATE) {
isAllowed = Application.getSecurityManager().allowed(caller, entity.getEntityCode(), action);
} else {
if (recordId == null) {
throw new IllegalArgumentException("No primary in record!");
}
isAllowed = Application.getSecurityManager().allowed(caller, recordId, permission);
isAllowed = Application.getSecurityManager().allowed(caller, recordId, action);
}
if (!isAllowed) {
throw new AccessDeniedException(
"User [ " + caller + " ] not allowed execute action [ " + permission.getName() + " ]. "
+ (recordId == null ? "Entity : " + entity.getName() : "Record : " + recordId));
"User [ " + caller + " ] not allowed execute action [ " + action + " ]. " + (recordId == null ? "Entity : " + entity : "Record : " + recordId));
}
}
@ -145,9 +138,14 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
* @return
*/
protected boolean isNeedCheck(MethodInvocation invocation) {
// GeneralEntityService 或其子类
if (!GeneralEntityService.class.isAssignableFrom(invocation.getThis().getClass())) {
return false;
}
String act = invocation.getMethod().getName();
return act.startsWith("create") || act.startsWith("update")
|| act.startsWith("delete") || act.startsWith("assign") || act.startsWith("share")
|| act.startsWith("bulkDelete");
return act.startsWith("create") || act.startsWith("update") || act.startsWith("delete")
|| act.startsWith("assign") || act.startsWith("share")
|| act.startsWith("bulk");
}
}

View file

@ -18,8 +18,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.bizz.privileges;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -156,6 +159,41 @@ public class UserStore {
return USERs.values().toArray(new User[USERs.size()]);
}
/**
* @param deptId
* @return
* @throws NoMemberFoundException
*/
public Department getDepartment(ID deptId) throws NoMemberFoundException {
Department b = DEPTs.get(deptId);
if (b == null) {
throw new NoMemberFoundException("No Department found: " + deptId);
}
return b;
}
/**
* @return
*/
public Department[] getAllDepartments() {
return DEPTs.values().toArray(new Department[DEPTs.size()]);
}
/**
* 获取一级部门列表
*
* @return
*/
public Department[] getTopDepartments() {
List<Department> top = new ArrayList<>();
for (Department dept : DEPTs.values()) {
if (dept.getParent() == null) {
top.add(dept);
}
}
return top.toArray(new Department[top.size()]);
}
/**
* @param roleId
* @return
@ -169,19 +207,6 @@ public class UserStore {
return r;
}
/**
* @param deptId
* @return
* @throws NoMemberFoundException
*/
public Department getDept(ID deptId) throws NoMemberFoundException {
Department b = DEPTs.get(deptId);
if (b == null) {
throw new NoMemberFoundException("No Department found: " + deptId);
}
return b;
}
/**
* 刷新用户缓存
*
@ -230,7 +255,7 @@ public class UserStore {
ID newDeptId = (ID) u[6];
if (oldDept == null || !oldDept.getIdentity().equals(newDeptId)) {
getDept(newDeptId).addMember(newUser);
getDepartment(newDeptId).addMember(newUser);
} else {
oldDept.addMember(newUser);
}
@ -259,7 +284,6 @@ public class UserStore {
role.addPrivileges(priv);
}
}
store(role);
return;
}
@ -273,7 +297,7 @@ public class UserStore {
*
* @param deptId
*/
public void refreshDept(ID deptId) {
public void refreshDepartment(ID deptId) {
Department oldDept = DEPTs.get(deptId);
Object[] o = aPMFactory.createQuery(
@ -286,16 +310,16 @@ public class UserStore {
ID parent = (ID) o[2];
if (oldDept != null) {
if (oldDept.getParent() == null && parent != null) { // 新加入了部门
getDept(parent).addChild(newDept);
getDepartment(parent).addChild(newDept);
} else if (oldDept.getParent() != null && parent == null) { // 离开了部门
getDept(parent).removeMember(oldDept);
getDepartment(parent).removeMember(oldDept);
} else if (oldDept.getParent() != null && parent != null && !oldDept.getIdentity().equals(parent)) {
getDept(deptId).removeMember(oldDept);
getDept(parent).addChild(newDept);
getDepartment(deptId).removeMember(oldDept);
getDepartment(parent).addChild(newDept);
}
} else if (parent != null) {
getDept(parent).addChild(newDept);
getDepartment(parent).addChild(newDept);
}
}
@ -390,9 +414,9 @@ public class UserStore {
// 组织关系
for (Map.Entry<ID, Set<ID>> e : parentTemp.entrySet()) {
BusinessUnit parent = getDept(e.getKey());
BusinessUnit parent = getDepartment(e.getKey());
for (ID child : e.getValue()) {
parent.addChild(getDept(child));
parent.addChild(getDepartment(child));
}
}
@ -414,6 +438,12 @@ public class UserStore {
* @param role
*/
private void store(Role role) {
Role old = ROLEs.get(role.getIdentity());
if (old != null) {
for (Principal user : old.getMembers()) {
role.addMember(user);
}
}
ROLEs.put((ID) role.getIdentity(), role);
}
@ -421,6 +451,12 @@ public class UserStore {
* @param dept
*/
private void store(Department dept) {
Department old = DEPTs.get(dept.getIdentity());
if (old != null) {
for (Principal user : old.getMembers()) {
dept.addMember(user);
}
}
DEPTs.put((ID) dept.getIdentity(), dept);
}

View file

@ -57,6 +57,7 @@ public class EasyMeta implements BaseMeta {
BUILTIN_ENTITY.add("PickList");
BUILTIN_ENTITY.add("LayoutConfig");
BUILTIN_ENTITY.add("FilterConfig");
BUILTIN_ENTITY.add("ShareAccess");
BUILTIN_FIELD.add(EntityHelper.createdOn);
BUILTIN_FIELD.add(EntityHelper.createdBy);

View file

@ -18,7 +18,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.helper;
import com.alibaba.fastjson.JSON;
import java.util.Date;
import cn.devezhao.commons.CalendarUtils;
/**
* 前台提交的耗时操作
@ -28,47 +30,43 @@ import com.alibaba.fastjson.JSON;
*/
public abstract class BulkTask implements Runnable {
private int totalQuantity = -1;
private int completeQuantity = 0;
private int total = -1;
private int complete = 0;
private Date beginTime;
/**
* 任务执行相关数据
*/
protected JSON data;
/**
* @param data
*/
protected BulkTask(JSON data) {
this.data = data;
protected BulkTask() {
this.beginTime = CalendarUtils.now();
}
/**
* @param totalQuantity
*/
protected void setTotalQuantity(int totalQuantity) {
this.totalQuantity = totalQuantity;
protected void setTotal(int total) {
this.total = total;
}
/**
* @param completeQuantity
*/
protected void setCompleteQuantity(int completeQuantity) {
this.completeQuantity = completeQuantity;
protected void setComplete(int complete) {
this.complete = complete;
}
/**
* @return
*/
public int getTotalQuantity() {
return totalQuantity;
public int getTotal() {
return total;
}
/**
* @return
*/
public int getCompleteQuantity() {
return completeQuantity;
public int getComplete() {
return complete;
}
/**
@ -77,13 +75,13 @@ public abstract class BulkTask implements Runnable {
* @return
*/
public double getCompletePercent() {
if (totalQuantity == -1 || completeQuantity == 0) {
if (total == -1 || complete == 0) {
return 0;
}
if (totalQuantity == completeQuantity) {
if (total == complete) {
return 1;
}
return completeQuantity / totalQuantity;
return complete / total;
}
/**
@ -92,6 +90,15 @@ public abstract class BulkTask implements Runnable {
* @return
*/
public boolean isCompleted() {
return totalQuantity == -1 || totalQuantity == completeQuantity;
return total == -1 || total == complete;
}
/**
* 任务已耗时ms
*
* @return
*/
public long getElapsedTime() {
return CalendarUtils.now().getTime() - beginTime.getTime();
}
}

View file

@ -29,6 +29,7 @@ import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ThreadPool;
/**
* 大任务执行调度
*
* @author devezhao
* @since 09/29/2018

View file

@ -1,76 +0,0 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.server.helper;
import java.util.Date;
import com.alibaba.fastjson.JSON;
import cn.devezhao.commons.CalendarUtils;
/**
*
* @author devezhao
* @since 09/29/2018
*/
public abstract class TimeBulkTask extends BulkTask {
private Date beginTime;
private Date endTime;
/**
* @param data
*/
protected TimeBulkTask(JSON data) {
super(data);
this.beginTime = CalendarUtils.now();
}
/**
* @return
*/
public Date getBeginTime() {
return beginTime;
}
/**
* @return
*/
public Date getEndTime() {
return endTime;
}
/**
* @param endTime
*/
protected void setEndTime(Date endTime) {
this.endTime = endTime;
}
/**
* 任务耗时ms
*
* @return
*/
public long getElapsedTime() {
Date end = getEndTime();
end = end == null ? CalendarUtils.now() : end;
return end.getTime() - beginTime.getTime();
}
}

View file

@ -50,7 +50,7 @@ public abstract class CacheTemplate<V> {
}
protected Cache getCache() {
return cacheManager.getCache("default");
return cacheManager.getCache("rebuild");
}
/**

View file

@ -18,14 +18,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.helper.manager;
import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.JSONUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
/**
@ -58,7 +63,30 @@ public class NavManager extends LayoutManager {
public static JSONArray getNavForPortal(HttpServletRequest request) {
ID user = AppUtils.getRequestUser(request);
Object[] cfgs = getLayoutConfigRaw("N", TYPE_NAVI, user);
return cfgs == null ? JSON.parseArray("[]") : (JSONArray) cfgs[1];
if (cfgs == null) {
return JSON.parseArray("[]");
}
// 过滤
JSONArray navs = (JSONArray) cfgs[1];
for (Iterator<Object> iter = navs.iterator(); iter.hasNext(); ) {
JSONObject nav = (JSONObject) iter.next();
if ("ENTITY".equalsIgnoreCase(nav.getString("type"))) {
String entity = nav.getString("value");
if (!MetadataHelper.containsEntity(entity)) {
LOG.warn("Unknow entity in nav : " + entity);
iter.remove();
continue;
}
Entity entityMeta = MetadataHelper.getEntity(entity);
if (!Application.getSecurityManager().allowedR(user, entityMeta.getEntityCode())) {
iter.remove();
continue;
}
}
}
return navs;
}
/**

View file

@ -124,7 +124,7 @@ public class EntityHelper {
public static final String owningUser = "owningUser";
public static final String owningDept = "owningDept";
// 实体代码
// 系统实体
public static final int User = 1;
public static final int Department = 2;
@ -138,4 +138,6 @@ public class EntityHelper {
public static final int LayoutConfig = 13;
public static final int FilterConfig = 14;
public static final int ShareAccess = 20;
}

View file

@ -18,10 +18,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.metadata;
import java.util.ArrayList;
import java.util.List;
import com.rebuild.server.Application;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.metadata.MetadataException;
/**
@ -106,4 +110,26 @@ public class MetadataHelper {
Entity entity = getEntity(entityName);
return entity.getField(fieldName);
}
/**
* <tt>reference</tt> 中的哪些字段引用了 <tt>source</tt>
*
* @param source
* @param reference
* @return
*/
public static Field[] getReferenceToFields(Entity source, Entity reference) {
List<Field> fields = new ArrayList<>();
for (Field field : reference.getFields()) {
if (field.getType() != FieldType.REFERENCE) {
continue;
}
Entity ref = field.getReferenceEntities()[0];
if (ref.getEntityCode() == source.getEntityCode()) {
fields.add(field);
}
}
return fields.toArray(new Field[fields.size()]);
}
}

View file

@ -24,11 +24,13 @@ import java.util.List;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.util.Assert;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.entityhub.DisplayType;
import com.rebuild.server.entityhub.EasyMeta;
import com.rebuild.server.metadata.MetadataHelper;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
@ -44,6 +46,16 @@ public class AdvFilterParser {
private Entity rootEntity;
private JSONObject filterExp;
/**
* @param filterExp
*/
public AdvFilterParser(JSONObject filterExp) {
String entity = filterExp.getString("entity");
Assert.notNull(entity, "[entity] node can't be blank");
this.rootEntity = MetadataHelper.getEntity(entity);
this.filterExp = filterExp;
}
/**
* @param rootEntity
* @param filterExp

View file

@ -32,9 +32,9 @@ public abstract class BaseService {
final protected PersistManagerFactory aPMFactory;
protected BaseService(PersistManagerFactory factory) {
protected BaseService(PersistManagerFactory aPMFactory) {
super();
this.aPMFactory = factory;
this.aPMFactory = aPMFactory;
}
/**

View file

@ -0,0 +1,56 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.server.service.base;
import com.rebuild.server.Application;
import cn.devezhao.persist4j.engine.ID;
/**
*
* @author devezhao
* @since 09/29/2018
*/
public class BulkAssign extends BulkOperator {
public BulkAssign(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
public Object exec() {
ID[] records = getWillRecords();
int complated = 0;
int assigned = 0;
for (ID id : records) {
if (Application.getSecurityManager().allowedA(context.getOpUser(), id)) {
int a = ges.assign(id, context.getToUser(), context.getCascades());
assigned += (a > 0 ? 1 : 0);
} else {
LOG.warn("No have privileges to ASSIGN : " + context.getOpUser() + " > " + id);
}
complated++;
setComplete(complated);
}
return assigned;
}
}

View file

@ -0,0 +1,121 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.server.service.base;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.util.Assert;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.metadata.MetadataHelper;
import cn.devezhao.bizz.privileges.Permission;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
/**
* @author devezhao
* @since 10/17/2018
*/
public class BulkContext {
// 操作用户
private ID opUser;
// 执行动作
private Permission action;
// [目标用户]
private ID toUser;
// 待操作记录
private ID[] records;
// 待操作过滤条件通过条件确定记录
private JSONObject filterExp;
// 级联操作实体
private String cascades[];
private Entity mainEntity;
/**
* @param opUser
* @param action
* @param toUser
* @param cascades
* @param records
*/
public BulkContext(ID opUser, Permission action, ID toUser, String cascades[], ID[] records) {
Assert.isTrue(records.length <= 200, "最多允许操作200条记录");
this.opUser = opUser;
this.action = action;
this.toUser = toUser;
this.cascades = cascades;
this.records = records;
}
/**
* @param opUser
* @param action
* @param toUser
* @param cascades
* @param filterExp
*/
public BulkContext(ID opUser, Permission action, ID toUser, String cascades[], JSONObject filterExp) {
this.opUser = opUser;
this.action = action;
this.toUser = toUser;
this.cascades = cascades;
this.filterExp = filterExp;
}
public ID getOpUser() {
return opUser;
}
public Permission getAction() {
return action;
}
public ID getToUser() {
return toUser;
}
public String[] getCascades() {
return cascades == null ? ArrayUtils.EMPTY_STRING_ARRAY : cascades;
}
public ID[] getRecords() {
return records;
}
public JSONObject getFilterExp() {
return filterExp;
}
public Entity getMainEntity() {
if (mainEntity != null) {
return mainEntity;
}
if (records != null) {
mainEntity = MetadataHelper.getEntity(records[0].getEntityCode());
} else {
mainEntity = MetadataHelper.getEntity(filterExp.getString("entity"));
}
return mainEntity;
}
}

View file

@ -0,0 +1,57 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.server.service.base;
import com.rebuild.server.Application;
import cn.devezhao.persist4j.engine.ID;
/**
* 删除
*
* @author devezhao
* @since 10/16/2018
*/
public class BulkDelete extends BulkOperator {
public BulkDelete(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
public Object exec() {
ID[] records = getWillRecords();
int complated = 0;
int deleted = 0;
for (ID id : records) {
if (Application.getSecurityManager().allowedD(context.getOpUser(), id)) {
int a = ges.delete(id);
deleted += (a > 0 ? 1 : 0);
} else {
LOG.warn("No have privileges to DELETE : " + context.getOpUser() + " > " + id);
}
complated++;
setComplete(complated);
}
return deleted;
}
}

View file

@ -0,0 +1,98 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.server.service.base;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.helper.BulkTask;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.query.AdvFilterParser;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
/**
* 批量操作
*
* @author devezhao
* @since 10/16/2018
*/
public abstract class BulkOperator extends BulkTask {
protected static final Log LOG = LogFactory.getLog(BulkOperator.class);
final protected BulkContext context;
final protected GeneralEntityService ges;
private ID[] records;
/**
* @param context
* @param ges
*/
protected BulkOperator(BulkContext context, GeneralEntityService ges) {
super();
this.context = context;
this.ges = ges;
}
/**
* 获取待操作记录
*
* @return
*/
protected ID[] getWillRecords() {
if (this.records != null) {
return this.records;
}
if (context.getRecords() != null) {
this.records = context.getRecords();
setTotal(this.records.length);
return this.records;
}
JSONObject filterExp = context.getFilterExp();
AdvFilterParser filterParser = new AdvFilterParser(filterExp);
String sqlWhere = filterParser.toSqlWhere();
Entity entity = MetadataHelper.getEntity(filterExp.getString("entity"));
String sql = "select %s from %s where (1=1) and " + sqlWhere;
sql = String.format(sql, entity.getPrimaryField().getName(), entity.getName());
// TODO 解析过滤并查询结果
throw new UnsupportedOperationException();
}
@Override
public void run() {
this.exec();
}
/**
* 实际执行
*
* @return
*/
abstract public Object exec();
}

View file

@ -18,21 +18,39 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.service.base;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.helper.TimeBulkTask;
import com.rebuild.server.Application;
import cn.devezhao.persist4j.engine.ID;
/**
*
* @author devezhao
* @since 09/29/2018
*/
public class BatchModifiedTask extends TimeBulkTask {
public class BulkShare extends BulkOperator {
protected BatchModifiedTask(JSON data) {
super(data);
public BulkShare(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
public void run() {
public Object exec() {
ID[] records = getWillRecords();
int complated = 0;
int shared = 0;
for (ID id : records) {
if (Application.getSecurityManager().allowedS(context.getOpUser(), id)) {
int a = ges.share(id, context.getToUser(), context.getCascades());
shared += (a > 0 ? 1 : 0);
} else {
LOG.warn("No have privileges to SHARE : " + context.getOpUser() + " > " + id);
}
complated++;
setComplete(complated);
}
return shared;
}
}

View file

@ -18,18 +18,35 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.service.base;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.rebuild.server.Application;
import com.rebuild.server.bizz.privileges.PrivilegesGuardInterceptor;
import com.rebuild.server.bizz.privileges.User;
import com.rebuild.server.helper.BulkTaskExecutor;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.BaseService;
import cn.devezhao.bizz.privileges.Permission;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
/**
* 业务实体用会带有系统设置规则的执行
* 业务实体用
* - 会带有系统设置规则的执行详见 {@link PrivilegesGuardInterceptor}
* - 会开启一个事务详见 <tt>application-ctx.xml</tt> 配置
*
* @author zhaofang123@gmail.com
* @since 11/06/2017
@ -51,48 +68,193 @@ public class GeneralEntityService extends BaseService {
return 0;
}
/**
* 获取级联操作记录
*
* @param main 主记录
* @param cascades 级联实体
* @param action 动作
* @return
*/
public Map<String, Set<ID>> getCascadeRecords(ID main, String[] cascades, Permission action) {
if (cascades == null || cascades.length == 0) {
return Collections.emptyMap();
}
Map<String, Set<ID>> cascadeRecordsMap = new HashMap<>();
Entity mainEntity = MetadataHelper.getEntity(main.getEntityCode());
for (String ref : cascades) {
Entity refEntity = MetadataHelper.getEntity(ref);
String sql = "select %s from %s where ( ";
sql = String.format(sql, refEntity.getPrimaryField().getName(), refEntity.getName());
Field[] refToFields = MetadataHelper.getReferenceToFields(mainEntity, refEntity);
for (Field refToField : refToFields) {
sql += refToField.getName() + " = '" + main + "' or ";
}
sql = sql.substring(0, sql.length() - 4); // remove last ' or '
sql += " )";
// TODO 此处查询权限不对应该查询能分派的记录而非可读的记录
Object[][] array = Application.createQuery(sql).array();
Set<ID> records = new HashSet<>();
for (Object[] o : array) {
records.add((ID) o[0]);
}
cascadeRecordsMap.put(ref, records);
}
return cascadeRecordsMap;
}
@Override
public int delete(ID record) {
return super.delete(record);
return delete(record, null);
}
/**
* @param records
* @return 实际删除数量请注意请求删除数量和实际删除数量可能不一致因为可能没有删除权限
* 删除带级联删除选项
*
* @see #delete(ID)
* @param record
* @param cascades 级联删除的实体
* @return
*/
public int bulkDelete(ID records[]) {
ID user = Application.currentCallerUser();
int deleted = 0;
for (ID id : records) {
if (Application.getSecurityManager().allowed(user, id, BizzPermission.DELETE)) {
int affected = this.delete(id);
deleted += (affected > 0 ? 1 : 0);
} else {
LOG.warn("No have privileges to delete : " + user + " > " + id);
public int delete(ID record, String[] cascades) {
super.delete(record);
int affected = 1;
Map<String, Set<ID>> cass = getCascadeRecords(record, cascades, BizzPermission.DELETE);
for (Map.Entry<String, Set<ID>> e : cass.entrySet()) {
if (LOG.isDebugEnabled()) {
LOG.debug("级联删除 - " + e.getKey() + "/" + e.getValue().size());
}
for (ID id : e.getValue()) {
affected += delete(id, null);
}
}
return deleted;
return affected;
}
/**
* 分派
*
* @param record
* @param to
* @param cascades 级联分派的实体
* @return
*/
public int assign(ID record, ID to, String[] cascades) {
ID currentOwn = Application.getRecordOwningCache().getOwningUser(record);
if (currentOwn.equals(to)) {
LOG.warn("记录所属人未变化,忽略本次操作 : " + record);
return 1;
}
User toUser = Application.getUserStore().getUser(to);
Record assign = EntityHelper.forUpdate(record, (ID) toUser.getIdentity());
assign.setID(EntityHelper.owningUser, (ID) toUser.getIdentity());
assign.setID(EntityHelper.owningDept, (ID) toUser.getOwningDept().getIdentity());
super.update(assign);
int affected = 1;
Map<String, Set<ID>> cass = getCascadeRecords(record, cascades, BizzPermission.ASSIGN);
for (Map.Entry<String, Set<ID>> e : cass.entrySet()) {
if (LOG.isDebugEnabled()) {
LOG.debug("级联分派 - " + e.getKey() + "/" + e.getValue().size());
}
for (ID id : e.getValue()) {
affected += assign(id, to, null);
}
}
return affected;
}
/**
* 共享
*
* @param recordId
* @param record
* @param to
* @param cascades 级联共享的实体
* @return
*/
public int share(ID recordId) {
return 0;
public int share(ID record, ID to, String[] cascades) {
Object[] exists = aPMFactory.createQuery(
"select accessId,rights from ShareAccess where recordId = ? and shareTo = ?")
.setParameter(1, record.toLiteral())
.setParameter(2, to.toLiteral())
.unique();
if (exists != null) {
LOG.warn("记录已分享过,忽略本次操作 : " + record);
return 1;
}
ID currentUser = Application.currentCallerUser();
Record share = EntityHelper.forNew(EntityHelper.ShareAccess, currentUser);
share.setInt("entity", record.getEntityCode());
share.setString("recordId", record.toLiteral());
share.setString("shareTo", to.toLiteral());
share.setInt("rights", 999);
super.create(share);
int affected = 1;
Map<String, Set<ID>> cass = getCascadeRecords(record, cascades, BizzPermission.SHARE);
for (Map.Entry<String, Set<ID>> e : cass.entrySet()) {
if (LOG.isDebugEnabled()) {
LOG.debug("级联共享 - " + e.getKey() + "/" + e.getValue().size());
}
for (ID id : e.getValue()) {
affected += share(id, to, null);
}
}
return affected;
}
/**
* 分配
* 批量操作
*
* @param recordId
* @param context
* @return
*/
public int assgin(ID recordId) {
return 0;
public int bulk(BulkContext context) {
BulkOperator operator = buildBulkOperator(context);
Object affected = operator.exec();
return (Integer) affected;
}
/**
* 批量操作-异步
*
* @param context
* @return
*
* @see BulkTaskExecutor
*/
public String bulkAsync(BulkContext context) {
BulkOperator operator = buildBulkOperator(context);
String taskid = BulkTaskExecutor.submit(operator);
return taskid;
}
/**
* @param context
* @return
*/
private BulkOperator buildBulkOperator(BulkContext context) {
if (context.getAction() == BizzPermission.DELETE) {
return new BulkDelete(context, this);
} else if (context.getAction() == BizzPermission.ASSIGN) {
return new BulkAssign(context, this);
} else if (context.getAction() == BizzPermission.SHARE) {
return new BulkShare(context, this);
}
throw new UnsupportedOperationException("Unsupported bulk action : " + context.getAction());
}
}

View file

@ -16,23 +16,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.service.base;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.helper.TimeBulkTask;
package com.rebuild.server.service.notification;
/**
*
* @author devezhao
* @since 09/29/2018
* @since 10/17/2018
*/
public class BatchShareTask extends TimeBulkTask {
public class Message {
protected BatchShareTask(JSON data) {
super(data);
}
@Override
public void run() {
}
}

View file

@ -16,23 +16,24 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.service.base;
package com.rebuild.server.service.notification;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.helper.TimeBulkTask;
import com.rebuild.server.service.BaseService;
import cn.devezhao.persist4j.PersistManagerFactory;
/**
* 通知/消息 服务
*
* @author devezhao
* @since 09/29/2018
* @since 10/17/2018
*/
public class BatchAssignTask extends TimeBulkTask {
public class NotificationService extends BaseService {
protected BatchAssignTask(JSON data) {
super(data);
protected NotificationService(PersistManagerFactory aPMFactory) {
super(aPMFactory);
}
@Override
public void run() {
public void send(Message message) {
}
}

View file

@ -73,7 +73,7 @@ public class UserControll extends BaseControll {
JSONArray firsts = new JSONArray();
for (Object dept[] : topDepts) {
Department first = Application.getUserStore().getDept((ID) dept[0]);
Department first = Application.getUserStore().getDepartment((ID) dept[0]);
firsts.add(recursiveDeptTree(first));
}
writeSuccess(response, firsts);

View file

@ -21,8 +21,10 @@ package com.rebuild.web.base;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -75,4 +77,25 @@ public class MetadataGet extends BaseControll {
}
writeSuccess(response, list);
}
@RequestMapping("references")
public void ref(HttpServletRequest request, HttpServletResponse response) throws IOException {
String entity = getParameterNotNull(request, "entity");
Entity entityMeta = MetadataHelper.getEntity(entity);
Set<Entity> references = new HashSet<>();
Field[] rtFields = entityMeta.getReferenceToFields();
for (Field field : rtFields) {
Entity own = field.getOwnEntity();
references.add(own);
}
List<String[]> list = new ArrayList<>();
for (Entity e : references) {
EasyMeta easy = new EasyMeta(e);
list.add(new String[] { easy.getName(), easy.getLabel() });
}
writeSuccess(response, list);
}
}

View file

@ -0,0 +1,91 @@
/*
rebuild - Building your system freely.
Copyright (C) 2018 devezhao <zhaofang123@gmail.com>
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/>.
*/
package com.rebuild.web.base;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.rebuild.server.Application;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.web.BaseControll;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
/**
* 公用搜索
*
* @author zhaofang123@gmail.com
* @since 08/24/2018
*/
@Controller
public class SimpleSearch extends BaseControll {
@RequestMapping("/commons/search")
public void search(HttpServletRequest request, HttpServletResponse response) throws IOException {
String entity = getParameterNotNull(request, "entity");
String qfields = getParameterNotNull(request, "qfields");
String q = getParameter(request, "q");
if (StringUtils.isBlank(q)) {
writeSuccess(response, ArrayUtils.EMPTY_STRING_ARRAY);
return;
}
q = StringEscapeUtils.escapeSql(q);
Entity entityMeta = MetadataHelper.getEntity(entity);
Field idFiled = entityMeta.getPrimaryField();
Field nameField = entityMeta.getNameField();
String sql = "select %s,%s from %s";
sql = String.format(sql, idFiled.getName(), nameField.getName(), entityMeta.getName());
List<String> or = new ArrayList<>();
for (String qField : qfields.split(",")) {
if (!entityMeta.containsField(qField)) {
LOG.warn("No field for search : " + qField);
} else {
or.add(qField + " like '%" + q + "%'");
}
}
sql += " where ( " + StringUtils.join(or, " or ") + " ) order by modifiedOn desc";
Object[][] array = Application.createQuery(sql).setLimit(10).array();
List<Object> result = new ArrayList<>();
for (Object[] o : array) {
Map<String, Object> map = new HashMap<>();
map.put("id", o[0].toString());
map.put("text", o[1]);
result.add(map);
}
writeSuccess(response, result);
}
}

View file

@ -28,6 +28,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@ -37,10 +38,13 @@ import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.base.BulkContext;
import com.rebuild.server.service.base.GeneralEntityService;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseControll;
import com.rebuild.web.InvalidRequestException;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
@ -64,10 +68,11 @@ public class GeneralRecordControll extends BaseControll {
JSON formJson = ServletUtils.getRequestJson(request);
Record record = EntityHelper.parse((JSONObject) formJson, user);
if (record.getPrimary() == null
&& !Application.getSecurityManager().allowedC(user, record.getEntity().getEntityCode())) {
writeFailure(response, "没有新建权限");
return;
if (record.getPrimary() == null) {
if (!Application.getSecurityManager().allowedC(user, record.getEntity().getEntityCode())) {
writeFailure(response, "没有新建权限");
return;
}
} else if (!Application.getSecurityManager().allowedU(user, record.getPrimary())) {
writeFailure(response, "没有编辑权限");
return;
@ -82,48 +87,99 @@ public class GeneralRecordControll extends BaseControll {
@RequestMapping("record-delete")
public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
String ids = getParameterNotNull(request, "id");
// TODO 级联删除
Set<ID> idList = new HashSet<>();
int deleteEntityCode = 0;
for (String id : ids.split(",")) {
ID id0 = ID.valueOf(id);
if (deleteEntityCode == 0) {
deleteEntityCode = id0.getEntityCode();
}
if (deleteEntityCode != id0.getEntityCode()) {
writeFailure(response, "只能批量删除同一实体的记录");
return;
}
idList.add(ID.valueOf(id));
}
if (idList.isEmpty()) {
final ID user = getRequestUser(request);
final ID[] ids = parseIdList(request);
if (ids.length == 0) {
writeFailure(response, "没有要删除的记录");
return;
}
ID firstId = idList.iterator().next();
if (!Application.getSecurityManager().allowedD(user, firstId)) {
Entity entity = MetadataHelper.getEntity(ids[0].getEntityCode());
if (!Application.getSecurityManager().allowedD(user, entity.getEntityCode())) {
writeFailure(response, "没有删除权限");
return;
}
GeneralEntityService ges = Application.getGeneralEntityService(deleteEntityCode);
int deleted = 0;
if (idList.size() == 1) {
deleted = ges.delete(firstId);
String[] cascades = parseCascades(request);
GeneralEntityService ges = Application.getGeneralEntityService(entity.getEntityCode());
int affected = 0;
if (ids.length == 1) {
affected = ges.delete(ids[0], cascades);
} else {
deleted = ges.bulkDelete(idList.toArray(new ID[idList.size()]));
BulkContext context = new BulkContext(user, BizzPermission.DELETE, null, cascades, ids);
affected = ges.bulk(context);
}
JSON ret = JSONUtils.toJSONObject(
new String[] { "deleted", "request_delete" },
new Object[] { deleted, idList.size() });
new String[] { "deleted", "requests" },
new Object[] { affected, ids.length });
writeSuccess(response, ret);
}
@RequestMapping("record-assign")
public void assign(HttpServletRequest request, HttpServletResponse response) throws IOException {
final ID user = getRequestUser(request);
final ID[] ids = parseIdList(request);
if (ids.length == 0) {
writeFailure(response, "没有要分派的记录");
return;
}
Entity entity = MetadataHelper.getEntity(ids[0].getEntityCode());
if (!Application.getSecurityManager().allowedA(user, entity.getEntityCode())) {
writeFailure(response, "没有分派权限");
return;
}
ID assignTo = getIdParameterNotNull(request, "to");
String[] cascades = parseCascades(request);
GeneralEntityService ges = Application.getGeneralEntityService(entity.getEntityCode());
int affected = 0;
if (ids.length == 1) {
affected = ges.assign(ids[0], assignTo, cascades);
} else {
BulkContext context = new BulkContext(user, BizzPermission.ASSIGN, assignTo, cascades, ids);
affected = ges.bulk(context);
}
JSON ret = JSONUtils.toJSONObject(
new String[] { "shared", "requests" },
new Object[] { affected, ids.length });
writeSuccess(response, ret);
}
@RequestMapping("record-share")
public void share(HttpServletRequest request, HttpServletResponse response) throws IOException {
final ID user = getRequestUser(request);
final ID[] ids = parseIdList(request);
if (ids.length == 0) {
writeFailure(response, "没有要共享的记录");
return;
}
Entity entity = MetadataHelper.getEntity(ids[0].getEntityCode());
if (!Application.getSecurityManager().allowedA(user, entity.getEntityCode())) {
writeFailure(response, "没有共享权限");
return;
}
ID shareTo = getIdParameterNotNull(request, "to");
String[] cascades = parseCascades(request);
GeneralEntityService ges = Application.getGeneralEntityService(entity.getEntityCode());
int affected = 0;
if (ids.length == 1) {
affected = ges.share(ids[0], shareTo, cascades);
} else {
BulkContext context = new BulkContext(user, BizzPermission.SHARE, shareTo, cascades, ids);
affected = ges.bulk(context);
}
JSON ret = JSONUtils.toJSONObject(
new String[] { "shared", "requests" },
new Object[] { affected, ids.length });
writeSuccess(response, ret);
}
@ -164,4 +220,45 @@ public class GeneralRecordControll extends BaseControll {
map.put(entity.getPrimaryField().getName(), id);
writeSuccess(response, map);
}
/**
* 操作ID列表
*
* @param request
* @return
*/
private ID[] parseIdList(HttpServletRequest request) {
String ids = getParameterNotNull(request, "id");
Set<ID> idList = new HashSet<>();
int sameEntityCode = 0;
for (String id : ids.split(",")) {
ID id0 = ID.valueOf(id);
if (sameEntityCode == 0) {
sameEntityCode = id0.getEntityCode();
}
if (sameEntityCode != id0.getEntityCode()) {
throw new InvalidRequestException("只能批量删除同一实体的记录");
}
idList.add(ID.valueOf(id));
}
return idList.toArray(new ID[idList.size()]);
}
/**
* 级联操作实体
*
* @param request
* @return
*/
private String[] parseCascades(HttpServletRequest request) {
String cascades = getParameter(request, "cascades");
if (StringUtils.isBlank(cascades)) {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
String[] cass = cascades.split(",");
// TODO 验证实体???
return cass;
}
}

View file

@ -48,11 +48,10 @@ import cn.devezhao.persist4j.dialect.FieldType;
* @since 08/24/2018
*/
@Controller
@RequestMapping("/app/commons/")
public class ReferenceSearch extends BaseControll {
@RequestMapping("search")
public void search(HttpServletRequest request, HttpServletResponse response) throws IOException {
@RequestMapping("/app/entity/ref-search")
public void searchRefs(HttpServletRequest request, HttpServletResponse response) throws IOException {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
String q = getParameter(request, "q");
@ -61,11 +60,12 @@ public class ReferenceSearch extends BaseControll {
writeSuccess(response, ArrayUtils.EMPTY_STRING_ARRAY);
return;
}
q = StringEscapeUtils.escapeSql(q);
Entity root = MetadataHelper.getEntity(entity);
Field referenceField = root.getField(field);
Entity referenceEntity = referenceField.getReferenceEntities()[0];
Field nameField = referenceEntity.getNameField();
Entity entityMeta = MetadataHelper.getEntity(entity);
Field refField = entityMeta.getField(field);
Entity refEntity = refField.getReferenceEntities()[0];
Field nameField = refEntity.getNameField();
if (nameField == null) {
writeSuccess(response, ArrayUtils.EMPTY_STRING_ARRAY);
return;
@ -76,20 +76,18 @@ public class ReferenceSearch extends BaseControll {
nameField2str = "&" + nameField2str;
}
String searchSql = "select %s,%s from %s where %s ";
searchSql = String.format(searchSql,
referenceEntity.getPrimaryField().getName(), nameField2str, referenceEntity.getName(), nameField2str);
searchSql += "like '%" + StringEscapeUtils.escapeSql(q) + "%'";
String sql = "select %s,%s from %s where %s ";
sql = String.format(sql, refEntity.getPrimaryField().getName(), nameField2str, refEntity.getName(), nameField2str);
sql += "like '%" + q + "%' order by modifiedOn desc";
Object[][] array = Application.createQuery(searchSql).setLimit(10).array();
List<Object> list = new ArrayList<>();
Object[][] array = Application.createQuery(sql).setLimit(10).array();
List<Object> result = new ArrayList<>();
for (Object[] o : array) {
Map<String, Object> map = new HashMap<>();
map.put("id", o[0].toString());
map.put("text", o[1]);
list.add(map);
result.add(map);
}
writeSuccess(response, list);
writeSuccess(response, result);
}
}

View file

@ -95,7 +95,8 @@
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="assign*" propagation="REQUIRED" />
<tx:method name="share*" propagation="REQUIRED" />
<tx:method name="bulk*" propagation="REQUIRED" />
<tx:method name="bulk*" propagation="REQUIRED" />
<tx:method name="tx*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
@ -106,16 +107,20 @@
<aop:advisor pointcut-ref="servicesPoint" advice-ref="txAdvice" />
<aop:advisor pointcut-ref="servicesPoint" advice-ref="privilegesGuard" />
</aop:config>
<bean class="com.rebuild.server.entityhub.PickListService">
<constructor-arg index="0" ref="PersistManagerFactory" />
</bean>
<bean class="com.rebuild.server.service.CommonService">
<constructor-arg index="0" ref="PersistManagerFactory" />
</bean>
<bean name="GeneralEntityService" class="com.rebuild.server.service.base.GeneralEntityService">
<bean class="com.rebuild.server.service.notification.NotificationService">
<constructor-arg index="0" ref="PersistManagerFactory" />
</bean>
<bean class="com.rebuild.server.entityhub.PickListService">
<bean name="GeneralEntityService" class="com.rebuild.server.service.base.GeneralEntityService">
<constructor-arg index="0" ref="PersistManagerFactory" />
</bean>

View file

@ -1,5 +1,5 @@
# $Id: default-bizz-cfg.properties 7 2013-12-24 15:22:33Z zhaoff@qidapp.com $
field.own-user=ownUser
field.own-biz-unit=ownDept
field.own-user=owningUser
field.own-biz-unit=owningDept
value.root-user=001-0000000000000001
value.root-role=003-0000000000000001

View file

@ -1,14 +1,29 @@
<ehcache>
<diskStore path="java.io.tmpdir" />
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<defaultCache
<diskStore path="java.io.tmpdir" />
<defaultCache
maxElementsInMemory="1000000"
eternal="false"
timeToIdleSeconds="604800"
eternal="false"
timeToIdleSeconds="604800"
timeToLiveSeconds="2592000"
overflowToDisk="true"
overflowToDisk="true"
maxElementsOnDisk="100000000"
diskPersistent="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" />
<cache name="rebuild"
maxElementsInMemory="1000000"
eternal="false"
timeToIdleSeconds="604800"
timeToLiveSeconds="2592000"
overflowToDisk="true"
maxElementsOnDisk="100000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" />

View file

@ -40,7 +40,7 @@
<entity name="RolePrivileges" type-code="004" parent="false">
<field name="privilegesId" type="primary" />
<field name="roleId" type="reference" ref-entity="Role" />
<field name="roleId" type="reference" ref-entity="Role" cascade="delete" nullable="false" />
<field name="entity" type="string" max-length="100" nullable="false" default-value="N"/>
<field name="zeroKey" type="string" description="其他权限KEY"/>
<field name="definition" type="string" max-length="100" description="权限定义"/>
@ -115,4 +115,13 @@
<field name="applyTo" type="string" default-value="SELF" description="共享给哪些人,可选值: ALL/SELF/$MemberID(U/D/R)" />
</entity>
<entity name="ShareAccess" type-code="020" description="记录共享">
<field name="accessId" type="primary" />
<field name="entity" type="int" nullable="false" description="哪个实体"/>
<field name="recordId" type="string" max-length="20" nullable="false" updatable="false" description="记录ID" />
<field name="shareTo" type="string" max-length="20" nullable="false" updatable="false" description="共享给谁 (User/Dept/Role)" />
<field name="rights" type="int" nullable="false" updatable="false" description="共享权限(C=1,R=2,U=4,D=8)" />
<index field-list="entity,recordId,shareTo"/>
</entity>
</metadata-config>

View file

@ -35,11 +35,11 @@ final String showName = UserHelper.getShowName(currentUser);
<ul class="nav navbar-nav float-right rb-icons-nav">
<% if (currentUser.isAdmin()) { %>
<li class="nav-item dropdown">
<a class="nav-link" href="${baseUrl}/admin/systems" title="系统置"><span class="icon zmdi zmdi-settings"></span></a>
<a class="nav-link" href="${baseUrl}/admin/systems" title="系统置"><span class="icon zmdi zmdi-settings"></span></a>
</li>
<%} %>
<li class="nav-item dropdown">
<a class="nav-link" href="${baseUrl}/app/messages" title="消息中心"><span class="icon zmdi zmdi-notifications"></span></a>
<a class="nav-link" href="${baseUrl}/app/notifications" title="通知"><span class="icon zmdi zmdi-notifications"></span></a>
</li>
</ul>
</div>

View file

@ -101,8 +101,8 @@
<table class="table table-priv">
<thead>
<tr>
<th width="25%">权限项</th>
<th class="text-center"><span data-action="Z">允许</span></th>
<th width="25%">权限项</th>
<th class="text-center"><a data-action="Z">允许</a></th>
<th>前置条件</th>
<th></th>
<th></th>
@ -203,6 +203,10 @@ $(document).ready(function(){
$('#priv-zero tbody .priv').click(function(){
clickPriv($(this), 'Z')
})
$('#priv-zero thead th>a').click(function(){
let privAll = $('#priv-zero tbody .priv[data-action="Z"]')
clickPriv(privAll, 'Z')
})
$('#priv-zero tbody .name>a').click(function(){
let el = $(this).parent().next().find('i.priv')
clickPriv(el, 'Z')
@ -211,8 +215,8 @@ $(document).ready(function(){
})
const clickPriv = function(elements, action) {
if (action == 'C' || action == 'Z') {
elements.toggleClass('R0')
elements.toggleClass('R4')
if (elements.first().hasClass('R0')) elements.removeClass('R0').addClass('R4')
else elements.removeClass('R4').addClass('R0')
} else {
let clz = 'R0'
if (elements.hasClass('R0')) clz = 'R1'

View file

@ -3,7 +3,7 @@
<html>
<head>
<%@ include file="/_include/Head.jsp"%>
<title>验证管理员</title>
<title>验证管理员身份</title>
<style type="text/css">
</style>
</head>

View file

@ -3736,8 +3736,9 @@ input[type=submit].btn-block {
.dropdown-item.disabled,
.dropdown-item:disabled {
color: #878787;
background-color: transparent
color: #878787 !important;
background-color: transparent !important;
cursor: default;
}
.dropdown-menu.show {

View file

@ -0,0 +1,72 @@
var opType = opType || ['assign', '分派']
$(document).ready(function(){
let selected = parent.RbListPage._RbList.getSelectedRows()
const ids = selected.map((item) => { return item[0] })
const entity = $urlp('entity')
$('#records').text('选中的记录 (' + ids.length + '条)')
$('#toUser').select2({
language: 'zh-CN',
placeholder: '选择用户',
minimumInputLength: 1,
ajax: {
url: rb.baseUrl + '/commons/search',
delay: 300,
data: function(params) {
let query = {
entity: 'User',
qfields: 'loginName,fullName,email',
q: params.term,
}
return query
},
processResults: function(data){
let rs = data.data.map((item) => { return item })
return { results: rs }
}
}
})
$('.J_click-cass a').click(function(){
$('.J_click-cass').remove();
$('.J_cass').removeClass('hide');
parent.RbListPage._ModalShare.resize()
$.get(rb.baseUrl + '/commons/metadata/references?entity=' + entity, function(res){
$(res.data).each(function(){
$('<option value="' + this[0] + '">' + this[1] + '</option>').appendTo('#cascades')
})
$('#cascades').select2({
language: 'zh-CN',
placeholder: '选择关联实体 (可选)',
})
})
});
$('.J_submit').click(function() {
let to = $('#toUser').val()
if (!to || to.length < 1){ rb.notice('请选择' + opType[1] + '给谁'); return }
let cascades = $('#cascades').val()
let url = rb.baseUrl + '/app/entity/record-' + opType[0] + '?id=' + ids.join(',') + '&cascades=' + cascades.join(',') + '&to=' + to.join(',')
$.post(url, function(res) {
if (res.error_code == 0){
rb.notice('已成功' + opType[1] + ' ' + (res.data.assigned || res.data.shared) + ' 条记录', 'success')
if (parent.RbListPage) {
if (parent.RbListPage._RbList) parent.RbListPage._RbList.reload()
$('.J_cancel').trigger('click')
}
} else {
rb.notice(res.error_msg || (opType[1] + '失败,请稍后重试'), 'danger')
}
})
})
$('.J_cancel').click(function(){
if (parent.RbListPage) {
if (parent.RbListPage._ModalAssign) parent.RbListPage._ModalAssign.hide()
if (parent.RbListPage._ModalShare) parent.RbListPage._ModalShare.hide()
}
})
});

View file

@ -169,8 +169,8 @@ class RbForm extends React.Component {
}
//
static postAfter(data) {
if (window.rbList) window.rbList.reload()
else if (parent.rbList) parent.rbList.reload()
if (window.RbListPage) window.RbListPage._RbList.reload()
else if (parent.RbListPage) parent.RbListPage._RbList.reload()
if (window.rbFromView) location.reload()
}
}
@ -596,7 +596,7 @@ class RbFormReference extends RbFormElement {
allowClear: true,
minimumInputLength: 1,
ajax: {
url: rb.baseUrl + '/app/commons/search',
url: rb.baseUrl + '/app/entity/ref-search',
delay: 300,
data: function(params) {
let query = {

View file

@ -355,11 +355,13 @@ const RbListPage = {
})
$(this.refs['rbalter']).find('.btn').button('loading')
let that = this
let thatModal = this
$.post(rb.baseUrl + '/app/entity/record-delete?id=' + ids.join(','), function(res){
if (res.error_code == 0){
rbList.reload()
that.hide()
that._RbList.reload()
thatModal.hide()
if (res.data.deleted == res.data.requests) rb.notice('删除成功', 'success')
else rb.notice('已成功删除 ' + res.data.deleted + ' 条记录', 'success')
} else {
rb.notice(res.error_msg || '删除失败,请稍后重试', 'danger')
}
@ -384,12 +386,14 @@ const RbListPage = {
})
$('.J_assign').click(function(){
if (that._RbList.getSelectedRows().length < 1) { rb.notice('请选择要分派的记录'); return}
if (that._ModalAssign) that._ModalAssign.show()
else that._ModalAssign = rb.modal(`${rb.baseUrl}/page/general-entity/assign?entity=entity${entity[1]}`, '分记录')
else that._ModalAssign = rb.modal(`${rb.baseUrl}/page/general-entity/assign?entity=${entity[1]}`, '分记录')
})
$('.J_share').click(function(){
if (that._RbList.getSelectedRows().length < 1) { rb.notice('请选择要共享的记录'); return}
if (that._ModalShare) that._ModalShare.show()
else that._ModalShare = rb.modal(`${rb.baseUrl}/page/general-entity/share?entity=entity${entity[1]}`, '共享记录')
else that._ModalShare = rb.modal(`${rb.baseUrl}/page/general-entity/share?entity=${entity[1]}`, '共享记录')
})
$('.J_columns').click(function(){

View file

@ -1,12 +1,48 @@
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<%@ include file="/_include/Head.jsp"%>
<title>分派</title>
</head>
<body>
<body class="dialog">
<div class="main-content">
<form>
<div class="form-group row">
<label class="col-sm-3 col-form-label text-sm-right">分派哪些记录</label>
<div class="col-sm-7 col-lg-4">
<div class="form-control-plaintext" id="records">选中的记录</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label text-sm-right">分派给谁</label>
<div class="col-sm-7 col-lg-4">
<select class="form-control form-control-sm" id="toUser" multiple="multiple">
</select>
</div>
</div>
<div class="form-group row J_click-cass">
<div class="col-sm-7 offset-sm-3">
<a href="javascript:;">同时分派关联记录</a>
</div>
</div>
<div class="form-group row J_cass hide">
<label class="col-sm-3 col-form-label text-sm-right">同时分派关联记录</label>
<div class="col-sm-7 col-lg-4">
<select class="form-control form-control-sm" id="cascades" multiple="multiple">
</select>
</div>
</div>
<div class="form-group row footer">
<div class="col-sm-7 offset-sm-3">
<button class="btn btn-primary J_submit" type="button" data-loading-text="请稍后">确定</button>
<a class="btn btn-link J_cancel">取消</a>
</div>
</div>
</form>
</div>
<%@ include file="/_include/Foot.jsp"%>
<script src="${baseUrl}/assets/js/assign-share.js"></script>
</body>
</html>
</html>

View file

@ -43,10 +43,12 @@
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item J_share"><i class="icon zmdi zmdi-slideshare"></i> 共享</a>
<a class="dropdown-item J_assign"><i class="icon zmdi zmdi-mail-reply-all"></i> 分配</a>
<a class="dropdown-item J_assign"><i class="icon zmdi zmdi-mail-reply-all"></i> 分派</a>
<!--
<div class="dropdown-divider"></div>
<a class="dropdown-item J_exports"><i class="icon zmdi zmdi-download"></i> 导出</a>
<a class="dropdown-item J_imports"><i class="icon zmdi zmdi-upload"></i> 导入</a>
-->
<div class="dropdown-divider"></div>
<a class="dropdown-item J_columns"><i class="icon zmdi zmdi-sort-amount-asc"></i> 列显示</a>
</div>

View file

@ -1,12 +1,51 @@
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<%@ include file="/_include/Head.jsp"%>
<title>共享</title>
</head>
<body>
<body class="dialog">
<div class="main-content">
<form>
<div class="form-group row">
<label class="col-sm-3 col-form-label text-sm-right">共享哪些记录</label>
<div class="col-sm-7 col-lg-4">
<div class="form-control-plaintext" id="records">选中的记录</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label text-sm-right">共享给谁</label>
<div class="col-sm-7 col-lg-4">
<select class="form-control form-control-sm" id="toUser" multiple="multiple">
</select>
</div>
</div>
<div class="form-group row J_click-cass">
<div class="col-sm-7 offset-sm-3">
<a href="javascript:;">同时共享关联记录</a>
</div>
</div>
<div class="form-group row J_cass hide">
<label class="col-sm-3 col-form-label text-sm-right">同时共享关联记录</label>
<div class="col-sm-7 col-lg-4">
<select class="form-control form-control-sm" id="cascades" multiple="multiple">
</select>
</div>
</div>
<div class="form-group row footer">
<div class="col-sm-7 offset-sm-3">
<button class="btn btn-primary J_submit" type="button" data-loading-text="请稍后">确定</button>
<a class="btn btn-link J_cancel">取消</a>
</div>
</div>
</form>
</div>
<%@ include file="/_include/Foot.jsp"%>
<script type="text/javascript">
opType = ['share', '共享']
</script>
<script src="${baseUrl}/assets/js/assign-share.js"></script>
</body>
</html>
</html>

View file

@ -55,10 +55,7 @@ $(document).ready(function() {
$('.J_login-btn').click(function() {
let user = $val('#user'), passwd = $val('#passwd');
if (!user || !passwd){
rb.notice('请输入用户名和密码')
return;
}
if (!user || !passwd) return;
let btn = $(this).button('loading');
$.post(rb.baseUrl + '/user/user-login?user=' + $encode(user) + '&passwd=' + $encode(passwd), function(res) {

View file

@ -50,8 +50,9 @@ public class SchemaGen {
// gen(EntityHelper.MetaField);
// gen(EntityHelper.PickList);
// gen(EntityHelper.RolePrivileges);
gen(EntityHelper.LayoutConfig);
gen(EntityHelper.FilterConfig);
// gen(EntityHelper.LayoutConfig);
// gen(EntityHelper.FilterConfig);
gen(EntityHelper.ShareAccess);
System.exit(0);
}