前言
今天其实首先想跟大家探讨下:微服务架构,分业务线了,接入第三方服务、包啥的是否自己定义一个stater更好?
一、starter是什么?
在 Spring Boot 中,Starter 是一种特殊的依赖模块,用于快速引入一整套相关的依赖和默认配置,以简化开发者的配置工作。
1.starter的核心作用
Starter 的本质是一个 Maven 或 Gradle 的依赖包,它:
-
封装了某种功能所需的所有依赖库;
-
提供了该功能的默认自动配置;
-
遵循 Spring Boot 的自动化配置规范(@Conditional 机制);
-
允许用户覆盖默认配置。
2.Starter 的内部结构(自定义时)
一个典型的自定义 Starter 包含两个模块:
-
starter 模块:用于提供自动配置类(通常使用 @Configuration + @Conditional),并注入 Bean。
-
starter-autoconfigure 模块:用于定义配置逻辑和默认行为。
也有时候直接打包成一个模块就行。
二、Starter自定义步骤
首先了解下starter的目录结构:
1.目前idea的New没看到有直接选择Starter这种(有说new project,maven勾选Create from archetype,试了下跟新建module差不多),但是问题不大,可以直接New Module,具体:
- 1.新建Module,设置好name、group、artifact等等,注意name最好是项目名称开头-spring-boot-starter-模块功能。比如我这里是接入虹软的人脸识别,所以名称:项目名称-spring-boot-starter-arcface
- 修改pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<-- 这个父级依赖,如果没有需要的在父级其实可以不要 -->
<parent>
<groupId>com.bsr.healthcheck</groupId>
<artifactId>bsr-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bsr-spring-boot-starter-arcface</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>虹软人脸扩展</description>
<url/>
<dependencies>
<!-- 自定义的通用包 -->
<dependency>
<groupId>com.bsr.healthcheck</groupId>
<artifactId>bsr-common</artifactId>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- 虹软人脸识别 -->
<dependency>
<groupId>com.arcsoft.face</groupId>
<artifactId>arcsoft-sdk-face</artifactId>
<version>3.0.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
</dependency>
</dependencies>
</project>
-
因为虹软的jar,目前在maven源上没有公开,都是申请的时候下载的,这里在目录下建一个libs,放jar、dll(这个后期应该是放到服务器上)
-
删除启动App、resources下的配置文件
-
resources下新建META_INF目录,目录下新建spring.factories,内容:org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.bsr.healthcheck.framework.face.config.ArcFaceAutoConfiguration
注意ArcFaceAutoConfiguration配置类目录要跟计划写的类对得上哦,最好是检查写好了,这里能点过去就ok的 -
src下建目录,写配置类、properties等
我的starter的目录:
2.具体代码
ArcFaceAutoConfiguration
/**
* 虹软人脸识别
*
* @author zwmac
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(ArcFaceProperties.class)
@ConditionalOnProperty(prefix = "arcface", name = "enabled", havingValue = "true", matchIfMissing = true)
public class ArcFaceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public FaceEngineWrapper faceEngineWrapper(ArcFaceProperties properties) {
if (SystemUtil.getOsInfo().isWindows()){
return new FaceEngineWrapper(
properties.getWinAppId(),
properties.getWinSdkKey(),
properties.getWinLibPath()
);
}
if(SystemUtil.getOsInfo().isLinux()){
return new FaceEngineWrapper(
properties.getAppId(),
properties.getSdkKey(),
properties.getLibPath()
);
}
if(SystemUtil.getOsInfo().isMac()){
log.info("虹软人脸识别不支持 Mac OS X");
//throw new IllegalStateException("Unsupported OS for ArcFace engine");
}
log.warn("系统未知,虹软人脸识别引擎初始化失败");
return null;
}
@Lazy
@Bean
@ConditionalOnMissingBean
public ArcFaceService arcFaceService() {
return new ArcFaceServiceImpl();
}
}
FaceEngineConfig
/**
* @author zwmac
*/
@Configuration
public class FaceEngineConfig {
@Value("${arcface.app-id}")
private String appId;
@Value("${arcface.sdk-key}")
private String sdkKey;
@Value("${arcface.lib-path}")
private String libPath;
@Bean
public FaceEngineWrapper faceEngineWrapper() {
return new FaceEngineWrapper(appId, sdkKey, libPath);
}
}
FaceEngineWrapper
/**
* @author zwmac
*/
@Lazy
@Slf4j
public class FaceEngineWrapper {
private FaceEngine faceEngine;
/**
* 初始化人脸引擎
* @param appId 授权id
* @param sdkKey 授权key
* @param libPath 脚本路径
*/
public FaceEngineWrapper(String appId, String sdkKey, String libPath) {
try {
faceEngine = new FaceEngine(libPath);
//激活引擎
int errorCode = faceEngine.activeOnline(appId, sdkKey);
if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
log.error("引擎激活失败");
}
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
errorCode = faceEngine.getActiveFileInfo(activeFileInfo);
if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
log.error("获取激活文件信息失败");
}
//引擎配置
EngineConfiguration engineConfiguration = new EngineConfiguration();
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
engineConfiguration.setDetectFaceMaxNum(10);
engineConfiguration.setDetectFaceScaleVal(16);
//功能配置
FunctionConfiguration functionConfiguration = new FunctionConfiguration();
functionConfiguration.setSupportAge(true);
functionConfiguration.setSupportFace3dAngle(true);
functionConfiguration.setSupportFaceDetect(true);
functionConfiguration.setSupportFaceRecognition(true);
functionConfiguration.setSupportGender(true);
functionConfiguration.setSupportLiveness(true);
functionConfiguration.setSupportIRLiveness(true);
engineConfiguration.setFunctionConfiguration(functionConfiguration);
//初始化引擎
errorCode = faceEngine.init(engineConfiguration);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.error("初始化引擎失败");
}
} catch (RuntimeException e) {
log.error("人脸引擎FaceEngine加载脚本异常,{}", e.getMessage(), e);
}
}
public FaceEngine getEngine() {
return this.faceEngine;
}
/*
public void destroy() {
faceEngine.unInit();
}*/
@PreDestroy
public void destroy() {
if (faceEngine != null) {
faceEngine.unInit();
log.info("人脸识别引擎已释放");
}
}
}
ArcFaceProperties
/**
* @author zwmac
*/
@ConfigurationProperties(prefix = "arcface")
@Data
public class ArcFaceProperties {
private String appId;
private String sdkKey;
private String libPath;
private String winAppId;
private String winSdkKey;
private String winLibPath;
private float matchThreshold = 0.8f;
private float rgbThreshold;
private float irThreshold;
}
ArcFaceService
/**
* @author zwmac
*/
public interface ArcFaceService {
/**
* 校验是否活体
* @param imgFile 照片文件File
* @return 是否活体
*/
boolean checkLive(File imgFile);
}
ArcFaceServiceImpl
/**
* @author zwmac
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ArcFaceServiceImpl implements ArcFaceService {
@Resource
private FaceEngineWrapper engineWrapper;
@Override
public boolean checkLive(File imgFile) {
boolean liveOk = false;
Assert.notNull(imgFile, "照片文件为空");
ImageInfo imageInfo = getRGBData(imgFile);
FaceEngine faceEngine = engineWrapper.getEngine();
//人脸检测
List<FaceInfo> faceInfoList = new ArrayList<>();
int errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
Assert.isTrue(errorCode == ErrorInfo.MOK.getValue(), "人脸检测失败");
//特征提取
FaceFeature faceFeature = new FaceFeature();
errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
Assert.isTrue(errorCode == ErrorInfo.MOK.getValue(), "人脸特征提取失败");
//开启活体检测
errorCode = faceEngine.setLivenessParam(0.5f, 0.7f);
Assert.isTrue(errorCode == ErrorInfo.MOK.getValue(), "设置活体测试失败");
List<LivenessInfo> livenessInfoList = new ArrayList<>();
errorCode = faceEngine.getLiveness(livenessInfoList);
Assert.isTrue(errorCode == ErrorInfo.MOK.getValue(), "活体检测失败");
int liveness = livenessInfoList.get(0).getLiveness();
log.info("活体liveness:{}", liveness);
liveOk = liveness == 1;
//释放引擎
engineWrapper.destroy();
return liveOk;
}
}
总结
- 这里的ArcFaceService在业务模块是可以懒加载的,好处就不解释了
- 目前starter里service就写了一个接口,需要的可以自己扩展
- 其实以前我也不赞成微服务还搞好多starter,但是最近想法有点改变。
– 首先是业务线细分后,可能多个服务都需要接入某项技术,用starter可以细粒度管理模块。
– 其次,让业务更加专注业务,第三方仅仅是第三方服务
– 最后,其实就是可以复用,但是不得不说开发工作量增加了 - 总结一句话:Spring Boot Starter 就是为了“开箱即用”而准备的功能集装箱。
– 通过引入一个 starter,就可以获得一整套某个功能的支持,开发者只需关注业务逻辑,而无需自己配置和管理底层依赖。
最后,希望能帮到大家,未来之所以迷人,是因为是机遇也是挑战,加油!