All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.gdface.facelog.DaoManagement Maven / Gradle / Ivy

There is a newer version: 5.3.0
Show newest version
package net.gdface.facelog;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.Managers.*;
import static net.gdface.facelog.FeatureConfig.*;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.tuple.Pair;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import gu.sql2java.BaseBean;
import gu.sql2java.ColumnGetter;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableManager;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;
import net.gdface.facelog.db.Constant;
import net.gdface.facelog.db.DeviceBean;
import net.gdface.facelog.db.DeviceGroupBean;
import net.gdface.facelog.db.ErrorLogBean;
import net.gdface.facelog.db.FaceBean;
import net.gdface.facelog.db.FeatureBean;
import net.gdface.facelog.db.IDeviceGroupManager;
import net.gdface.facelog.db.IPermitManager;
import net.gdface.facelog.db.IPersonGroupManager;
import net.gdface.facelog.db.ImageBean;
import net.gdface.facelog.db.LogBean;
import net.gdface.facelog.db.PermitBean;
import net.gdface.facelog.db.PersonBean;
import net.gdface.facelog.db.PersonGroupBean;
import net.gdface.facelog.db.StoreBean;
import net.gdface.image.LazyImage;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.utils.Assert;
import net.gdface.utils.BinaryUtils;
import net.gdface.utils.Judge;
import net.gdface.utils.MiscellaneousUtils;

/**
 * 数据库操作扩展
 * @author guyadong
 *
 */
public class DaoManagement extends BaseDao implements ServiceConstant,Constant{
	private final CryptographGenerator cg;
	public DaoManagement(CryptographGenerator cg) {
		this.cg = checkNotNull(cg,"cg is null");
	}
	public DaoManagement() {
		this(CryptographBySalt.INSTANCE);
	}
	public CryptographGenerator getCryptographGenerator() {
		return cg;
	}

