This commit is contained in:
2025-04-05 13:21:52 +08:00
parent 67dca0d4d4
commit 0ab142e1c4
19 changed files with 215 additions and 337 deletions

View File

@@ -1,33 +1,20 @@
package com.ycwl.basic.service.task;
import com.ycwl.basic.model.task.resp.AddFaceSampleRespVo;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.multipart.MultipartFile;
public interface TaskFaceService {
SearchFaceRespVo searchFace(Long faceId);
SearchFaceRespVo searchFace(Long scenicId, String faceUrl);
SearchFaceRespVo searchFace(String dbName, String faceUrl);
SearchFaceRespVo searchFace(String dbName, String faceUrl, String reason);
AddFaceSampleRespVo addFaceSample(Long faceSampleId);
AddFaceSampleRespVo addFaceSample(String dbName, String entityId, String faceUrl, String extData);
SearchFaceRespVo searchFace(IFaceBodyAdapter adapter, String dbName, String faceUrl, String reason);
void batchDeleteExpiredFace(Long scenicId);
void deleteFaceDB(String scenicId);
void createFaceDB(String scenicId);
void assureFaceDB(String scenicId);
String uploadFile(MultipartFile file, Long userId);
boolean deleteFaceSample(String dbName, String entityId);
boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName);
}

View File

