mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-15 22:57:26 +00:00
1、人脸模块:人脸查询支持 向量数据库Milvus 和 SQLite
2、人脸模块:FaceNet人脸模型也支持人脸注册,查询等功能 3、人脸模块:Seetaface6 自动下载人脸库 4、人脸模块:Seetaface6解决依赖库重复下载问题 5、人脸模块:支持手动加载人脸库 6、人脸模块:人脸识别相关功能支持更多参数
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package cn.smartjavaai.face.vector.config;
|
||||
|
||||
|
||||
import cn.smartjavaai.face.enums.IdStrategy;
|
||||
import cn.smartjavaai.face.enums.VectorDBType;
|
||||
import io.milvus.param.IndexType;
|
||||
import io.milvus.param.MetricType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Milvus配置类
|
||||
* @author smartjavaai
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MilvusConfig extends VectorDBConfig {
|
||||
|
||||
/**
|
||||
* 服务器地址
|
||||
*/
|
||||
private String host = "localhost";
|
||||
|
||||
/**
|
||||
* 服务器端口
|
||||
*/
|
||||
private int port = 19530;
|
||||
|
||||
/**
|
||||
* 索引类型
|
||||
*/
|
||||
private IndexType indexType = IndexType.IVF_FLAT;
|
||||
|
||||
/**
|
||||
* 聚类数量,用于IVF索引
|
||||
*/
|
||||
private int nlist = 1024;
|
||||
|
||||
/**
|
||||
* 向量维度
|
||||
*/
|
||||
private int dimension;
|
||||
|
||||
/**
|
||||
* ID策略
|
||||
*/
|
||||
private IdStrategy idStrategy = IdStrategy.AUTO;
|
||||
|
||||
|
||||
/**
|
||||
* 相似度计算方式
|
||||
*/
|
||||
private MetricType metricType;
|
||||
|
||||
/**
|
||||
* 集合名称
|
||||
*/
|
||||
private String collectionName;
|
||||
|
||||
/**
|
||||
* 是否使用内存缓存
|
||||
*/
|
||||
private boolean useMemoryCache = true;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public MilvusConfig() {
|
||||
setType(VectorDBType.MILVUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param host 服务器地址
|
||||
* @param port 服务器端口
|
||||
*/
|
||||
public MilvusConfig(String host, int port) {
|
||||
this();
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.smartjavaai.face.vector.config;
|
||||
|
||||
import cn.smartjavaai.face.enums.SimilarityType;
|
||||
import cn.smartjavaai.face.enums.VectorDBType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/5/31
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SQLiteConfig extends VectorDBConfig {
|
||||
|
||||
/**
|
||||
* 数据库路径(包含文件名称)
|
||||
*/
|
||||
private String dbPath;
|
||||
|
||||
/**
|
||||
* 相似度计算方式
|
||||
*/
|
||||
private SimilarityType similarityType;
|
||||
|
||||
|
||||
public SQLiteConfig() {
|
||||
setType(VectorDBType.SQLITE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.smartjavaai.face.vector.config;
|
||||
|
||||
import cn.smartjavaai.face.enums.VectorDBType;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 向量数据库基础配置
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public abstract class VectorDBConfig {
|
||||
|
||||
/**
|
||||
* 向量数据库类型
|
||||
*/
|
||||
private VectorDBType type;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.smartjavaai.face.vector.constant;
|
||||
|
||||
/**
|
||||
* 向量数据库常量类
|
||||
* @author dwj
|
||||
*/
|
||||
public class VectorDBConstants {
|
||||
/**
|
||||
* 字段名称常量
|
||||
*/
|
||||
public static class FieldNames {
|
||||
/** ID字段名 */
|
||||
public static final String ID_FIELD = "id";
|
||||
/** 向量字段名 */
|
||||
public static final String VECTOR_FIELD = "vector";
|
||||
/** 元数据字段名 */
|
||||
public static final String METADATA_FIELD = "metadata";
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认配置常量
|
||||
*/
|
||||
public static class Defaults {
|
||||
/** 默认搜索探针数 */
|
||||
public static final int DEFAULT_SEARCH_PARAM_NPROBE = 10;
|
||||
/** 默认向量维度 */
|
||||
public static final int DEFAULT_VECTOR_DIMENSION = 512;
|
||||
/** 默认元数据最大长度 */
|
||||
public static final int DEFAULT_METADATA_MAX_LENGTH = 32 * 1024;
|
||||
/** 默认ID字段最大长度 */
|
||||
public static final int DEFAULT_ID_MAX_LENGTH = 36;
|
||||
|
||||
/**
|
||||
* 默认集合名称
|
||||
*/
|
||||
public static final String DEFAULT_COLLECTION_NAME = "face";
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索参数常量
|
||||
*/
|
||||
public static class SearchParams {
|
||||
/** 默认相似度阈值 */
|
||||
public static final float DEFAULT_SIMILARITY_THRESHOLD = 0.7f;
|
||||
/** 默认返回TOP-K结果数 */
|
||||
public static final int DEFAULT_TOP_K = 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
package cn.smartjavaai.face.vector.core;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceSearchParams;
|
||||
import cn.smartjavaai.face.enums.IdStrategy;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.vector.config.MilvusConfig;
|
||||
import cn.smartjavaai.face.vector.constant.VectorDBConstants;
|
||||
import cn.smartjavaai.face.vector.entity.FaceVector;
|
||||
import cn.smartjavaai.common.entity.FaceSearchResult;
|
||||
import cn.smartjavaai.face.vector.exception.VectorDBException;
|
||||
import io.milvus.client.MilvusServiceClient;
|
||||
import io.milvus.grpc.*;
|
||||
import io.milvus.param.*;
|
||||
import io.milvus.param.collection.*;
|
||||
import io.milvus.param.dml.*;
|
||||
import io.milvus.param.index.CreateIndexParam;
|
||||
import io.milvus.response.QueryResultsWrapper;
|
||||
import io.milvus.response.SearchResultsWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Milvus向量数据库客户端实现
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class MilvusClient implements VectorDBClient {
|
||||
|
||||
private final MilvusConfig config;
|
||||
private MilvusServiceClient serviceClient;
|
||||
|
||||
private String collectionName;
|
||||
|
||||
/**
|
||||
* 是否初始化完毕
|
||||
*/
|
||||
private boolean isInit;
|
||||
|
||||
public MilvusClient(MilvusConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
ConnectParam connectParam = ConnectParam.newBuilder()
|
||||
.withHost(config.getHost())
|
||||
.withPort(config.getPort())
|
||||
.build();
|
||||
serviceClient = new MilvusServiceClient(connectParam);
|
||||
collectionName = StringUtils.isNotBlank(config.getCollectionName()) ? config.getCollectionName() : VectorDBConstants.Defaults.DEFAULT_COLLECTION_NAME;
|
||||
createCollection(collectionName, config.getDimension());
|
||||
if(config.isUseMemoryCache()){
|
||||
// 加载集合到内存
|
||||
loadFaceFeatures();
|
||||
}
|
||||
isInit = true;
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("初始化Milvus客户端失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCollection(String collectionName, int dimension) {
|
||||
try {
|
||||
if (hasCollection(collectionName)) {
|
||||
log.debug("集合已存在:{}", collectionName);
|
||||
return;
|
||||
}
|
||||
// 创建集合字段
|
||||
FieldType idField = null;
|
||||
|
||||
switch (config.getIdStrategy()){
|
||||
case AUTO://自动生成ID
|
||||
idField = FieldType.newBuilder()
|
||||
.withName(VectorDBConstants.FieldNames.ID_FIELD)
|
||||
.withDataType(DataType.Int64)
|
||||
.withPrimaryKey(true)
|
||||
.withAutoID(true)
|
||||
.build();
|
||||
break;
|
||||
case CUSTOM://自定义ID
|
||||
idField = FieldType.newBuilder()
|
||||
.withName(VectorDBConstants.FieldNames.ID_FIELD)
|
||||
.withDataType(DataType.VarChar)
|
||||
.withMaxLength(VectorDBConstants.Defaults.DEFAULT_ID_MAX_LENGTH)
|
||||
.withPrimaryKey(true)
|
||||
.withAutoID(false)
|
||||
.build();
|
||||
break;
|
||||
}
|
||||
|
||||
FieldType vectorField = FieldType.newBuilder()
|
||||
.withName(VectorDBConstants.FieldNames.VECTOR_FIELD)
|
||||
.withDataType(DataType.FloatVector)
|
||||
.withDimension(dimension)
|
||||
.build();
|
||||
|
||||
FieldType metadataField = FieldType.newBuilder()
|
||||
.withName(VectorDBConstants.FieldNames.METADATA_FIELD)
|
||||
.withDataType(DataType.VarChar)
|
||||
.withMaxLength(VectorDBConstants.Defaults.DEFAULT_METADATA_MAX_LENGTH)
|
||||
.withNullable(true)//允许为空值
|
||||
.build();
|
||||
|
||||
// 创建集合参数
|
||||
CreateCollectionParam createCollectionParam = CreateCollectionParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withDescription("人脸特征向量集合")
|
||||
.addFieldType(idField)
|
||||
.addFieldType(vectorField)
|
||||
.addFieldType(metadataField)
|
||||
.build();
|
||||
|
||||
R<RpcStatus> response = serviceClient.createCollection(createCollectionParam);
|
||||
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 创建集合失败:" + response.getMessage());
|
||||
}
|
||||
|
||||
log.debug("创建集合成功");
|
||||
|
||||
// 创建索引
|
||||
IndexType indexType = IndexType.IVF_FLAT;
|
||||
if (config.getIndexType() != null) {
|
||||
indexType = config.getIndexType();
|
||||
}
|
||||
|
||||
CreateIndexParam indexParam = CreateIndexParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withFieldName(VectorDBConstants.FieldNames.VECTOR_FIELD)
|
||||
.withIndexType(indexType)
|
||||
.withMetricType(config.getMetricType())
|
||||
.withExtraParam(String.format("{\"nlist\":%d}", config.getNlist()))
|
||||
.withSyncMode(Boolean.TRUE)//调用方法后等待 Milvus 执行完成
|
||||
.build();
|
||||
|
||||
R<RpcStatus> createIndexResponse = serviceClient.createIndex(indexParam);
|
||||
|
||||
if (createIndexResponse.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 创建索引失败:" + createIndexResponse.getMessage());
|
||||
}
|
||||
|
||||
log.debug("创建索引成功");
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("创建Milvus集合失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropCollection(String collectionName) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
if (hasCollection(collectionName)) {
|
||||
R<RpcStatus> response = serviceClient.dropCollection(DropCollectionParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.build());
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 删除集合失败:" + response.getMessage());
|
||||
}
|
||||
isInit = false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("删除Milvus集合失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCollection(String collectionName) {
|
||||
|
||||
try {
|
||||
R<Boolean> response = serviceClient.hasCollection(
|
||||
HasCollectionParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.build()
|
||||
);
|
||||
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 查询失败:" + response.getMessage());
|
||||
}
|
||||
return Boolean.TRUE.equals(response.getData());
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("检查Milvus集合是否保存失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String insert(FaceVector faceVector) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
//验证
|
||||
if(faceVector == null){
|
||||
throw new VectorDBException("插入数据失败:faceVector不能为空");
|
||||
}
|
||||
if(faceVector.getVector() == null || faceVector.getVector().length == 0){
|
||||
throw new VectorDBException("插入数据失败:vector不能为空");
|
||||
}
|
||||
//自定义ID
|
||||
if(config.getIdStrategy() == IdStrategy.CUSTOM){
|
||||
if(StringUtils.isBlank(faceVector.getId())){
|
||||
throw new VectorDBException("插入数据失败:ID生成策略-自定义ID,id不能为空");
|
||||
}
|
||||
}
|
||||
// 转 float[] 为 List<Float>
|
||||
List<Float> vectorList = new ArrayList<>();
|
||||
for (float v : faceVector.getVector()) {
|
||||
vectorList.add(v);
|
||||
}
|
||||
|
||||
List<List<Float>> vectors = Collections.singletonList(vectorList);
|
||||
//List<String> metadataList = Collections.singletonList(faceVector.getMetadata());
|
||||
|
||||
List<String> metadataList = Optional.ofNullable(faceVector.getMetadata())
|
||||
.map(Collections::singletonList)
|
||||
.orElse(Collections.emptyList());
|
||||
List<InsertParam.Field> fields = null;
|
||||
switch (config.getIdStrategy()){
|
||||
case AUTO://自动生成ID
|
||||
fields = Arrays.asList(
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.VECTOR_FIELD).values(vectors).build(),
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.METADATA_FIELD).values(metadataList).build()
|
||||
);
|
||||
break;
|
||||
case CUSTOM://自定义ID
|
||||
List<String> ids = Collections.singletonList(faceVector.getId());
|
||||
fields = Arrays.asList(
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.ID_FIELD).values(ids).build(),
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.VECTOR_FIELD).values(vectors).build(),
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.METADATA_FIELD).values(metadataList).build()
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
InsertParam insertParam = InsertParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withFields(fields)
|
||||
.build();
|
||||
|
||||
R<MutationResult> response = serviceClient.insert(insertParam);
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("插入失败: " + response.getMessage());
|
||||
}
|
||||
|
||||
List<Long> autoIds = response.getData().getIDs().getIntId().getDataList();
|
||||
|
||||
|
||||
return config.getIdStrategy() == IdStrategy.AUTO ? String.valueOf(autoIds.get(0)) : faceVector.getId();
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("插入Milvus向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsert(FaceVector faceVector) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
if(config.getIdStrategy() == IdStrategy.AUTO){
|
||||
throw new VectorDBException("idStrategy为AUTO时,不支持更新操作");
|
||||
}
|
||||
//验证
|
||||
if(faceVector == null){
|
||||
throw new VectorDBException("更新数据失败:faceVector不能为空");
|
||||
}
|
||||
if(faceVector.getVector() == null || faceVector.getVector().length == 0){
|
||||
throw new VectorDBException("更新数据失败:vector不能为空");
|
||||
}
|
||||
if(StringUtils.isBlank(faceVector.getId())){
|
||||
throw new VectorDBException("更新数据失败:id不能为空");
|
||||
}
|
||||
|
||||
// 转换向量为 List<Float>
|
||||
List<Float> vectorList = new ArrayList<>();
|
||||
for (float v : faceVector.getVector()) {
|
||||
vectorList.add(v);
|
||||
}
|
||||
|
||||
List<List<Float>> vectors = Collections.singletonList(vectorList);
|
||||
List<String> metadataList = Optional.ofNullable(faceVector.getMetadata())
|
||||
.map(Collections::singletonList)
|
||||
.orElse(Collections.emptyList());
|
||||
|
||||
// 准备字段列表
|
||||
List<UpsertParam.Field> fields = new ArrayList<>();
|
||||
|
||||
fields.add(UpsertParam.Field.builder()
|
||||
.name(VectorDBConstants.FieldNames.ID_FIELD)
|
||||
.values(Collections.singletonList(faceVector.getId()))
|
||||
.build());
|
||||
|
||||
// 添加向量和元数据字段
|
||||
fields.add(UpsertParam.Field.builder()
|
||||
.name(VectorDBConstants.FieldNames.VECTOR_FIELD)
|
||||
.values(vectors)
|
||||
.build());
|
||||
|
||||
fields.add(UpsertParam.Field.builder()
|
||||
.name(VectorDBConstants.FieldNames.METADATA_FIELD)
|
||||
.values(metadataList)
|
||||
.build());
|
||||
|
||||
// 构建Upsert参数
|
||||
UpsertParam upsertParam = UpsertParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withFields(fields)
|
||||
.build();
|
||||
|
||||
// 执行Upsert操作
|
||||
R<MutationResult> response = serviceClient.upsert(upsertParam);
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Upsert操作失败: " + response.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("Milvus Upsert操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> insertBatch(List<FaceVector> faceVectors) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
List<String> ids = faceVectors.stream()
|
||||
.map(FaceVector::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
List<List<Float>> vectors = new ArrayList<>();
|
||||
for (FaceVector fv : faceVectors) {
|
||||
List<Float> list = new ArrayList<>();
|
||||
for (float f : fv.getVector()) {
|
||||
list.add(f);
|
||||
}
|
||||
vectors.add(list);
|
||||
}
|
||||
|
||||
List<String> metadataList = faceVectors.stream()
|
||||
.map(FaceVector::getMetadata)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
InsertParam insertParam = InsertParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withFields(Arrays.asList(
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.ID_FIELD).values(ids).build(),
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.VECTOR_FIELD).values(vectors).build(),
|
||||
InsertParam.Field.builder().name(VectorDBConstants.FieldNames.METADATA_FIELD).values(metadataList).build()
|
||||
))
|
||||
.build();
|
||||
|
||||
R<MutationResult> insertResult = serviceClient.insert(insertParam);
|
||||
return ids;
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("批量插入Milvus向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
String expr = String.format("%s == \"%s\"", VectorDBConstants.FieldNames.ID_FIELD, id);
|
||||
DeleteParam deleteParam = DeleteParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withExpr(expr)
|
||||
.build();
|
||||
|
||||
R<MutationResult> response = serviceClient.delete(deleteParam);
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("删除操作失败: " + response.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("删除Milvus向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBatch(List<String> ids) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
// 构建IN表达式: id in ["id1", "id2", ...]
|
||||
StringBuilder expr = new StringBuilder(VectorDBConstants.FieldNames.ID_FIELD + " in [");
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
expr.append(ids.get(i)); // 不加引号
|
||||
if (i < ids.size() - 1) {
|
||||
expr.append(", ");
|
||||
}
|
||||
}
|
||||
expr.append("]");
|
||||
DeleteParam deleteParam = DeleteParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withExpr(expr.toString())
|
||||
.build();
|
||||
|
||||
R<MutationResult> response = serviceClient.delete(deleteParam);
|
||||
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("删除操作失败: " + response.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("批量删除Milvus向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FaceSearchResult> search(float[] queryVector, FaceSearchParams faceSearchParams) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
// 1. 包装查询向量
|
||||
List<List<Float>> vectors = new ArrayList<>();
|
||||
List<Float> floatList = new ArrayList<>();
|
||||
for (float f : queryVector) {
|
||||
floatList.add(f);
|
||||
}
|
||||
vectors.add(floatList);
|
||||
|
||||
// 2. 构造搜索参数
|
||||
SearchParam searchParam = SearchParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withVectorFieldName(VectorDBConstants.FieldNames.VECTOR_FIELD)
|
||||
.withTopK(faceSearchParams.getTopK())
|
||||
.withMetricType(config.getMetricType())
|
||||
.withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD))
|
||||
.withVectors(vectors)
|
||||
.withParams("{\"nprobe\": 10}")//和nlist有关
|
||||
.build();
|
||||
R<SearchResults> resp = serviceClient.search(searchParam);
|
||||
if (resp.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 查询失败: " + resp.getMessage());
|
||||
}
|
||||
|
||||
SearchResults results = resp.getData();
|
||||
SearchResultsWrapper wrapper = new SearchResultsWrapper(results.getResults());
|
||||
|
||||
// 3. 获取字段数据(FieldData)
|
||||
List<SearchResultsWrapper.IDScore> scores = wrapper.getIDScore(0); // 默认只有一条 query 向量
|
||||
|
||||
|
||||
List<FaceSearchResult> finalResults = new ArrayList<>();
|
||||
for (int i = 0; i < scores.size(); i++) {
|
||||
SearchResultsWrapper.IDScore score = scores.get(i);
|
||||
float similarity = score.getScore();
|
||||
if (faceSearchParams.getNormalizeSimilarity()) {
|
||||
// 将分数转换为相似度
|
||||
similarity = FaceUtils.convertScoreToSimilarity(config.getMetricType().name(), score.getScore());
|
||||
}
|
||||
if (similarity >= faceSearchParams.getThreshold()) {
|
||||
// 获取 Metadata
|
||||
String metadata = wrapper.getFieldData(VectorDBConstants.FieldNames.METADATA_FIELD, i).get(0).toString();
|
||||
// 获取 ID
|
||||
String id = wrapper.getFieldData(VectorDBConstants.FieldNames.ID_FIELD, i).get(0).toString();
|
||||
finalResults.add(new FaceSearchResult(id, similarity, metadata));
|
||||
}
|
||||
}
|
||||
return finalResults;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("搜索 Milvus 向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(String collectionName) {
|
||||
try {
|
||||
if (serviceClient == null){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
R<QueryResults> response = serviceClient.query(
|
||||
QueryParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withOutFields(Collections.singletonList("count(*)"))
|
||||
.build()
|
||||
);
|
||||
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 查询失败,msg: " + response.getMessage());
|
||||
}
|
||||
|
||||
List<FieldData> fields = response.getData().getFieldsDataList();
|
||||
if (fields.isEmpty()) {
|
||||
throw new VectorDBException("Milvus 返回空字段数据");
|
||||
}
|
||||
|
||||
FieldData countField = fields.get(0);
|
||||
List<Long> countValues = countField.getScalars().getLongData().getDataList();
|
||||
if (countValues.isEmpty()) {
|
||||
throw new VectorDBException("Milvus count(*) 返回为空");
|
||||
}
|
||||
|
||||
return countValues.get(0); // count(*) 查询的结果
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("获取 Milvus 集合数量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (serviceClient != null) {
|
||||
serviceClient.close();
|
||||
isInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseCollection(String collectionName) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
R<RpcStatus> response = serviceClient.releaseCollection(ReleaseCollectionParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.build());
|
||||
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus releaseCollection失败,msg: " + response.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FaceSearchResult getById(String id) {
|
||||
try {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("Milvus未初始化完毕");
|
||||
}
|
||||
// 构造搜索参数
|
||||
SearchParam searchParam = SearchParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withExpr(VectorDBConstants.FieldNames.ID_FIELD + " == " + id)
|
||||
.withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD))
|
||||
.build();
|
||||
|
||||
|
||||
// 5. 执行查询
|
||||
R<QueryResults> response = serviceClient.query(
|
||||
QueryParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.withExpr(VectorDBConstants.FieldNames.ID_FIELD + " == " + id)
|
||||
.withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD))
|
||||
.build()
|
||||
);
|
||||
|
||||
// 处理响应
|
||||
if (response.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("查询失败: " + response.getMessage());
|
||||
}
|
||||
|
||||
QueryResultsWrapper wrapper = new QueryResultsWrapper(response.getData());
|
||||
List<QueryResultsWrapper.RowRecord> records = wrapper.getRowRecords();
|
||||
if (records.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 提取第一条记录
|
||||
QueryResultsWrapper.RowRecord row = records.get(0);
|
||||
return new FaceSearchResult(id, 1,(String)row.get(VectorDBConstants.FieldNames.METADATA_FIELD));
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("搜索 Milvus 向量失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFaceFeatures() {
|
||||
// 加载集合到内存
|
||||
R<RpcStatus> loadResponse = serviceClient.loadCollection(LoadCollectionParam.newBuilder()
|
||||
.withCollectionName(collectionName)
|
||||
.build());
|
||||
if (loadResponse.getStatus() != R.Status.Success.getCode()) {
|
||||
throw new VectorDBException("Milvus 加载集合到内存失败:" + loadResponse.getMessage());
|
||||
}
|
||||
long count = count(collectionName);
|
||||
log.debug("加载集合到内存成功,人脸数量:{}", count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseFaceFeatures() {
|
||||
releaseCollection(collectionName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package cn.smartjavaai.face.vector.core;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.face.dao.FaceDao;
|
||||
import cn.smartjavaai.face.entity.FaceSearchParams;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.utils.SimilarityUtil;
|
||||
import cn.smartjavaai.face.vector.config.SQLiteConfig;
|
||||
import cn.smartjavaai.face.vector.entity.FaceVector;
|
||||
import cn.smartjavaai.common.entity.FaceSearchResult;
|
||||
import cn.smartjavaai.face.vector.exception.VectorDBException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class SQLiteClient implements VectorDBClient {
|
||||
|
||||
private final FaceDao faceDao;
|
||||
private final List<FaceVector> memoryIndex = new CopyOnWriteArrayList<>();
|
||||
private int featureDimension; // 维度
|
||||
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(4);
|
||||
|
||||
private SQLiteConfig config;
|
||||
|
||||
/**
|
||||
* 是否初始化完毕
|
||||
*/
|
||||
private boolean isInit;
|
||||
|
||||
public SQLiteClient(SQLiteConfig config) {
|
||||
this.config = config;
|
||||
String dbPath = config.getDbPath();
|
||||
//如果未指定db路径,则使用默认路径
|
||||
if(StringUtils.isBlank(config.getDbPath())){
|
||||
dbPath = Config.getCachePath() + File.separator + "face.db";
|
||||
log.debug("使用默认SQLite人脸库路径: {}", dbPath);
|
||||
}
|
||||
this.faceDao = FaceDao.getInstance(dbPath);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
// 加载所有特征到内存
|
||||
loadAllFeaturesToMemory();
|
||||
isInit = true;
|
||||
log.debug("SQLiteVectorDB initialized with {} faces", memoryIndex.size());
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 以下方法保持接口兼容但忽略collectionName参数
|
||||
@Override
|
||||
public void createCollection(String collectionName, int dimension) {
|
||||
this.featureDimension = dimension;
|
||||
log.debug("特征维度设置为: {}", dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropCollection(String collectionName) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
clearAllData();
|
||||
log.warn("所有数据已被清空");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCollection(String collectionName) {
|
||||
throw new UnsupportedOperationException("Sqlite 不支持此操作");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String insert(FaceVector faceVector) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
return insertBatch(Collections.singletonList(faceVector)).get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsert(FaceVector faceVector) {
|
||||
// if (faceVector.getId() != null) {
|
||||
// delete(faceVector.getId());
|
||||
// }
|
||||
insert(faceVector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> insertBatch(List<FaceVector> faceVectors) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
List<String> ids = new ArrayList<>();
|
||||
try {
|
||||
for (FaceVector faceVector : faceVectors) {
|
||||
String id = faceVector.getId() != null ?
|
||||
faceVector.getId() : IdUtil.simpleUUID();
|
||||
faceVector.setId(id);
|
||||
// 保存到数据库
|
||||
faceDao.insertOrUpdate(faceVector);
|
||||
// 添加到内存索引
|
||||
addToMemoryIndex(faceVector);
|
||||
ids.add(id);
|
||||
}
|
||||
log.debug("插入了 {} 个人脸向量", faceVectors.size());
|
||||
return ids;
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("批量插入失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
deleteBatch(Collections.singletonList(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBatch(List<String> ids) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
try {
|
||||
// 从数据库中删除
|
||||
boolean isSuccess = faceDao.deleteFace(ids.toArray(new String[0]));
|
||||
// 从内存中删除
|
||||
memoryIndex.removeIf(v -> ids.contains(v.getId()));
|
||||
if(!isSuccess){
|
||||
throw new VectorDBException("删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("批量删除失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FaceSearchResult> search(float[] queryVector, FaceSearchParams faceSearchParams) {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
if (memoryIndex.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 并行计算相似度
|
||||
List<CompletableFuture<FaceSearchResult>> futures = memoryIndex.stream()
|
||||
.map(vector -> CompletableFuture.supplyAsync(() -> {
|
||||
float similarity = SimilarityUtil.calculate(queryVector, vector.getVector(), config.getSimilarityType(), faceSearchParams.getNormalizeSimilarity());
|
||||
return similarity >= faceSearchParams.getThreshold() ?
|
||||
new FaceSearchResult(vector.getId(), similarity, vector.getMetadata()) :
|
||||
null;
|
||||
}, executor))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 收集结果并过滤null
|
||||
List<FaceSearchResult> allResults = futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 获取TopK结果
|
||||
return allResults.stream()
|
||||
.sorted(Comparator.comparingDouble(FaceSearchResult::getSimilarity).reversed())
|
||||
.limit(faceSearchParams.getTopK())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(String collectionName) {
|
||||
return memoryIndex.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// ============= 私有辅助方法 =============
|
||||
|
||||
private void loadAllFeaturesToMemory() {
|
||||
try {
|
||||
int pageSize = 1000;
|
||||
int page = 0;
|
||||
while (true) {
|
||||
List<FaceVector> batch = faceDao.findFace(page, pageSize);
|
||||
if (CollectionUtils.isEmpty(batch)) {
|
||||
break;
|
||||
}
|
||||
for (FaceVector vector : batch) {
|
||||
addToMemoryIndex(vector);
|
||||
}
|
||||
page++;
|
||||
}
|
||||
log.debug("从数据库加载了 {} 个特征向量到内存", memoryIndex.size());
|
||||
} catch (Exception e) {
|
||||
throw new VectorDBException("加载特征到内存失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addToMemoryIndex(FaceVector faceVector) {
|
||||
memoryIndex.add(faceVector);
|
||||
}
|
||||
|
||||
private void clearAllData() {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
try {
|
||||
faceDao.deleteAll();
|
||||
memoryIndex.clear();
|
||||
} catch (Exception e) {
|
||||
log.error("清空数据库失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceSearchResult getById(String id) {
|
||||
try {
|
||||
FaceVector faceVector = faceDao.findById(id);
|
||||
if(faceVector != null){
|
||||
return new FaceSearchResult(faceVector.getId(), 1.0f, faceVector.getMetadata());
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException | RuntimeException | ClassNotFoundException e ) {
|
||||
throw new VectorDBException("SQLite查询异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFaceFeatures() {
|
||||
// 加载所有特征到内存
|
||||
loadAllFeaturesToMemory();
|
||||
isInit = true;
|
||||
log.debug("SQLiteVectorDB load success {} faces", memoryIndex.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseFaceFeatures() {
|
||||
if (!isInit){
|
||||
throw new VectorDBException("人脸库未加载完毕");
|
||||
}
|
||||
memoryIndex.clear();
|
||||
isInit = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.smartjavaai.face.vector.core;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceSearchParams;
|
||||
import cn.smartjavaai.face.vector.entity.FaceVector;
|
||||
import cn.smartjavaai.common.entity.FaceSearchResult;
|
||||
import cn.smartjavaai.face.vector.exception.VectorDBException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 向量数据库客户端接口
|
||||
* 定义与向量数据库交互的通用操作
|
||||
* @author dwj
|
||||
*/
|
||||
public interface VectorDBClient extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* 初始化连接和集合
|
||||
* @throws VectorDBException 初始化异常
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* 创建集合
|
||||
* @param collectionName 集合名称
|
||||
* @param dimension 向量维度
|
||||
*/
|
||||
void createCollection(String collectionName, int dimension);
|
||||
|
||||
/**
|
||||
* 删除集合
|
||||
* @param collectionName 集合名称
|
||||
*/
|
||||
void dropCollection(String collectionName);
|
||||
|
||||
/**
|
||||
* 检查集合是否存在
|
||||
* @param collectionName 集合名称
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean hasCollection(String collectionName);
|
||||
|
||||
|
||||
/**
|
||||
* 插入人脸向量
|
||||
* @param faceVector
|
||||
* @return
|
||||
*/
|
||||
String insert(FaceVector faceVector);
|
||||
|
||||
/**
|
||||
* 更新或新增人脸向量
|
||||
* @param faceVector
|
||||
*/
|
||||
void upsert(FaceVector faceVector);
|
||||
|
||||
/**
|
||||
* 批量插入人脸向量
|
||||
* @param faceVectors
|
||||
* @return
|
||||
*/
|
||||
List<String> insertBatch(List<FaceVector> faceVectors);
|
||||
|
||||
/**
|
||||
* 根据ID删除向量
|
||||
* @param id
|
||||
*/
|
||||
void delete(String id);
|
||||
|
||||
/**
|
||||
* 批量删除向量
|
||||
* @param ids
|
||||
*/
|
||||
void deleteBatch(List<String> ids);
|
||||
|
||||
/**
|
||||
* 搜索相似人脸
|
||||
* @param queryVector
|
||||
* @param faceSearchParams
|
||||
* @return
|
||||
*/
|
||||
List<FaceSearchResult> search(float[] queryVector, FaceSearchParams faceSearchParams);
|
||||
|
||||
/**
|
||||
* 获取集合中的向量数量
|
||||
* @param collectionName 集合名称
|
||||
* @return 向量数量
|
||||
*/
|
||||
long count(String collectionName);
|
||||
|
||||
/**
|
||||
* 关闭连接
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定ID的向量
|
||||
* @return
|
||||
*/
|
||||
FaceSearchResult getById(String id);
|
||||
|
||||
/**
|
||||
* 加载人脸特征到内存
|
||||
*/
|
||||
void loadFaceFeatures();
|
||||
|
||||
/**
|
||||
* 释放人脸特征缓存
|
||||
*/
|
||||
void releaseFaceFeatures();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.smartjavaai.face.vector.core;
|
||||
|
||||
|
||||
|
||||
import cn.smartjavaai.face.vector.config.MilvusConfig;
|
||||
import cn.smartjavaai.face.vector.config.SQLiteConfig;
|
||||
import cn.smartjavaai.face.vector.config.VectorDBConfig;
|
||||
import cn.smartjavaai.face.vector.exception.VectorDBException;
|
||||
|
||||
/**
|
||||
* 向量数据库工厂类
|
||||
* 用于创建不同类型的向量数据库客户端
|
||||
* @author dwj
|
||||
*/
|
||||
public class VectorDBFactory {
|
||||
|
||||
private VectorDBFactory() {
|
||||
// 私有构造函数,防止实例化
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建向量数据库客户端
|
||||
* @param config 配置信息
|
||||
* @return 向量数据库客户端
|
||||
* @throws VectorDBException 创建异常
|
||||
*/
|
||||
public static VectorDBClient createClient(VectorDBConfig config) {
|
||||
if (config == null) {
|
||||
throw new VectorDBException("配置不能为空");
|
||||
}
|
||||
VectorDBClient client;
|
||||
|
||||
switch (config.getType()) {
|
||||
case SQLITE:
|
||||
if (!(config instanceof SQLiteConfig)) {
|
||||
throw new VectorDBException("SQLite类型需要SQLiteConfig配置");
|
||||
}
|
||||
client = new SQLiteClient((SQLiteConfig) config);
|
||||
break;
|
||||
case MILVUS:
|
||||
if (!(config instanceof MilvusConfig)) {
|
||||
throw new VectorDBException("Milvus类型需要MilvusConfig配置");
|
||||
}
|
||||
client = new MilvusClient((MilvusConfig) config);
|
||||
break;
|
||||
// 未来可以在这里添加其他向量数据库的支持
|
||||
default:
|
||||
throw new VectorDBException("不支持的向量数据库类型: " + config.getType());
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.smartjavaai.face.vector.entity;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 人脸向量实体类
|
||||
* @author smartjavaai
|
||||
*/
|
||||
@Data
|
||||
public class FaceVector {
|
||||
|
||||
/**
|
||||
* 向量ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 人脸特征向量
|
||||
*/
|
||||
private float[] vector;
|
||||
|
||||
/**
|
||||
* 元数据,可以存储人脸相关的其他信息(JSON格式)
|
||||
*/
|
||||
private String metadata;
|
||||
|
||||
/**
|
||||
* 默认构造函数
|
||||
*/
|
||||
public FaceVector() {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param vector 人脸特征向量
|
||||
*/
|
||||
public FaceVector(float[] vector) {
|
||||
this();
|
||||
this.vector = vector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param vector 人脸特征向量
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public FaceVector(float[] vector, String metadata) {
|
||||
this();
|
||||
this.vector = vector;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param id 向量ID
|
||||
* @param vector 人脸特征向量
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public FaceVector(String id, float[] vector, String metadata) {
|
||||
this.id = id;
|
||||
this.vector = vector;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.smartjavaai.face.vector.exception;
|
||||
|
||||
|
||||
/**
|
||||
* 向量数据库异常
|
||||
* @author smartjavaai
|
||||
*/
|
||||
public class VectorDBException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public VectorDBException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param message 异常信息
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
public VectorDBException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user