	/** 检查姓名是否有效,不允许使用保留字{@code root} ,无效抛出{@link IllegalArgumentException} 异常 */
	protected static void checkPersonName(PersonBean personBean){
		checkArgument(null == personBean || !ROOT_NAME.equals(personBean.getName()),
				"INVALID person name:%s, reserved word",ROOT_NAME);
	}
	/** 
	 * 增加人员姓名检查,参见 {@link #checkPersonName(PersonBean)}
* 增加 password密文更新 * @throws IllegalStateException {@code password}不是有效的MD5字符串 */ @Override protected PersonBean daoSavePerson(PersonBean personBean) throws RuntimeDaoException { checkPersonName(personBean); if(personBean.checkPasswordModified()){ String password = personBean.getPassword(); if(null != password){ checkState(BinaryUtils.validMd5(password),"password field must be MD5 string(32 char,lower case)"); // 重新生成password加盐密文 personBean.setPassword(cg.cryptograph(password, true)); } } return super.daoSavePerson(personBean); } protected static StoreBean makeStoreBean(ByteBuffer imageBytes,String md5,String encodeing){ if(Judge.isEmpty(imageBytes)){ return null; } if(null == md5){ md5 = BinaryUtils.getMD5String(imageBytes); } StoreBean storeBean = new StoreBean(); storeBean.setData(imageBytes); storeBean.setMd5(md5); if(!Strings.isNullOrEmpty(encodeing)){ storeBean.setEncoding(encodeing); } return storeBean; } /////////////////////PERMIT//////////////////// /** * 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行, * 本方法会对{@code deviceGroupId}的子结点向下递归: * {@code personGroupId } 及其子结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true, * 输入参数为{@code null}或找不到指定的记录则返回false * @param deviceGroupId * @param personGroupId * @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null} */ private PermitBean daoGetPermitOnPersonGroup(Integer deviceGroupId, final Integer personGroupId) throws RuntimeDaoException { DeviceGroupBean deviceGroup; if(null == deviceGroupId || null == personGroupId || null == (deviceGroup = daoGetDeviceGroup(deviceGroupId))){ return null; } for(DeviceGroupBean group : daoChildListByParentForDeviceGroup(deviceGroup)){ PermitBean permit = daoGetPermit(group.getId(), personGroupId); if(permit != null){ return permit; } } return null; } /** * 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行, * 本方法会对{@code personGroupId}的父结点向上回溯: * {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true, * 输入参数为{@code null}或找不到指定的记录则返回false * @param deviceGroupId * @param personGroupId * @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null} */ protected PermitBean daoGetGroupPermitOnDeviceGroup(final Integer deviceGroupId,Integer personGroupId){ PersonGroupBean personGroup; if(null == deviceGroupId || null == personGroupId || null == (personGroup = daoGetPersonGroup(personGroupId))){ return null; } List personGroupList = daoListOfParentForPersonGroup(personGroup); // first is self Collections.reverse(personGroupList); for(PersonGroupBean group : personGroupList){ PermitBean permit = daoGetPermitOnPersonGroup(deviceGroupId,group.getId()); if(permit != null){ return permit; } } return null; } /** * 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceId}设备上是否允许通行, * 本方法会对{@code personGroupId}的父结点向上回溯: * {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true, * 输入参数为{@code null}或找不到指定的记录则返回false * @param deviceId * @param personGroupId * @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null} * @see #daoGetGroupPermitOnDeviceGroup(Integer, Integer) */ protected PermitBean daoGetGroupPermit(Integer deviceId,Integer personGroupId){ DeviceBean device; if(null == deviceId || null ==(device = daoGetDevice(deviceId))){ return null; } return daoGetGroupPermitOnDeviceGroup(device.getGroupId(),personGroupId); } protected PermitBean daoGetPersonPermit(Integer deviceId,Integer personId){ PersonBean person; if( null == personId || null == (person = daoGetPerson(personId))){ return null; } return daoGetGroupPermit(deviceId,person.getGroupId()); } /** * 判断指定用户是否可以在指定设备上通行 * @param personId 用户ID * @param deviceId 设备ID * @return 允许通行返回{@code true},否则返回{@code false} */ protected boolean daoIsPermit(int personId,int deviceId){ Set features = daoGetPersonsPermittedOnDevice(deviceId, false, null, null); return Collections2.transform(features, daoCastPersonToPk).contains(personId); } protected List daoGetGroupPermit(final Integer deviceId,List personGroupIdList){ if(null == deviceId || null == personGroupIdList){ return Collections.emptyList(); } return Lists.newArrayList(Lists.transform(personGroupIdList, new Function(){ @Override public PermitBean apply(Integer input) { return daoGetGroupPermit(deviceId,input); }})); } protected List daoGetPermit(final Integer deviceId,List personIdList){ if(null == deviceId || null == personIdList){ return Collections.emptyList(); } return Lists.newArrayList(Lists.transform(personIdList, new Function(){ @Override public PermitBean apply(Integer input) { return daoGetPersonPermit(deviceId,input); }})); } /** * 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id * @param deviceGroupId 为{@code null}返回空表 * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制 * @return 人员组id列表 */ protected List daoGetPersonGroupsPermittedBy(Integer deviceGroupId,boolean ignoreSchedule){ if(deviceGroupId == null){ return Collections.emptyList(); } Set permittedGroups = Sets.newHashSet(); DateTimeJsonFilter shedule = new DateTimeJsonFilter(); final Date date = new Date(); for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){ if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){ permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId())); } } return Lists.newArrayList(Iterables.transform(permittedGroups, daoCastPersonGroupToPk)); } /** * 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id, * 忽略时间过滤器(fl_permit.schedule字段)的限制 * @param deviceGroupId 为{@code null}返回空表 * @return 人员组id列表 */ protected List daoGetPersonGroupsPermittedBy(Integer deviceGroupId){ if(deviceGroupId == null){ return Collections.emptyList(); } List permits = daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId); return Lists.transform(permits, daoCastPermitToPersonGroupId); } /** * 从permit表返回允许{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})对象的id * @param personGroupId 为{@code null}返回空表 * @return 设备组id列表 */ protected List daoGetDeviceGroupsPermittedBy(Integer personGroupId){ if(personGroupId == null){ return Collections.emptyList(); } List permits = daoGetPermitBeansByPersonGroupIdOnPersonGroup(personGroupId); return Lists.transform(permits, daoCastPermitToDeviceGroupId); } /** * 从permit表返回允许在{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})的id
* 不排序,不包含重复id * @param personGroupId 为{@code null}返回空表 * @return 设备组id列表 */ protected List daoGetDeviceGroupsPermit(Integer personGroupId){ if(personGroupId == null){ return Collections.emptyList(); } PermitBean template = PermitBean.builder().personGroupId(personGroupId).build(); List permits = daoLoadPermitUsingTemplate(template, 1, -1); HashSet groups = Sets.newHashSet(); for (PermitBean bean : permits) { groups.addAll(Lists.transform(daoListOfParentForDeviceGroup(bean.getDeviceGroupId()),daoCastDeviceGroupToPk)); } return Lists.newArrayList(groups); } /** * 从permit表删除指定{@code personGroupId}指定人员组的在所有设备上的通行权限 * @param personGroupId 为{@code null}返回0 * @return 删除的记录条数 */ protected int daoDeletePersonGroupPermit(Integer personGroupId) { if(personGroupId == null){ return 0; } PermitBean template = PermitBean.builder().personGroupId(personGroupId).build(); return instanceOf(IPermitManager.class).deleteUsingTemplate(template); } /** * 从permit表删除指定{@code deviceGroupId}指定设备组上的人员通行权限 * @param deviceGroupId 为{@code null}返回0 * @return 删除的记录条数 */ protected int daoDeleteGroupPermitOnDeviceGroup(Integer deviceGroupId) { if(deviceGroupId == null){ return 0; } PermitBean template = PermitBean.builder().deviceGroupId(deviceGroupId).build(); return instanceOf(IPermitManager.class).deleteUsingTemplate(template); } //////////////////////////////////////////// /** * 创建{@link ImageBean}对象,填充图像基本信息,同时创建对应的{@link StoreBean}对象 * @param imageBytes * @param md5 * @return 返回 {@link ImageBean}和{@link StoreBean}对象 * @throws NotImageException * @throws UnsupportedFormatException */ protected static Pair makeImageBean(ByteBuffer imageBytes,String md5) throws NotImageException, UnsupportedFormatException{ if(Judge.isEmpty(imageBytes)){ return null; } LazyImage image = LazyImage.create(imageBytes); if(null == md5){ md5 = BinaryUtils.getMD5String(imageBytes); } ImageBean imageBean = new ImageBean(); imageBean.setMd5(md5); imageBean.setWidth(image.getWidth()); imageBean.setHeight(image.getHeight()); imageBean.setFormat(image.getSuffix()); StoreBean storeBean = makeStoreBean(imageBytes, md5, null); return Pair.of(imageBean, storeBean); } protected ImageBean daoAddImage(ByteBuffer imageBytes,DeviceBean refFlDevicebyDeviceId , Collection impFlFacebyImgMd5 , Collection impFlPersonbyImageMd5) throws DuplicateRecordException{ if(Judge.isEmpty(imageBytes)){ return null; } String md5 = BinaryUtils.getMD5String(imageBytes); daoCheckDuplicateImage(md5); Pair pair; try { pair = makeImageBean(imageBytes,md5); } catch (Exception e) { throw new RuntimeException(e); } daoAddStore(pair.getRight()); if(null != impFlFacebyImgMd5){ pair.getLeft().setFaceNum(impFlFacebyImgMd5.size()); } return daoAddImage(pair.getLeft(), refFlDevicebyDeviceId, impFlFacebyImgMd5,null, impFlPersonbyImageMd5); } /** * (递归)删除imageMd5指定图像及其缩略图 * @param imageMd5 * @return 返回删除的记录条数(1),如果记录不存在返回0 */ @Override protected int daoDeleteImage(String imageMd5){ if(Strings.isNullOrEmpty(imageMd5)){ return 0; } daoDeleteStore(imageMd5); ImageBean imageBean = daoGetImage(imageMd5); if(null == imageBean){return 0;} String thumbMd5 = imageBean.getThumbMd5(); if( !Strings.isNullOrEmpty(thumbMd5)&& !imageBean.getMd5().equals(thumbMd5)){ daoDeleteImage(thumbMd5); } return super.daoDeleteImage(imageMd5); } /** * 根据配置定义的SDK算法参数,从特征数据中剥离出MD5计算范围的特征数据 * @param featureVersion * @param feature * @return MD5计算范围的特征数据 */ private static byte[] stripFeature(String featureVersion,ByteBuffer feature){ int md5Off = FEATURE_CONFIG.md5OffsetOf(featureVersion); int md5Len = FEATURE_CONFIG.md5LengthOf(featureVersion); byte[] featureBytes = BinaryUtils.getBytesInBuffer(feature); if(md5Len >= 0 && md5Len > 0 && (md5Off + md5Len) != featureBytes.length){ // 检查参数有效性 checkArgument((md5Off + md5Len) < featureBytes.length, "INVALID md5 range definition off:%s,len:%s for %s(feture size %s)", md5Off, md5Len,featureVersion,featureBytes.length); return Arrays.copyOfRange(featureBytes, md5Off, md5Off + md5Len); } return featureBytes; } protected FeatureBean daoMakeFeature(ByteBuffer feature, String featureVersion){ Assert.notEmpty(feature, "feature"); // featureVersion不可为空 checkArgument(!Strings.isNullOrEmpty(featureVersion),"featureVersion is null or empty"); // featureVersion内容只允许字母,数字,-,.,_符号 checkArgument(featureVersion.matches(SDK_VERSION_REGEX), "invalid sdk version format"); byte[] stripedFeature = stripFeature(featureVersion,feature); String md5 = BinaryUtils.getMD5String(stripedFeature); return FeatureBean.builder() .md5(md5) .feature(feature) .version(featureVersion) .build(); } protected byte[] daoGetFeatureBytes(String md5, boolean truncation) throws IOException{ FeatureBean featureBean = daoGetFeature(md5); if(featureBean == null){ return null; } if(!truncation){ return BinaryUtils.getBytes(featureBean.getFeature()); } return stripFeature(featureBean.getVersion(),featureBean.getFeature()); } protected List daoGetFeatureBytes(List md5List, boolean truncation) throws IOException{ List features = Lists.newLinkedList(); if(md5List != null){ for(String md5:md5List){ byte[] feature = daoGetFeatureBytes(md5,truncation); features.add(feature == null ? new byte[0] : feature) ; } } return features; } /** * 返回 persionId 关联的指定SDK的人脸特征记录 * @param personId 人员id(fl_person.id) * @param featureVersion 算法(SDK)版本号 * @return 返回 fl_feature.md5 列表 */ protected List daoGetFeaturesByPersonIdAndSdkVersion(int personId,String featureVersion) { FeatureBean tmpl = FeatureBean.builder().personId(personId).version(featureVersion).build(); return daoLoadFeatureUsingTemplate(tmpl, 1, -1); } /** * (递归)返回在指定设备上允许通行的所有人员记录
* @param output 输出的用户对象列表,过滤所有有效期失效的用户 * @param deviceId 设备ID * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制 * @param excludePersonIds 要排除的人员记录id,可为{@code null} * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录 */ private void getPersonsPermittedOnDeviceGroup(Setoutput, int deviceGroupId, boolean ignoreSchedule, List excludePersonIds, final Long timestamp) { Set permittedGroups = Sets.newHashSet(); DateTimeJsonFilter shedule = new DateTimeJsonFilter(); final Date date = new Date(); for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){ if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){ permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId())); } } Set persons = Sets.newHashSet(); for(PersonGroupBean group : permittedGroups){ persons.addAll(daoGetPersonsOfGroup(group.getId())); } final Set excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludePersonIds, Collections.emptySet())); output.addAll( Sets.filter(persons, new Predicate() { @Override public boolean apply(PersonBean input) { Date expiryDate = input.getExpiryDate(); return ! excludeIds.contains(input.getId()) /** 过滤所有有效期失效的用户 */ && (timestamp == null || input.getUpdateTime().getTime() > timestamp) && (null== expiryDate ? true : expiryDate.after(date)); } })); for(DeviceGroupBean group:daoChildListByParentForDeviceGroup(deviceGroupId)){ if(group.getId() != deviceGroupId){ // exclude self getPersonsPermittedOnDeviceGroup(output,group.getId(),ignoreSchedule,excludePersonIds,timestamp); } } } /** * 返回在指定设备上允许通行的所有人员记录
* @param deviceId 设备ID * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制 * @param excludePersonIds 要排除的人员记录id,可为{@code null} * @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录 * @return 返回的用户对象列表中,过滤所有有效期失效的用户
*/ protected Set daoGetPersonsPermittedOnDevice(int deviceId, boolean ignoreSchedule, List excludePersonIds, final Long timestamp) { Set output = new HashSet<>(); DeviceBean deviceBean = daoGetDeviceChecked(deviceId); getPersonsPermittedOnDeviceGroup(output,deviceBean.getGroupId(),ignoreSchedule,excludePersonIds,timestamp); return output; } /** * 返回在指定设备上允许通行的所有特征记录 * @param deviceId 设备ID * @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制 * @param sdkVersion 特征版本号 * @param excludeFeatureIds 要排除的特征记录id(MD5),可为{@code null} * @param timestamp 不为{@code null}时返回大于指定时间戳的所有fl_feature记录 * @return 特征记录集合 */ protected Set daoGetFeaturesPermittedOnDevice(int deviceId,boolean ignoreSchedule, String sdkVersion,final Collection excludeFeatureIds, final Long timestamp) { checkArgument(!Strings.isNullOrEmpty(sdkVersion),"sdkVersion is null or empty"); Set features = Sets.newHashSet(); for(PersonBean person:daoGetPersonsPermittedOnDevice(deviceId, ignoreSchedule, null, null)){ features.addAll(daoGetFeaturesByPersonIdAndSdkVersion(person.getId(),sdkVersion)); } return Sets.filter(features,new Predicate() { Set excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludeFeatureIds, Collections.emptySet())); @Override public boolean apply(FeatureBean input) { return ! excludeIds.contains(input.getMd5()) && (timestamp == null || input.getUpdateTime().getTime() > timestamp); } }); } /** * 添加人脸特征数据到数据库
* 如果用户的特征记录数量超过限制,且没有开启自动更新机制则抛出异常, * 如果已有特征数量超过限制,且开启了自动特征更新机制则删除最老的记录,确保记录总数不超过限制 * @param feature 人脸特征数据 * @param featureVersion SDK版本号 * @param refPersonByPersonId 所属的人员记录 * @param impFaceByFeatureMd5 关联的人脸信息记录 * @return 特征记录对象 * @throws DuplicateRecordException */ protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion, PersonBean refPersonByPersonId, Collection impFaceByFeatureMd5) throws DuplicateRecordException{ FEATURE_CONFIG.checkSdkVersion(featureVersion); boolean removeOld = false; List features = null; if(null != refPersonByPersonId && refPersonByPersonId.getId() != null){ Integer personId = refPersonByPersonId.getId(); features = daoGetFeaturesByPersonIdAndSdkVersion(personId,featureVersion); int count = features.size(); int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion); // 如果用户的特征记录数量超过限制,且没有开启自动更新机制则抛出异常 checkState(count < limitCount || FEATURE_CONFIG.featureAutoUpdateEnabled(), "person(id=%s)'s %s feature count exceed max limit(%s)",personId,featureVersion,limitCount); // 如果已有特征数量超过限制,且开启了自动特征更新机制则删除最老的记录 if(count >= limitCount && FEATURE_CONFIG.featureAutoUpdateEnabled()){ removeOld = true; } } FeatureBean newFeature = daoAddFeature(daoMakeFeature(feature, featureVersion), refPersonByPersonId, impFaceByFeatureMd5, null); // 放在成功添加记录之后再执行删除,以防止因为添加特征抛出异常而导致发送错误的通知消息 if(removeOld){ int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion); // 以update_time字段排序,删除最旧的记录 Collections.sort(features, RowMetaData.getMetaData(FeatureBean.class).comparatorOf(FL_FEATURE_ID_UPDATE_TIME,/** 降序 */true)); for(int i = limitCount-1;i < features.size();++i){ daoDeleteFeature(features.get(i).getMd5(), true); logger.info("AUTOUPDATE:remove feature [{}] and associated image",features.get(i).getMd5()); } } return newFeature; } protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion,PersonBean personBean,Map faceInfo, DeviceBean deviceBean) throws DuplicateRecordException{ if(null != faceInfo){ for(Entry entry:faceInfo.entrySet()){ ByteBuffer imageBytes = entry.getKey(); FaceBean faceBean = entry.getValue(); daoAddImage(imageBytes, deviceBean, Arrays.asList(faceBean), null); } } return daoAddFeature(feature, featureVersion, personBean, null == faceInfo?null:faceInfo.values()); } /** * 删除指定的特征 * @param featureMd5 * @param deleteImage 为{@code true}则删除关联的 image记录(如果该图像还关联其他特征则不删除) * @return 返回删除的特征记录关联的图像(image)记录的MD5 */ protected List daoDeleteFeature(final String featureMd5,boolean deleteImage){ List imageKeys = daoGetImageKeysImportedByFeatureMd5(featureMd5); if(deleteImage){ for(Iterator itor = imageKeys.iterator();itor.hasNext();){ String md5 = itor.next(); // 如果该图像还关联其他特征则不删除 if(!Iterables.tryFind(daoGetFaceBeansByImageMd5OnImage(md5), new Predicate(){ @Override public boolean apply(FaceBean input) { return !featureMd5.equals(input.getFeatureMd5()); }}).isPresent()){ daoDeleteImage(md5); itor.remove(); } } }else{ daoDeleteFaceBeansByFeatureMd5OnFeature(featureMd5); } daoDeleteFeature(featureMd5); return imageKeys; } protected List daoDeleteFeatureChecked(final String featureMd5,boolean deleteImage){ if(!Strings.isNullOrEmpty(featureMd5)){ checkArgument(daoExistsFeature(featureMd5),"INVALID feature id %s",featureMd5); return daoDeleteFeature(featureMd5,true); } return Collections.emptyList(); } protected int daoDeleteAllFeaturesByPersonId(Integer personId,boolean deleteImage){ int count = 0; for(FeatureBean featureBean: daoGetFeatureBeansByPersonIdOnPerson(personId)){ daoDeleteFeature(featureBean.getMd5(),deleteImage); ++count; } return count; } protected Integer daoGetDeviceIdOfFeature(String featureMd5){ for(String imageMd5: daoGetImageKeysImportedByFeatureMd5(featureMd5)){ ImageBean imageBean = daoGetImage(imageMd5); if(null !=imageBean){ return imageBean.getDeviceId(); } } return null; } protected PersonBean daoSetRefPersonOfFeature(String featureMd5,Integer personId){ PersonBean personBean = daoGetPerson(personId); FeatureBean featureBean = daoGetFeature(featureMd5); return (null == personBean || null == featureBean) ? null : daoSetReferencedByPersonIdOnFeature(featureBean, personBean); } protected List daoGetImageKeysImportedByFeatureMd5(String featureMd5){ return Lists.transform(daoGetFaceBeansByFeatureMd5OnFeature(featureMd5), this.daoCastFaceToImageMd5); } protected PersonBean daoReplaceFeature(Integer personId,String featureMd5,boolean deleteImage){ daoDeleteAllFeaturesByPersonId(personId, deleteImage); return daoSetRefPersonOfFeature(featureMd5,personId); } protected int daoSavePerson(Map persons) throws DuplicateRecordException { if(null == persons ){ return 0; } int count = 0; PersonBean personBean ; for(Entry entry:persons.entrySet()){ personBean = daoSavePerson(entry.getValue(),daoAddImage(entry.getKey(),null,null,null),null); if(null != personBean){++count;} } return count; } /** * 保存人员记录
* 如果提供了新的身份照片({@code idPhotoBean}不为{@code null}),则删除旧照片用新照片代替 * @param personBean * @param idPhotoBean 新的身份照片 * @param featureBean * @return personBean */ protected PersonBean daoSavePerson(PersonBean personBean, ImageBean idPhotoBean, Collection featureBean) { // delete old photo if exists if(idPhotoBean != null){ daoDeleteImage(daoGetReferencedByImageMd5OnPerson(personBean)); } return daoSavePerson(personBean, idPhotoBean, null, null, featureBean, null); } protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, FeatureBean featureBean, DeviceBean deviceBean) throws DuplicateRecordException { ImageBean imageBean = daoAddImage(idPhoto, deviceBean, null, null); return daoSavePerson(bean, imageBean, featureBean == null ? null : Arrays.asList(featureBean)); } protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, ByteBuffer feature, String featureVersion, Map faceInfo, DeviceBean deviceBean) throws DuplicateRecordException { return daoSavePerson(bean, idPhoto, daoAddFeature(feature, featureVersion, bean, faceInfo, deviceBean), deviceBean); } /** * * @param personBean 人员信息对象 * @param idPhoto 标准照图像 * @param feature 人脸特征数据 * @param featureVersion 特征(SDk)版本号 * @param featureImage 提取特征源图像,为null 时,默认使用idPhoto * @param faceBean 人脸位置对象,为null 时,不保存人脸数据,忽略featureImage * @param deviceBean featureImage来源设备对象 * @return personBean * @throws DuplicateRecordException */ protected PersonBean daoSavePerson(PersonBean personBean, ByteBuffer idPhoto, ByteBuffer feature, String featureVersion, ByteBuffer featureImage, FaceBean faceBean, DeviceBean deviceBean) throws DuplicateRecordException { List faceList = faceBean == null ? null : Arrays.asList(faceBean); if (Judge.isEmpty(featureImage)){ featureImage = idPhoto; } if(!Judge.isEmpty(idPhoto)){ /** 保存标准照 */ ImageBean imageBean = daoAddImage(idPhoto,deviceBean,featureImage == idPhoto ? faceList : null,null); personBean.setImageMd5(imageBean.getMd5()); } FeatureBean featureBean = null; if (null != faceBean) { if (!Judge.isEmpty(featureImage) && featureImage != idPhoto) { /** 保存特征的同时保存featureImage */ featureBean = daoAddFeature(feature, featureVersion, personBean, ImmutableMap.of(featureImage, faceBean), deviceBean); }else{ featureBean = daoAddFeature(feature, featureVersion, personBean, faceList); } } return daoSavePerson(personBean, null, featureBean, deviceBean); } /** * 删除personId指定的人员(person)记录及关联的所有记录 * @param personId * @return 返回删除的记录数量 */ @Override protected int daoDeletePerson(Integer personId) { PersonBean personBean = daoGetPerson(personId); if(null == personBean){ return 0; } // 删除标准照 daoDeleteImage(personBean.getImageMd5()); daoDeleteAllFeaturesByPersonId(personId,true); return super.daoDeletePerson(personId); } protected int daoDeletePersonByPapersNum(String papersNum) { PersonBean personBean = daoGetPersonByIndexPapersNum(papersNum); return null == personBean ? 0 : daoDeletePerson(personBean.getId()); } protected int daoDeletePersonByPapersNum(Collection collection) { int count =0; if(null != collection){ for(String papersNum:collection){ count += daoDeletePersonByPapersNum(papersNum); } } return count; } protected boolean daoIsDisable(int personId){ PersonBean personBean = daoGetPerson(personId); if(null == personBean){ return true; } Date expiryDate = personBean.getExpiryDate(); return null== expiryDate?false:expiryDate.before(new Date()); } protected void daoSetPersonExpiryDate(PersonBean personBean,Date expiryDate){ if(null != personBean){ personBean.setExpiryDate(expiryDate); daoSavePerson(personBean); } } protected void daoSetPersonExpiryDate(Collection personIdList,Date expiryDate){ if(null != personIdList){ for(Integer personId : personIdList){ daoSetPersonExpiryDate(daoGetPerson(personId),expiryDate); } } } protected List daoLoadUpdatedPersons(Date timestamp) { LinkedHashSet updatedPersons = Sets.newLinkedHashSet(daoLoadPersonIdByUpdateTime(timestamp)); List idList = Lists.transform( daoLoadFeatureByUpdateTime(timestamp), daoCastFeatureToPersonId); // 两个collection 合并去除重复 @SuppressWarnings("unused") boolean b = Iterators.addAll(updatedPersons, Iterators.filter(idList.iterator(), Predicates.notNull())); return Lists.newArrayList(updatedPersons); } /** * 创建管理边界
* 设置fl_person_group.root_group和fl_device_group.root_group字段互相指向, * 以事务操作方式更新数据库 * @param personGroupId 人员组id * @param deviceGroupId 设备组id * @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录 */ protected void daoBindBorder(final Integer personGroupId,final Integer deviceGroupId) throws ObjectRetrievalException{ final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId); final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId); { // 解除与人员组已有的其他设备组绑定 Integer rootId = personGroup.getRootGroup(); if(rootId != null && rootId != deviceGroupId){ DeviceGroupBean root = daoGetDeviceGroup(rootId); root.setRootGroup(null); daoSaveDeviceGroup(root); } } { // 解除与设备组已有的其他人员组绑定 Integer rootId = deviceGroup.getRootGroup(); if(rootId != null && rootId != deviceGroupId){ PersonGroupBean root = daoGetPersonGroup(rootId); root.setRootGroup(null); daoSavePersonGroup(root); } } personGroup.setRootGroup(deviceGroup.getId()); deviceGroup.setRootGroup(personGroup.getId()); daoSavePersonGroup(personGroup); daoSaveDeviceGroup(deviceGroup); } /** * 删除管理边界
* 删除fl_person_group.root_group和fl_device_group.root_group字段的互相指向,设置为{@code null}, * 以事务操作方式更新数据库
* 如果personGroupId和deviceGroupId不存在绑定关系则跳过 * @param personGroupId 人员组id * @param deviceGroupId 设备组id * @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录 */ protected void daoUnbindBorder(Integer personGroupId,Integer deviceGroupId) throws ObjectRetrievalException{ final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId); final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId); if(deviceGroup.getId().equals(personGroup.getRootGroup()) || personGroup.getId().equals(deviceGroup.getRootGroup())){ personGroup.setRootGroup(null); deviceGroup.setRootGroup(null); daoRunAsTransaction(new Runnable() { @Override public void run() { daoSavePersonGroup(personGroup); daoSaveDeviceGroup(deviceGroup); } }); } } /** * 返回personId所属的管理边界人员组id
* 在personId所属组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组,返回此记录组id * @param personId * @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null} * @throws ObjectRetrievalException 没有找到personId指定的记录 */ protected Integer daoRootGroupOfPerson(Integer personId) throws ObjectRetrievalException{ Integer groupId = daoGetPersonChecked(personId).getGroupId(); return daoRootGroupOfPersonGroup(groupId); } /** * 返回groupId指定人员组的管理边界人员组id
* 在groupId指定人员组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组,返回此记录组id * @param groupId 人员组id * @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null} */ protected Integer daoRootGroupOfPersonGroup(Integer groupId) { if(groupId == null){ return null; } // 循环引用检查 instanceOf(IPersonGroupManager.class).checkCycleOfParent(groupId); List parents = daoListOfParentForPersonGroup(groupId); Optional top = Iterables.tryFind(parents, new Predicate() { @Override public boolean apply(PersonGroupBean input) { return input.getRootGroup()!=null; } }); return top.isPresent() ? top.get().getId() : null; } /** * 返回deviceId所属的管理边界设备组id
* 在deviceId所属组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组,返回此记录id * @param deviceId * @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null} * @throws ObjectRetrievalException 没有找到deviceId指定的记录 */ protected Integer daoRootGroupOfDevice(Integer deviceId) throws ObjectRetrievalException{ Integer groupId = daoGetDeviceChecked(deviceId).getGroupId(); return daoRootGroupOfDeviceGroup(groupId); } /** * 返回groupId指定设备组的管理边界设备组id
* 在groupId指定设备组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组,返回此记录id * @param groupId 设备组id * @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null} */ protected Integer daoRootGroupOfDeviceGroup(Integer groupId){ if(groupId == null){ return null; } // 循环引用检查 instanceOf(IDeviceGroupManager.class).checkCycleOfParent(groupId); List parents = daoListOfParentForDeviceGroup(groupId); Optional top = Iterables.tryFind(parents, new Predicate() { @Override public boolean apply(DeviceGroupBean input) { return input.getRootGroup()!=null; } }); return top.isPresent() ? top.get().getId() : null; } private LogBean checkLogBean(LogBean logBean){ if(logBean.getPersonId()==null){ String featureId = checkNotNull(Strings.emptyToNull(logBean.getVerifyFeature()), "NOT FOUND valid person id caused by fl_log.verify_feature is null"); FeatureBean featureBean = checkNotNull(daoGetFeature(featureId), "NOT FOUND valid person id caused by invalid feature id %s",featureId); logBean.setPersonId(checkNotNull(featureBean.getPersonId(), "NOT FOUND valid person id caused by fl_feature.person_id is null")); } /*else { // 检查verifyFeature记录所属的person_id是否与log的person_id相等 String featureId = logBean.getVerifyFeature(); if(!Strings.isNullOrEmpty(featureId)){ FeatureBean featureBean = checkNotNull(dm.daoGetFeature(featureId), "INVALID feature id %s",featureId); checkArgument(logBean.getPersonId().equals(featureBean.getPersonId()), "MISMATCH person id for VerifyFeature"); } }*/ return logBean; } @Override protected LogBean daoAddLog(LogBean logBean) throws RuntimeDaoException, DuplicateRecordException { return super.daoAddLog(checkLogBean(logBean)); } @Override protected LogBean daoAddLog(LogBean logBean, DeviceBean refDeviceByDeviceId, FaceBean refFaceByCompareFace, FeatureBean refFeatureByVerifyFeature, ImageBean refImageByImageMd5, PersonBean refPersonByPersonId) throws RuntimeDaoException, DuplicateRecordException { return super.daoAddLog(checkLogBean(logBean), refDeviceByDeviceId, refFaceByCompareFace, refFeatureByVerifyFeature,refImageByImageMd5, refPersonByPersonId); } /** * 添加一条验证日志记录 *
{@code DEVICE_ONLY} * @param logBean 日志记录对象 * @param faceBean 需要保存到数据库的提取人脸特征的人脸信息对象 * @param featureImage 需要保存到数据库的现场采集人脸特征的照片 * @throws DuplicateRecordException 数据库中存在相同记录 */ protected LogBean daoAddLog(LogBean logBean,FaceBean faceBean,ByteBuffer featureImage) throws DuplicateRecordException { if(logBean == null || faceBean == null || featureImage == null){ return null; } PersonBean personBean = daoGetReferencedByPersonIdOnLog(logBean); DeviceBean deviceBean = daoGetReferencedByDeviceIdOnLog(logBean); daoAddImage(featureImage, deviceBean, faceBean == null ? null : Arrays.asList(faceBean), null); return daoAddLog(logBean, deviceBean, faceBean, null, null,personBean); } enum ErrorCatalog{ SERVICE,DEVICE,APPLICATION } private ErrorLogBean checkErrorLogBean(ErrorLogBean logBean){ String catalog = checkNotNull(logBean.getCatalog(),"catalog field is null"); Matcher machter = Pattern.compile("(\\w+)(/\\w+)+").matcher(catalog); checkArgument(machter.matches(),"INVALID catalog format [%s],such as [device/network] required",catalog); String level1 = machter.group(1); try { ErrorCatalog.valueOf(level1.toUpperCase()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("INVALID catalog level 1:" + level1); } return logBean; } @Override protected ErrorLogBean daoAddErrorLog(ErrorLogBean errorLogBean) throws RuntimeDaoException, DuplicateRecordException { return super.daoAddErrorLog(checkErrorLogBean(errorLogBean)); } private volatile PersonBean dummyUser; private PersonBean getDummyUser(){ // double check if(dummyUser == null){ synchronized (this) { if(dummyUser == null){ PersonBean tmpl = PersonBean.builder().name(DUMMY_USER).build(); List users = daoLoadPersonUsingTemplate(tmpl, 1, -1); if(users.isEmpty()){ dummyUser = daoSavePerson(tmpl); } else{ dummyUser = users.get(0); } } } } return dummyUser; } protected LogBean daoAddLog(LogBean logBean,ByteBuffer faceImage,Integer deviceId) throws DuplicateRecordException { if(logBean == null){ logBean = new LogBean(); } if(faceImage == null){ return null; } PersonBean person; if(logBean.getPersonId() == null){ person = getDummyUser(); logBean.setPersonId(person.getId()); }else{ person = daoGetPersonChecked(logBean.getPersonId()); } DeviceBean deviceBean = daoGetDevice(deviceId); ImageBean imageBean = daoAddImage(faceImage, deviceBean, null, null); return daoAddLog(logBean, deviceBean, null, null, imageBean,person); } /** * 设置 personId 指定的人员为禁止状态
* @param personId * @param moveToGroupId 将用户移动到指定的用户组,为{@code null}则不移动 * @param deletePhoto 为{@code true}删除用户标准照 * @param deleteFeature 为{@code true}删除用户所有的人脸特征数据(包括照片) * @param deleteLog 为{@code true}删除用户所有通行日志 */ protected void daoDisablePerson(int personId, Integer moveToGroupId, boolean deletePhoto, boolean deleteFeature, boolean deleteLog){ PersonBean personBean = daoGetPerson(personId); if(personBean == null){ return; } // 有效期设置为昨天 Date yesterday = new Date(System.currentTimeMillis() - 86400000L); personBean.setExpiryDate(yesterday); if(moveToGroupId != null){ personBean.setGroupId(moveToGroupId); } daoSavePerson(personBean); if(deletePhoto){ // 删除标准照 daoDeleteImage(personBean.getImageMd5()); } if(deleteFeature){ daoDeleteAllFeaturesByPersonId(personId,true); } if(deleteLog){ daoDeleteLogBeansByPersonIdOnPerson(personId); } } /** 允许修改的fl_permit表字段ID */ private static final HashSet allowedPermitColumns=Sets.newHashSet(FL_PERMIT_ID_SCHEDULE, FL_PERMIT_ID_PASS_LIMIT); /** * 修改指定记录的的字段值(String类型)
* 如果记录不存在则创建deviceGroupId和personGroupId之间的MANY TO MANY 联接表(fl_permit)记录, * @param deviceGroupId 设备组id * @param personGroupId 人员组id * @param column 字段名,允许的字段名为'schedule','pass_limit' * @param value 字段值 * @return (fl_permit)记录 */ protected PermitBean daoSavePermit(int deviceGroupId,int personGroupId,String column, String value){ int columnID = -1; if(column != null){ columnID=RowMetaData.getMetaData(PermitBean.class).columnIDOf(column); checkArgument(columnID>=0,"invalid column name %s",column); checkArgument(allowedPermitColumns.contains(columnID),"column %s can't be modify",column); } PermitBean permitBean = daoGetPermit(deviceGroupId, personGroupId); if(permitBean == null){ permitBean = PermitBean.builder() .deviceGroupId(deviceGroupId) .personGroupId(personGroupId) .build(); } if(columnID >= 0){ permitBean.setValue(columnID, value); } return daoSavePermit(permitBean); } /** * 获取指定人脸特征关联的人脸特征记录 * @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表 * @return 特征记录id列表 */ protected List daoGetFeaturesOfImage(String imageMd5){ if(Strings.isNullOrEmpty(imageMd5)){ return Collections.emptyList(); } List faces = daoGetFaceBeansByImageMd5OnImage(imageMd5); Iterable features = Iterables.filter(Lists.transform(faces, daoCastFaceToFeatureMd5),Predicates.notNull()); return Lists.newArrayList(features); } /** * 获取指定人脸特征关联的人脸记录 * @param featureMd5 人脸特征记录id(MD校验码),为空或{@code null}或记录不存在返回空表 * @return {@link FaceBean}列表 */ protected List daoGetFacesOfFeature(String featureMd5){ if(Strings.isNullOrEmpty(featureMd5)){ return Collections.emptyList(); } return daoGetFaceBeansByFeatureMd5OnFeature(featureMd5); } /** * 获取指定图像关联的人脸记录 * @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表 * @return {@link FaceBean}列表 */ protected List daoGetFacesOfImage(String imageMd5){ if(Strings.isNullOrEmpty(imageMd5)){ return Collections.emptyList(); } return daoGetFaceBeansByImageMd5OnImage(imageMd5); } private static final Function dateCaster = new Function(){ @Override public LogBean apply(LogBean bean) { if(null == bean){ return null; } Date v = bean.getVerifyTime(); if(v != null){ v = bean.getCreateTime(); } if(v != null){ Calendar calendar=Calendar.getInstance(); calendar.setTime(v); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); bean.setVerifyTime(calendar.getTime()); } return bean; }}; /** * 按天统计指定用户的通行次数
* @param personId * @param startDate 统计起始日期,可为{@code null} * @param endDate 统计结束日期,可为{@code null} * @return 返回统计结果,即每个有通行记录的日期(格式:yyyy-MM-dd)的通行次数 */ protected Map daoCountPersonLog(int personId,Date startDate,Date endDate){ SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMATTER_STR); StringBuffer buffer = new StringBuffer("WHERE"); buffer.append(" person_id=").append(personId); checkArgument(null == startDate || null == endDate || startDate.getTime() <=endDate.getTime(),"start must <= end"); if(startDate != null){ buffer.append(" and"); buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(startDate))); } if(endDate != null){ buffer.append(" and"); buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(endDate))); } List logList = daoLoadLogByWhere(buffer.toString(), 1, -1); Lists.transform(logList, dateCaster); Mapstat=Maps.newHashMap(); for(LogBean log:logList){ Date d = log.getVerifyTime(); if(d != null){ String key = formatter.format(d); Integer c = MoreObjects.firstNonNull(stat.get(key), 0); stat.put(key, c + 1); } } return stat; } /** * 根据图像的MD5校验码返回图像数据 * @param imageMD5 * @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null */ protected byte[] daoGetImageBytes(String imageMD5) throws IOException{ StoreBean storeBean = daoGetStore(imageMD5); return null ==storeBean?null:BinaryUtils.getBytes(storeBean.getData()); } /** * 根据提供的主键ID,返回图像数据 * @param primaryKey 主键 * @param refType 主键引用类型 * @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null * @throws IOException */ protected byte[] daoGetImageBytes(String primaryKey,RefSrcType refType) throws IOException{ ImageBean bean = daoGetImage(primaryKey,refType); return null == bean ? null : daoGetImageBytes(bean.getMd5()); } /** * 根据提供的主键ID,返回图像数据记录 * @param primaryKey 主键 * @param refType 主键引用类型 * @return 图像数据记录 */ protected ImageBean daoGetImage(String primaryKey,RefSrcType refType) { checkArgument(refType!=null,"refType is null"); if(null == primaryKey){ return null; } switch (MoreObjects.firstNonNull(refType,RefSrcType.DEFAULT)) { case PERSON: { PersonBean bean = daoGetPerson(Integer.valueOf(primaryKey)); return null == bean ? null : daoGetImage(bean.getImageMd5()); } case FACE: { FaceBean bean = daoGetFace(Integer.valueOf(primaryKey)); return null == bean ? null : daoGetImage(bean.getImageMd5()); } case FEATURE: { List beans = daoGetImageKeysImportedByFeatureMd5(primaryKey); return beans.size() > 0 ? daoGetImage(beans.get(0)) : null; } case LOG: { LogBean logBean = daoGetLog(Integer.valueOf(primaryKey)); // 获取现场采集图像数据的优先序为: compare_face,image_md5 ImageBean imageBean = null == logBean ? null : daoGetImage(logBean.getCompareFace() == null ? null : logBean.getCompareFace().toString(),RefSrcType.FACE); if(imageBean == null){ return daoGetImage(logBean.getImageMd5()); } return imageBean; } case LIGHT_LOG: { return daoGetImage(primaryKey,RefSrcType.LOG); } case IMAGE: case DEFAULT: return daoGetImage(primaryKey); default: return null; } } /** * MAC地址归一化处理 * @param mac * @return */ private static final String normalizeMac(String mac){ // mac地址保存为小写强制转小写查找 return mac == null ? null : mac.toLowerCase(); } private static final Function normalizeDeviceBean = new Function(){ @Override public DeviceBean apply(DeviceBean input) { if(input == null || input.getMac() == null){ return input; } int modified = input.getModified(); input.setMac(normalizeMac(input.getMac())); input.setModified(modified); return input; }}; @Override protected DeviceBean daoGetDeviceByIndexMac(String mac) throws RuntimeDaoException{ return super.daoGetDeviceByIndexMac(normalizeMac(mac)); } @Override protected DeviceBean daoSaveDevice(DeviceBean deviceBean) throws RuntimeDaoException{ return super.daoSaveDevice(normalizeDeviceBean.apply(deviceBean)); } /** * 个人信息脱敏转换器 */ protected static final Function DESENSITIZATION_TRANSFORMER= new Function(){ @Override public PersonBean apply(PersonBean input) { if(input != null){ PersonBean output = input.clone(); if(null != output.getName()){ output.setName(output.getName().replaceAll("\\S", "*")); } if(null != output.getPapersNum()){ output.setPapersNum(output.getPapersNum().replaceAll("\\S", "*")); } if(null != output.getMobilePhone()){ output.setMobilePhone(output.getMobilePhone().replaceAll("\\S", "*")); } return output; } return input; }}; /** * 个人信息脱敏 * @param input 输入数据 * @return 输出脱敏数据 */ protected static final PersonBean desensitize(PersonBean input) { return DESENSITIZATION_TRANSFORMER.apply(input); } /** * 个人信息脱敏 * @param input 输入数据 * @return 输出脱敏数据 */ protected static final List desensitize(List input) { return input == null ? null : Lists.transform(input, DESENSITIZATION_TRANSFORMER); } /** * 返回'name'字段为指定值的所有记录 * @param interfaceClass * @param name * @param filter 用于筛选记录的过滤器,可为{@code null} * @return 返回结果结录列表,没找到则返回空表 */ protected > List daoGetBeansByName(ClassinterfaceClass,String name, Predicate filter){ List beans = instanceOf(interfaceClass).loadByWhereAsList(String.format("WHERE name='%s'",name)); if(filter != null){ return Lists.newArrayList(Iterables.filter(beans, filter)); } return beans; } protected > String daoGroupPathOf(ClassinterfaceClass, Integer id){ M manager = instanceOf(interfaceClass); try { @SuppressWarnings({ "unchecked" }) List parentList = (List) interfaceClass.getMethod("listOfParent", Integer.class).invoke(manager, id); List parentNames = Lists.transform(parentList, new ColumnGetter("name")); return Joiner.on('/').skipNulls().join(parentNames); } catch (InvocationTargetException e) { Throwables.throwIfUnchecked(e.getTargetException()); throw new RuntimeException(e.getTargetException()); } catch (Exception e) { Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } } protected > String daoGroupPathOf(ClassinterfaceClass,B group){ return daoGroupPathOf(interfaceClass,(Integer)checkNotNull(group,"group is null").getValue("id")); } private > void deleteElements(ClassinterfaceClass, B child){ M manager = instanceOf(interfaceClass); try { String elementTypeName = child.getClass().getSimpleName().replace("Group", ""); String methodName = String.format("delete%ssByGroupId", elementTypeName); interfaceClass.getMethod(methodName, Integer.class).invoke(manager, child.getValue("id")); } catch (InvocationTargetException e) { Throwables.throwIfUnchecked(e.getTargetException()); throw new RuntimeException(e.getTargetException()); } catch (Exception e) { Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } } String daoPathOf(String tablename,Integer id){ if("fl_device_group".equals(tablename)){ return daoGroupPathOf(IDeviceGroupManager.class, id); }else if("fl_person_group".equals(tablename)){ return daoGroupPathOf(IPersonGroupManager.class, id); }else{ throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename)); } } /** * 删除指定组下的所有子节点 * @param interfaceClass * @param groupId * @param deleteElement 是否删除组中的元素(人员/设备记录) */ protected > void daoDeleteChilds(ClassinterfaceClass, Integer groupId, final boolean deleteElement){ M manager = instanceOf(interfaceClass); try { @SuppressWarnings("unchecked") Listchilds = (List) interfaceClass.getMethod("childListByParent", Integer.class).invoke(manager, groupId); for(B child:childs){ if(deleteElement){ deleteElements(interfaceClass,child); } manager.delete(child); } } catch (InvocationTargetException e) { Throwables.throwIfUnchecked(e.getTargetException()); throw new RuntimeException(e.getTargetException()); } catch (Exception e) { Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } } /** * 删除指定组下的所有子节点 * @param interfaceClass * @param groupId * @param deleteElement 是否删除组中的元素(人员/设备记录) */ protected > void daoDeleteChilds(ClassinterfaceClass, B groupId, final boolean deleteElement){ daoDeleteChilds( interfaceClass, (Integer)checkNotNull(groupId,"group is null").getValue("id"), deleteElement); } /** * 删除指定组并删除指定组下的所有子节点 * @param interfaceClass * @param groupId 组ID * @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录) */ protected > void daoDeleteTree(ClassinterfaceClass, Integer groupId, boolean deleteElement){ M manager = instanceOf(interfaceClass); B bean = manager.loadByPrimaryKey(groupId); if(bean != null){ daoDeleteChilds(interfaceClass,groupId, deleteElement); if(deleteElement){ deleteElements(interfaceClass,bean); } manager.delete(bean); } } /** * 删除指定组并删除指定组下的所有子节点 * @param interfaceClass * @param group * @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录) */ protected > void daoDeleteTree(ClassinterfaceClass, B group, boolean deleteElement){ daoDeleteTree( interfaceClass, (Integer)checkNotNull(group,"group is null").getValue("id"), deleteElement); } /** * 获取{@code parent}指定的组下不重复的唯一名字 * @param interfaceClass * @param name * @param parent * @param cleanMore * @param deleteElement 是否删除组中的元素(人员/设备记录) * @param found * @return */ private > String getUniqueName(ClassinterfaceClass,final String name, final Integer parent,boolean cleanMore, boolean deleteElement, AtomicReference found){ int c = 0; String n = name; do{ // 返回parent指定的组下所有指定名称的记录 List groups = daoGetBeansByName(interfaceClass,n, new Predicate() { @Override public boolean apply(B input) { return Objects.equal(parent, input.getValue("parent")); } }); if(groups.size() > 1){ if(cleanMore){ // 删除多余的同名记录 for(int i = 1; i < groups.size();++i){ B d = groups.get(i); daoDeleteTree(interfaceClass, d, deleteElement); logger.info("Delete duplicated group tree {}[{}] on {}", daoGroupPathOf(interfaceClass, d), d.getValue("id"), d.tableName()); } if(found != null){ found.set(groups.get(0)); } return n; }else{ n = String.format("%s(%d)", name,++c); continue; } }else if(groups.size() == 1){ if(found != null){ found.set(groups.get(0)); } return n; }else{ found.set(null); return n; } }while(true); } /** * 创建组节点 * @param interfaceClass * @param name * @param parent * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个 * @param deleteElement 删除组时是否删除组下的元素(设备/人员) * @return */ protected > B daoCreateGroupByNameIfNoExist(ClassinterfaceClass,String name,Integer parent, boolean cleanMore, boolean deleteElement){ M manager = instanceOf(interfaceClass); AtomicReference found = new AtomicReference<>(); name = getUniqueName(interfaceClass,name,parent,cleanMore, deleteElement, found); if(found.get() == null){ B group = manager.createBean(); group.setValue("name", name); group.setValue("parent", parent); manager.save(group); logger.info("Create group {}[{}] on {}", daoGroupPathOf(interfaceClass, group), group.getValue("id"), group.tableName()); return group; } logger.info("Exist group {}[{}] on {}", daoGroupPathOf(interfaceClass,found.get()), found.get().getValue("id"),found.get().tableName()); return found.get(); } /** * 根据{@code cleanExistingTop}参数标志删除存在同名顶级组节点 * @param interfaceClass * @param name 顶级组名 * @param cleanExistingTop 是否删除存在的同名顶级组节点 * @param deleteElement 是否删除组中的元素(人员/设备记录) */ private > void deleteTopGroupIfExist(ClassinterfaceClass, String name, boolean cleanExistingTop, boolean deleteElement){ if(cleanExistingTop){ // 删除存在的所有同名顶级组 List groups = daoGetBeansByName(interfaceClass,name,new Predicate() { @Override public boolean apply(B input) { return input.getValue("parent") == null; } }); for(B group:groups){ daoDeleteTree(interfaceClass,group, deleteElement); logger.info("REMOVE top group {}[{}] on {}",group.getValue("name"),group.getValue("id"),group.tableName()); } } } /** * 创建顶级组节点 * @param interfaceClass * @param name * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个 * @param deleteElement 删除组时是否删除组下的元素(设备/人员) * @return */ protected > B daoCreateTopGroupIfNoExist(ClassinterfaceClass, String name, boolean cleanMore, boolean deleteElement){ B group = daoCreateGroupByNameIfNoExist(interfaceClass,name,null, cleanMore, false); checkArgument(group.getValue("parent") == null, "group %s:%s is not top group",group.tableName(),name); return group; } /** * 根据路径创建组节点 * @param interfaceClass * @param path * @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个 * @param deleteElement 删除组时是否删除组下的元素(设备/人员) * @return */ protected > B[] daoCreateGroupByPathIfNoExist(ClassinterfaceClass,String path, boolean cleanMore, boolean deleteElement){ String[] nodes = TopGroupInfo.parseNodes(path); @SuppressWarnings("unchecked") B[] nodeBeans = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(), nodes.length); if(nodes.length > 0){ logger.info("Create path {}",Joiner.on('/').join(nodes)); B bean = daoCreateTopGroupIfNoExist(interfaceClass, nodes[0], cleanMore, deleteElement); Integer parent = bean.getValue("id"); nodeBeans[0] = bean; for(int i = 1; i < nodes.length; ++i){ bean = daoCreateGroupByNameIfNoExist(interfaceClass, nodes[i], parent, cleanMore, deleteElement); nodeBeans[i] = bean; parent = bean.getValue("id"); } } return nodeBeans; } protected > List daoGetGroupsByPath(final ClassinterfaceClass,final String path){ String[] nodes = TopGroupInfo.parseNodes(path); if(nodes.length > 0){ return daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate() { @Override public boolean apply(B input) { return path.equals(daoGroupPathOf(interfaceClass,input)); } }); } return Collections.emptyList(); } protected List daoGetGroupIdsByPath(String tablename,String path){ if("fl_device_group".equals(tablename)){ return Lists.transform(daoGetGroupsByPath(IDeviceGroupManager.class, path), daoCastDeviceGroupToPk); }else if("fl_person_group".equals(tablename)){ return Lists.transform(daoGetGroupsByPath(IPersonGroupManager.class, path), daoCastPersonGroupToPk); }else{ throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename)); } } protected > B daoGetGroupByPath(final ClassinterfaceClass,final String path){ String[] nodes = TopGroupInfo.parseNodes(path); if(nodes.length > 0){ List nodeBeans = daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate() { @Override public boolean apply(B input) { return path.equals(daoGroupPathOf(interfaceClass,input)); } }); switch(nodeBeans.size()){ case 0: return null; case 1: return nodeBeans.get(0); default: throw new IllegalArgumentException(String.format("FOUND multi record in %s,path=%s", nodeBeans.get(0).tableName(),path)); } } return null; } /** * 创建叶子节点 * @param interfaceClass * @param parent * @param name * @param cleanMore * @return */ protected > B daoCreateLeafIfNoExist(ClassinterfaceClass,int parent,String name, boolean cleanMore){ checkArgument(!Strings.isNullOrEmpty(name),"name is null or empty"); B bean; M manager = instanceOf(interfaceClass); B[] found = manager.loadByWhere(String.format("WHERE name='%s' AND parent=%d",name,parent)); if(found.length > 0){ if(found.length > 1 && cleanMore ){ for(int i = 1; i < found.length; ++i){ manager.delete(found[i]); } } bean = found[0]; logger.info("Exist leaf group {}[{}] on {}", daoGroupPathOf(interfaceClass, bean), bean.getValue("id"), bean.tableName()); }else{ bean = manager.createBean(); bean.setValue("name", name); bean.setValue("parent", parent); bean.setValue("leaf", 1); manager.save(bean); logger.info("Create leaf group {}[{}] on {}", daoGroupPathOf(interfaceClass, bean), bean.getValue("id"), bean.tableName()); } return bean; } /** * 创建叶子节点 * @param interfaceClass * @param parent * @param names * @param cleanMore * @return */ protected > B[] daoCreateLeafBeansIfNoExist(ClassinterfaceClass,int parent,String names, boolean cleanMore){ List list = MiscellaneousUtils.elementsOf(names); @SuppressWarnings("unchecked") B[] ids = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(),list.size()); for(int i = 0 ; i < list.size(); ++i){ ids[i] = daoCreateLeafIfNoExist(interfaceClass,parent,list.get(i),cleanMore); } return ids; } private void permitPath(PersonGroupBean permitPersonGroup,String devicePath){ DeviceGroupBean deviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, devicePath); if(deviceGroup != null){ daoSavePermit(deviceGroup.getId(), permitPersonGroup.getId(), null, null); logger.info("Permit person group {}[{}] on device {}[{}]", daoGroupPathOf(IPersonGroupManager.class, permitPersonGroup), permitPersonGroup.getId(), devicePath, deviceGroup.getId()); }else{ logger.warn("NO FOUND device path {}",devicePath); } } /** * 根据输入的{@link TopGroupInfo}对象创建顶级组(人员/设备)其下子节点
* @param groupInfo * @return 顶级人员组id */ protected int initTopGroup(TopGroupInfo groupInfo){ checkArgument(groupInfo != null,"groupInfo is null"); checkArgument(!Strings.isNullOrEmpty(groupInfo.getName()),"name is null or empty"); groupInfo.normalize(); deleteTopGroupIfExist(IDeviceGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement()); deleteTopGroupIfExist(IPersonGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement()); DeviceGroupBean topDeviceGroup = daoCreateTopGroupIfNoExist(IDeviceGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement()); PersonGroupBean topPersonGroup = daoCreateTopGroupIfNoExist(IPersonGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement()); daoBindBorder(topPersonGroup.getId(), topDeviceGroup.getId()); logger.info("==BindBorder person top group {}[{}] VS device top person {}[{}]", topPersonGroup.getName(), topPersonGroup.getId(), topDeviceGroup.getName(), topDeviceGroup.getId()); if(!groupInfo.getNodes().isEmpty()){ logger.info("=============================="); logger.info("====Create deive/permit groups pairs====="); logger.info("=============================="); // 创建配对的设备组人员组并授权 for(Entry entry:groupInfo.getNodes().entrySet()){ String path = groupInfo.fullPathOf( entry.getKey()); String leafs = entry.getValue(); DeviceGroupBean[] deviceGroupParentBeans = daoCreateGroupByPathIfNoExist(IDeviceGroupManager.class, path, groupInfo.isClearMore(), false); PersonGroupBean[] personGroupParentBeans = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, path, groupInfo.isClearMore(), false); checkState(deviceGroupParentBeans.length == personGroupParentBeans.length,"MISMATCH length between deviceGroupIds and personGroupIds"); for(int i = 0; i < deviceGroupParentBeans.length; i++){ daoSavePermit(deviceGroupParentBeans[i].getId(), personGroupParentBeans[i].getId(), null, null); logger.info("Permit person group {}[{}] ON device {}[{}]", daoGroupPathOf(IPersonGroupManager.class, personGroupParentBeans[i]), personGroupParentBeans[i].getId(), daoGroupPathOf(IDeviceGroupManager.class,deviceGroupParentBeans[i]), deviceGroupParentBeans[i].getId()); } DeviceGroupBean[] deviceGroupLeafBeans = daoCreateLeafBeansIfNoExist(IDeviceGroupManager.class, deviceGroupParentBeans[deviceGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore()); PersonGroupBean[] personGroupLeftBeans = daoCreateLeafBeansIfNoExist(IPersonGroupManager.class, personGroupParentBeans[personGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore()); for(int i = 0; i < deviceGroupLeafBeans.length; ++i){ daoSavePermit(deviceGroupLeafBeans[i].getId(), personGroupLeftBeans[i].getId(), null, null); logger.info("Permit person group {}[{}] on device {}[{}]", daoGroupPathOf(IPersonGroupManager.class, personGroupLeftBeans[i]), personGroupLeftBeans[i].getId(), daoGroupPathOf(IDeviceGroupManager.class,deviceGroupLeafBeans[i]), deviceGroupLeafBeans[i].getId()); } } } if(!groupInfo.getPermits().isEmpty()){ logger.info("=============================="); logger.info("===========Create Permits=========="); logger.info("=============================="); // 对指定人员组授权指定的设备组 for(Entry entry:groupInfo.getPermits().entrySet()){ String personGroupPath = groupInfo.fullPathOf(Strings.nullToEmpty(entry.getKey())); String deviceLeafs = Strings.nullToEmpty(entry.getValue()); if(!Strings.isNullOrEmpty(deviceLeafs)){ PersonGroupBean[] personGroupIds = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, personGroupPath, groupInfo.isClearMore(), false); PersonGroupBean permitPersonGroup = personGroupIds[personGroupIds.length - 1]; String[] entries = deviceLeafs.split("[,]"); if(entries.length > 0){ String prefix = groupInfo.fullPathOf(entries[0]); if(entries.length == 2){ if(entries[1].equals("*")){ // 第二个元素为通配符则列出所有子节点 DeviceGroupBean prefixDeviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, prefix); if(prefixDeviceGroup != null){ List childs = daoGetSubDeviceGroup(prefixDeviceGroup.getId()); List childNames = Lists.newArrayList(Iterables.transform(childs, new Function(){ @Override public String apply(DeviceGroupBean input) { return input.getName(); }})); childNames.add(0, entries[0]); entries = childNames.toArray(new String[0]); }else{ logger.warn("NO FOUND device path {}",prefix); continue; } } } for(int i = 1; i < entries.length; ++i){ if(!entries[i].isEmpty()){ permitPath(permitPersonGroup, prefix + "/" + entries[i]); } } permitPath(permitPersonGroup,prefix); } } } } return topPersonGroup.getId(); } }