@@ -3,23 +3,19 @@ package com.ycwl.basic.service.task.impl;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.facebody.model.v20191230.AddFaceEntityRequest;
import com.aliyuncs.facebody.model.v20191230.AddFaceRequest;
import com.aliyuncs.facebody.model.v20191230.AddFaceResponse;
import com.aliyuncs.facebody.model.v20191230.CreateFaceDbRequest;
import com.aliyuncs.facebody.model.v20191230.DeleteFaceDbRequest;
import com.aliyuncs.facebody.model.v20191230.DeleteFaceEntityRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceDbsRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceDbsResponse;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesResponse;
import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest;
import com.aliyuncs.facebody.model.v20191230.SearchFaceResponse;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.config.FaceDetectConfig;
import com.ycwl.basic.constant.FaceConstant;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.facebody.FaceBodyFactory;
import com.ycwl.basic.facebody.adapter.AliFaceBodyAdapter;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.entity.SearchFaceResp;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.mapper.FaceDetectLogMapper;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
@@ -37,7 +33,6 @@ import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.task.resp.AddFaceSampleRespVo;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.ratelimiter.FixedRateLimiter;
import com.ycwl.basic.repository.DeviceRepository;
@@ -63,17 +58,17 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
@@ -87,8 +82,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
@Autowired
private ScenicMapper scenicMapper;
@Autowired
private FaceDetectConfig faceDetectConfig;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private FaceDetectLogMapper logMapper;
@@ -98,11 +91,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
private SourceMapper sourceMapper;
@Autowired
private OrderBiz orderBiz;
// 阿里云人脸检索限制qps=2
private final FixedRateLimiter addEntityLimiter = new FixedRateLimiter(600, TimeUnit.MILLISECONDS);
// 阿里云人脸检索限制qps=5
private final FixedRateLimiter searchFaceLimiter = new FixedRateLimiter(200, TimeUnit.MILLISECONDS);
private final FixedRateLimiter deleteDbLimiter = new FixedRateLimiter(600, TimeUnit.MILLISECONDS);
private final FixedRateLimiter deleteEntityLimiter = new FixedRateLimiter(600, TimeUnit.MILLISECONDS);
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 1024, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024));
@@ -112,10 +100,8 @@ public class TaskFaceServiceImpl implements TaskFaceService {
private DeviceRepository deviceRepository;
private IAcsClient getClient() {
DefaultProfile profile = DefaultProfile.getProfile(
faceDetectConfig.getRegion(),faceDetectConfig.getAccessKeyId(), faceDetectConfig.getAccessKeySecret());
IAcsClient client = new DefaultAcsClient(profile);
return client;
AliFaceBodyAdapter use = (AliFaceBodyAdapter) FaceBodyFactory.use();
return use.getClient().getClient();
}
@Override
@@ -127,7 +113,15 @@ public class TaskFaceServiceImpl implements TaskFaceService {
return vo;
}
Long scenicId = faceRespVO.getScenicId();
SearchFaceRespVo respVo = searchFace(scenicId.toString(), faceRespVO.getFaceUrl(), "系统定时任务检索");
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
IFaceBodyAdapter faceBodyAdapter;
if (scenicConfig != null && scenicConfig.getFaceType() != null) {
faceBodyAdapter = FaceBodyFactory.getAdapter(scenicConfig.getFaceType());
faceBodyAdapter.loadConfig(JSONObject.parseObject(scenicConfig.getFaceConfigJson(), Map.class));
} else {
faceBodyAdapter = FaceBodyFactory.use();
}
SearchFaceRespVo respVo = searchFace(faceBodyAdapter, scenicId.toString(), faceRespVO.getFaceUrl(), "系统定时任务检索");
if (respVo != null) {
FaceEntity faceEntity = new FaceEntity();
faceEntity.setId(faceId);
@@ -184,29 +178,14 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
@Override
public SearchFaceRespVo searchFace(Long scenicId, String faceUrl) {
return searchFace(scenicId.toString(), faceUrl, "预留字段");
}
@Override
public SearchFaceRespVo searchFace(String dbName, String faceUrl) {
return searchFace(dbName, faceUrl, "预留字段");
}
@Override
public SearchFaceRespVo searchFace(String dbName, String faceUrl, String reason) {
assureFaceDB(dbName);
IAcsClient client = getClient();
public SearchFaceRespVo searchFace(IFaceBodyAdapter adapter, String dbName, String faceUrl, String reason) {
assureFaceDb(adapter, dbName);
SearchFaceRequest request = new SearchFaceRequest();
request.setDbName(dbName);
request.setImageUrl(faceUrl);
request.setLimit(200);
// request.setQualityScoreThreshold(60f);
FaceDetectLog logEntity = FaceDetectLog.quickCreate(reason, request);
try {
searchFaceLimiter.acquire();
} catch (InterruptedException ignored) {
}
float threshold = 0.525F;
int tourMinutes = -1;
if (StringUtils.isNumeric(dbName)) {
@@ -223,26 +202,30 @@ public class TaskFaceServiceImpl implements TaskFaceService {
final float _threshold = threshold;
List<Long> acceptFaceSampleIds;
try {
SearchFaceResponse response = client.getAcsResponse(request);
SearchFaceResp response = adapter.searchFace(dbName, faceUrl);
logEntity.fillResponse(response);
List<SearchFaceResponse.Data.MatchListItem> matchList = response.getData().getMatchList();
if (matchList.isEmpty()) {
List<SearchFaceResultItem> records = response.getResult();
if (records.isEmpty()) {
return null;
}
SearchFaceRespVo respVo = new SearchFaceRespVo();
respVo.setScore(matchList.get(0).getQualitieScore());
respVo.setSearchResultJson(JSON.toJSONString(matchList.get(0)));
if (matchList.get(0).getFaceItems().isEmpty()) {
respVo.setScore(response.getOriginalFaceScore());
respVo.setSearchResultJson(JSON.toJSONString(records));
if (records.isEmpty()) {
return respVo;
}
List<SearchFaceResponse.Data.MatchListItem.FaceItemsItem> records = matchList.get(0).getFaceItems();
logEntity.setMatchRawRecord(records);
acceptFaceSampleIds = records.stream()
.filter(record -> record.getScore() > _threshold)
.map(SearchFaceResponse.Data.MatchListItem.FaceItemsItem::getExtraData)
.map(SearchFaceResultItem::getExtData)
.filter(StringUtils::isNumeric)
.map(Long::valueOf)
.collect(Collectors.toList());
if (acceptFaceSampleIds.isEmpty()) {
respVo.setFirstMatchRate(0f);
respVo.setSampleListIds(Collections.emptyList());
return respVo;
}
List<FaceSampleEntity> faceSampleList = new ArrayList<>();
if (StringUtils.isNumeric(dbName)) { // 景区
faceSampleList = faceSampleMapper.listByIds(acceptFaceSampleIds);
@@ -258,19 +241,25 @@ public class TaskFaceServiceImpl implements TaskFaceService {
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配:{},时间范围需要在:{}~{}间", firstFaceSample, startDate, endDate);
} else {
acceptFaceSampleIds = faceSampleList.stream()
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:景区未限制");
}
} else {
acceptFaceSampleIds = faceSampleList.stream()
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配ID:{},未找到", firstFaceSampleId);
}
}
List<MatchLocalRecord> collect = new ArrayList<>();
for (SearchFaceResponse.Data.MatchListItem.FaceItemsItem item : records) {
for (SearchFaceResultItem item : records) {
MatchLocalRecord record = new MatchLocalRecord();
record.setIdStr(item.getExtraData());
record.setFaceSampleId(Long.parseLong(item.getExtraData()));
record.setIdStr(item.getExtData());
record.setFaceSampleId(Long.parseLong(item.getExtData()));
if (StringUtils.isNumeric(item.getDbName())) {
Optional<FaceSampleEntity> optionalFse = faceSampleList.stream().filter(face -> face.getId().equals(Long.parseLong(item.getExtraData()))).findAny();
Optional<FaceSampleEntity> optionalFse = faceSampleList.stream().filter(face -> face.getId().equals(Long.parseLong(item.getExtData()))).findAny();
if (optionalFse.isPresent()) {
DeviceEntity device = deviceRepository.getDevice(optionalFse.get().getDeviceId());
if (device != null) {
@@ -285,11 +274,10 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
record.setScore(item.getScore());
record.setMatched(item.getScore() > _threshold);
record.setConfidence(item.getConfidence());
collect.add(record);
}
logEntity.setMatchLocalRecord(JSONObject.toJSONString(collect));
respVo.setFirstMatchRate(matchList.get(0).getFaceItems().get(0).getScore());
respVo.setFirstMatchRate(response.getFirstMatchRate());
respVo.setSampleListIds(acceptFaceSampleIds);
return respVo;
} catch (Exception e) {
@@ -300,54 +288,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
}
@Override
public AddFaceSampleRespVo addFaceSample(Long faceSampleId) {
FaceSampleRespVO faceSampleRespVO = faceSampleMapper.getById(faceSampleId);
String entityId = generateEntityId(faceSampleRespVO);
AddFaceSampleRespVo respVo = addFaceSample(faceSampleRespVO.getScenicId().toString(), entityId, faceSampleRespVO.getFaceUrl(), faceSampleId.toString());
FaceSampleEntity faceSampleEntity = new FaceSampleEntity();
faceSampleEntity.setId(faceSampleId);
if (respVo != null) {
faceSampleEntity.setScore(respVo.getScore());
}
faceSampleEntity.setUpdateAt(new Date());
faceSampleMapper.update(faceSampleEntity);
return respVo;
}
@Override
public AddFaceSampleRespVo addFaceSample(String dbName, String entityId, String faceUrl, String extData) {
assureFaceDB(dbName);
AddFaceEntityRequest request = new AddFaceEntityRequest();
request.setDbName(dbName);
request.setEntityId(entityId);
IAcsClient client = getClient();
try {
addEntityLimiter.acquire();
} catch (InterruptedException ignored) {
}
try {
client.getAcsResponse(request);
} catch (ClientException e) {
log.error("addFaceEntity, {}/{}", dbName, entityId, e);
return null;
}
AddFaceRequest addFaceRequest = new AddFaceRequest();
addFaceRequest.setDbName(dbName);
addFaceRequest.setEntityId(entityId);
addFaceRequest.setImageUrl(faceUrl);
addFaceRequest.setExtraData(extData);
AddFaceSampleRespVo respVo = new AddFaceSampleRespVo();
try {
AddFaceResponse acsResponse = client.getAcsResponse(addFaceRequest);
respVo.setScore(acsResponse.getData().getQualitieScore());
return respVo;
} catch (ClientException e) {
log.error("addFace, {}/{}", dbName, entityId, e);
return null;
}
}
@Override
public void batchDeleteExpiredFace(Long scenicId) {
log.info("当前景区{},开始删除人脸样本", scenicId);
@@ -384,6 +324,37 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
} catch (Exception ignored) {
}
listFaceEntitiesRequest.setDbName(String.valueOf(scenicId));
listFaceEntitiesRequest.setEntityIdPrefix("");
try {
AtomicInteger count = new AtomicInteger(0);
IAcsClient client = getClient();
while (true) {
listFaceEntitiesRequest.setOffset(count.get());
ListFaceEntitiesResponse listFaceEntitiesResponse = client.getAcsResponse(listFaceEntitiesRequest);
if (listFaceEntitiesResponse == null || listFaceEntitiesResponse.getData() == null || listFaceEntitiesResponse.getData().getEntities() == null || listFaceEntitiesResponse.getData().getEntities().isEmpty()) {
break;
}
listFaceEntitiesResponse.getData().getEntities().forEach(entity -> {
String entityId = entity.getEntityId();
String[] split = entityId.split("_");
if (split.length != 2) {
return;
}
String dateString = split[1];
if (StringUtils.isBlank(dateString)) {
return;
}
if (DateUtils.parse(dateString, DATE_FORMAT).before(endDate)) {
deleteFaceSample(entity.getDbName(), entity.getEntityId());
} else {
count.incrementAndGet();
log.info("当前景区{},人脸样本:{}未过期", scenicId, entity.getEntityId());
}
});
}
} catch (Exception ignored) {
}
List<DeviceEntity> devices = deviceRepository.getAllDeviceByScenicId(scenicId);
listFaceEntitiesRequest.setDbName(String.valueOf(scenicId));
listFaceEntitiesRequest.setOrder("asc");
@@ -439,98 +410,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
});
}
@Override
public void deleteFaceDB(String dbName) {
ListFaceEntitiesRequest request = new ListFaceEntitiesRequest();
request.setDbName(dbName);
request.setLimit(200);
IAcsClient client = getClient();
try {
while (true) {
ListFaceEntitiesResponse response = client.getAcsResponse(request);
if (response.getData().getTotalCount() == 0) {
break;
}
response.getData().getEntities().forEach(entity -> {
DeleteFaceEntityRequest deleteFaceEntityRequest = new DeleteFaceEntityRequest();
deleteFaceEntityRequest.setDbName(entity.getDbName());
deleteFaceEntityRequest.setEntityId(entity.getEntityId());
try {
deleteEntityLimiter.acquire();
} catch (InterruptedException ignored) {
}
try {
client.getAcsResponse(deleteFaceEntityRequest);
} catch (ClientException e) {
log.error("删除人脸数据失败!", e);
}
});
}
DeleteFaceDbRequest deleteFaceDbRequest = new DeleteFaceDbRequest();
deleteFaceDbRequest.setName(dbName);
try {
deleteDbLimiter.acquire();
} catch (InterruptedException ignored) {
}
client.getAcsResponse(deleteFaceDbRequest);
removeFaceDBCache(dbName);
} catch (ClientException e) {
log.error("删除人脸数据库失败!", e);
}
}
@Override
public void createFaceDB(String dbName) {
try {
CreateFaceDbRequest request = new CreateFaceDbRequest();
request.setName(dbName);
IAcsClient client = getClient();
client.getAcsResponse(request);
addFaceDBCache(dbName);
} catch (ClientException e) {
log.error("阿里云添加人脸数据库失败!", e);
}
}
@Override
public void assureFaceDB(String dbName) {
if (redisTemplate.hasKey(FaceConstant.FACE_DB_NAME_PFX + dbName)) {
return;
}
try {
long offset = 0;
List<ListFaceDbsResponse.Data.DbListItem> dbList = new ArrayList<>();
IAcsClient client = getClient();
while (true) {
ListFaceDbsRequest request = new ListFaceDbsRequest();
request.setLimit(200L);
request.setOffset(offset);
ListFaceDbsResponse response = client.getAcsResponse(request);
List<ListFaceDbsResponse.Data.DbListItem> list = response.getData().getDbList();
if (list == null || list.isEmpty()) {
break;
} else {
dbList.addAll(list);
offset += list.size();
}
}
boolean mismatch;
if (dbList.isEmpty()) {
clearFaceDBCache();
mismatch = true;
} else {
mismatch = dbList.stream().peek(item -> {
redisTemplate.opsForValue().set(FaceConstant.FACE_DB_NAME_PFX + dbName, "1");
}).noneMatch(db -> db.getName().equals(dbName));
}
if (mismatch) {
createFaceDB(dbName);
}
} catch (ClientException e) {
log.error("阿里云确保人脸数据库失败!", e);
}
}
@Override
public String uploadFile(MultipartFile file, Long userId) {
if (file.isEmpty()) {
@@ -572,8 +451,28 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
}
@Override
public boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName) {
if (redisTemplate.hasKey(FaceConstant.FACE_DB_NAME_PFX + dbName)) {
return true;
}
try {
if (!faceBodyAdapter.assureFaceDb(dbName)) {
log.info("创建人脸库失败!");
removeFaceDBCache(dbName);
return false;
}
addFaceDBCache(dbName);
return true;
} catch (Exception e) {
log.error("创建人脸库失败!", e);
removeFaceDBCache(dbName);
return false;
}
}
public void addFaceDBCache(String dbName) {
redisTemplate.opsForValue().set(FaceConstant.FACE_DB_NAME_PFX + dbName, "1");
redisTemplate.opsForValue().set(FaceConstant.FACE_DB_NAME_PFX + dbName, "1", 2, TimeUnit.HOURS);
}
public Boolean removeFaceDBCache(String dbName) {