Compare commits
53 Commits
9e3608772f
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1634084aea | |||
| 5a411843b5 | |||
| 9ea1026daa | |||
| 9c09e8f004 | |||
| ab05bf79ce | |||
| 7fc9839038 | |||
| 9c08c6e7d4 | |||
| d8d53ebb22 | |||
| 693947c3a8 | |||
| d4e4923bb6 | |||
| 1707e0f4ca | |||
| ad4fb627f9 | |||
| fcc0390c80 | |||
| bdd33043b3 | |||
| d9d73434f6 | |||
| 237cf8dbe3 | |||
| d46490fc6b | |||
| 909ad5cc8c | |||
|
|
da3aa8d49c | ||
| 0875aca566 | |||
| 6dac863d45 | |||
| ee0b1ac055 | |||
| 67b48d209d | |||
| 71f44a6ec8 | |||
| 5f688ed98a | |||
| 557852ba40 | |||
| 4cda6728fe | |||
| 02c725de01 | |||
| 8095ead92c | |||
| a5f37fd4f0 | |||
| 23ca847b93 | |||
| 4331fe9919 | |||
| 4376ee65aa | |||
| 31734adb7e | |||
| 4da3a0670d | |||
| b018f819d1 | |||
| eb038419a1 | |||
| 78a3bceecd | |||
| 58c1975b05 | |||
| 906928ea0f | |||
| 3eb441d41c | |||
| 12f2374ccf | |||
| aa0dc12805 | |||
| 6755206d2e | |||
| 3938301b66 | |||
| 546f644ffd | |||
| ffaa06f934 | |||
| 20b25042b5 | |||
| 503ff3a73e | |||
| ee2115e367 | |||
| c9592dfd90 | |||
| 85b2b1bd87 | |||
| 6c126a6757 |
6
doc/lib/command.bat
Normal file
6
doc/lib/command.bat
Normal file
@@ -0,0 +1,6 @@
|
||||
echo off
|
||||
|
||||
:: 添加jar包到本地仓库
|
||||
cmd /k "mvn install:install-file -Dfile=jacob-1.21.jar -DgroupId=com.jacob -DartifactId=jacob -Dversion=1.21 -Dpackaging=jar"
|
||||
|
||||
pause
|
||||
BIN
doc/lib/jacob-1.21-x64.dll
Normal file
BIN
doc/lib/jacob-1.21-x64.dll
Normal file
Binary file not shown.
BIN
doc/lib/jacob-1.21-x86.dll
Normal file
BIN
doc/lib/jacob-1.21-x86.dll
Normal file
Binary file not shown.
BIN
doc/lib/jacob-1.21.jar
Normal file
BIN
doc/lib/jacob-1.21.jar
Normal file
Binary file not shown.
23
doc/win/yinyitong-dll-stand.bat
Normal file
23
doc/win/yinyitong-dll-stand.bat
Normal file
@@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
|
||||
:: 医保程序地址
|
||||
set CHSPATH=D:/Project/CHS
|
||||
:: jar名称
|
||||
set NAME=yinyitong-dll-stand
|
||||
:: 端口号
|
||||
set PROT=5946
|
||||
|
||||
echo 关闭端口进程:%PROT%
|
||||
for /f "tokens=1-5" %%i in ('netstat -ano^|findstr ":%PROT%"') do taskkill /pid %%m -t -f
|
||||
|
||||
echo 启动:%NAME%
|
||||
|
||||
cd %CHSPATH%
|
||||
|
||||
start javaw.exe -Dfile.encoding=UTF-8 -Djava.library.path=%CHSPATH% -Dlog.path=%~dp0 -jar %~dp0\%NAME%.jar --server.port=%PROT%
|
||||
|
||||
echo 启动完成
|
||||
|
||||
exit
|
||||
::pause
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
<service>
|
||||
<id>yinyitong-dll-stand</id>
|
||||
<name>yinyitong-dll-stand</name>
|
||||
<description>银医通-台式机-DLL调用服务</description>
|
||||
<!-- 环境变量 文件名称 -->
|
||||
<env name="NAME" value="yinyitong-dll-stand"/>
|
||||
|
||||
<!-- 服务ID -->
|
||||
<id>dpkj-%NAME%</id>
|
||||
<!-- 服务名称 -->
|
||||
<name>%BASE%</name>
|
||||
<!-- 服务描述 -->
|
||||
<description>银医台式机中间服务调用程序</description>
|
||||
|
||||
<!-- 启动命令 -->
|
||||
<executable>java</executable>
|
||||
<arguments>-jar %BASE%\yinyitong-dll-stand.jar</arguments>
|
||||
<!-- 启动参数 -->
|
||||
<arguments>-jar %BASE%\%NAME%.jar</arguments>
|
||||
|
||||
<!-- 日志-->
|
||||
<logpath>%BASE%\logs\winws</logpath>
|
||||
<!-- 按大小和时间滚动模式 -->
|
||||
<log mode="roll-by-size-time">
|
||||
<sizeThreshold>10240</sizeThreshold>
|
||||
<pattern>yyyyMMdd</pattern>
|
||||
<autoRollAtTime>00:00:00</autoRollAtTime>
|
||||
</log>
|
||||
</service>
|
||||
|
||||
67
pom.xml
67
pom.xml
@@ -19,8 +19,8 @@
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<hutool.version>5.8.25</hutool.version>
|
||||
<jna.version>5.14.0</jna.version>
|
||||
<hutool.version>5.8.36</hutool.version>
|
||||
<jna.version>5.17.0</jna.version>
|
||||
<pdfbox.version>3.0.2</pdfbox.version>
|
||||
</properties>
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据校验-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
@@ -50,21 +56,38 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 调用DLL -->
|
||||
<!-- <dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<artifactId>jna-jpms</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!--<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform-jpms</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 调用DLL - COM库 -->
|
||||
<dependency>
|
||||
<groupId>com.jacob</groupId>
|
||||
<artifactId>jacob</artifactId>
|
||||
<version>1.21</version>
|
||||
</dependency>
|
||||
|
||||
<!--PDF转换工具-->
|
||||
@@ -119,17 +142,23 @@
|
||||
<version>3.1.28</version> <!-- 可以根据实际情况调整版本 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--微信支付-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.15</version>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
<version>4.7.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据校验-->
|
||||
<!--添加JDOM库-->
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>2.0.1.Final</version>
|
||||
<groupId>org.jdom</groupId>
|
||||
<artifactId>jdom2</artifactId>
|
||||
<version>2.0.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
@@ -183,7 +212,7 @@
|
||||
<!--jar包名字是否包含唯一版本标识-->
|
||||
<useUniqueVersions>false</useUniqueVersions>
|
||||
<!--指定含main方法的主类入口-->
|
||||
<mainClass>com.dpkj.Application</mainClass>
|
||||
<mainClass>com.dpkj.StandDllApplication</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<!--MANIFEST.MF 中 Class-Path 加入资源文件目录!用命令java -jar时就不用-Dloader.path指定外部资源路径了 -->
|
||||
@@ -192,11 +221,11 @@
|
||||
</archive>
|
||||
<!-- 打包时从jar包里排除资源文件 -->
|
||||
<excludes>
|
||||
<exclude>*.yml</exclude>
|
||||
<!--<exclude>*.yml</exclude>
|
||||
<exclude>*.xml</exclude>
|
||||
<exclude>templates/**</exclude>
|
||||
<exclude>win32-x86-64/**</exclude>
|
||||
<exclude>win32-x86/**</exclude>
|
||||
<exclude>win32-x86/**</exclude>-->
|
||||
</excludes>
|
||||
<!-- 指定项目打成jar包输出位置 -->
|
||||
<outputDirectory>${project.build.directory}/output</outputDirectory>
|
||||
@@ -220,7 +249,7 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--拷贝资源文件! 插件maven-jar-plugin只负责打包时排除文件, 而把资源文件拷贝到外部resource目录就需要maven-dependency-plugin插件-->
|
||||
<plugin>
|
||||
<!--<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
@@ -232,16 +261,16 @@
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>
|
||||
<!--拷贝此目录下的所有文件到指定的外部目录。只负责拷贝,而不是从jar包中排除!-->
|
||||
<!–拷贝此目录下的所有文件到指定的外部目录。只负责拷贝,而不是从jar包中排除!–>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<!-- 把“<resource><directory>”指定目录中的文件输出到此处指定目录 -->
|
||||
<!– 把“<resource><directory>”指定目录中的文件输出到此处指定目录 –>
|
||||
<outputDirectory>${project.build.directory}/output/resources</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugin>-->
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import java.net.UnknownHostException;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
public class StandDllApplication {
|
||||
|
||||
public static void main(String[] args) throws UnknownHostException {
|
||||
ConfigurableApplicationContext application = SpringApplication.run(Application.class, args);
|
||||
ConfigurableApplicationContext application = SpringApplication.run(StandDllApplication.class, args);
|
||||
Environment env = application.getEnvironment();
|
||||
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||
String port = env.getProperty("server.port");
|
||||
22
src/main/java/com/dpkj/common/config/HisConfig.java
Normal file
22
src/main/java/com/dpkj/common/config/HisConfig.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.dpkj.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2024/4/28 14:55
|
||||
* @Description:
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "dpkj.his")
|
||||
public class HisConfig {
|
||||
|
||||
/**
|
||||
* 调用者ID
|
||||
*/
|
||||
private String operationId;
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.dpkj.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -29,4 +28,20 @@ public class PrinterConfig {
|
||||
* 波特率 串口连接下使用
|
||||
*/
|
||||
private Integer baudRate;
|
||||
|
||||
/**
|
||||
* 终端号
|
||||
*/
|
||||
private String terminalNumber;
|
||||
|
||||
/**
|
||||
* 时间格式
|
||||
*/
|
||||
private String timeType;
|
||||
|
||||
/**
|
||||
* 只有两层,要么是A4,要么是A5
|
||||
*/
|
||||
private String levelOne;
|
||||
|
||||
}
|
||||
|
||||
42
src/main/java/com/dpkj/common/constant/WxConstant.java
Normal file
42
src/main/java/com/dpkj/common/constant/WxConstant.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.dpkj.common.constant;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/5/28 11:04
|
||||
*/
|
||||
public interface WxConstant {
|
||||
|
||||
/**
|
||||
* 门店编号, 由商户定义, 各门店唯一。
|
||||
* TODO 修改
|
||||
*/
|
||||
String STORE_ID = "1111111111111";
|
||||
|
||||
//门店名称,由商户定义。(可用于展示);中文会导致调用失败
|
||||
String STORE_TEXT = "device8998No1";
|
||||
|
||||
//终端设备编号,由商户定义
|
||||
String DEVICE_ID = "device8998";
|
||||
|
||||
//版本号。固定为1
|
||||
String VERSION = "1";
|
||||
|
||||
//参数签名,使用MD5
|
||||
String SING_TYPE = "MD5";
|
||||
|
||||
/**
|
||||
* 支付类型
|
||||
*/
|
||||
String FACEPAY = "FACEPAY";
|
||||
|
||||
/**
|
||||
* 结果状态 成功
|
||||
*/
|
||||
String STATE_SUCCESS = "SUCCESS";
|
||||
|
||||
/**
|
||||
* 结果状态 失败
|
||||
*/
|
||||
String STATE_FAIL = "FAIL";
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
@@ -15,13 +13,12 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* controller控制器异常处理接口实现类
|
||||
* <p>其它module可以集成此类进行controller层的异常处理</p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/shi-chongli">石头人</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -38,41 +35,37 @@ public class ControllerAdvice {
|
||||
*/
|
||||
@ExceptionHandler(value = MethodArgumentNotValidException.class)
|
||||
public Result<String> bindingException(MethodArgumentNotValidException e) {
|
||||
// 获得所有校验出错的返回集
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
|
||||
// 循环获得所有校验异常的字段
|
||||
Map<String, String> fieldMap = new HashMap<>();
|
||||
for (FieldError fieldError : fieldErrors) {
|
||||
fieldMap.put(fieldError.getField(), fieldError.getDefaultMessage());
|
||||
}
|
||||
|
||||
String errMsg = fieldMap.values().toString().replaceAll("]", "").replaceAll("\\[", "");
|
||||
log.warn(errMsg);
|
||||
// 返回给前端
|
||||
String errMsg = e.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.parallelStream()
|
||||
.map(m -> /*m.getField() + ":" +*/ m.getDefaultMessage())
|
||||
.collect(Collectors.joining("、"));
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(errMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空指针异常
|
||||
*
|
||||
* @param nullPointerException 空指针异常
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(value = NullPointerException.class)
|
||||
public Result<String> nullPointException(NullPointerException nullPointerException) {
|
||||
log.error("空指针异常类型: {},信息: {}", nullPointerException.getClass(),nullPointerException.getMessage());
|
||||
log.error("空指针异常类型: {},信息: {}", nullPointerException.getClass(), nullPointerException);
|
||||
return Result.error(ErrorEnum.NULL_POINTER_EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有的运行时异常,抛出异常
|
||||
*
|
||||
* @param throwable 异常
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(value = Throwable.class)
|
||||
public Result<String> handleException(Throwable throwable) {
|
||||
log.error("异常类型: {}, {}, 信息为: {}", throwable.getCause(), throwable.getClass(), throwable.getMessage());
|
||||
if (throwable instanceof RRException){
|
||||
if (throwable instanceof RRException) {
|
||||
RRException rrException = (RRException) throwable;
|
||||
return Result.error(rrException.getCode(), rrException.getMsg());
|
||||
}
|
||||
@@ -81,24 +74,26 @@ public class ControllerAdvice {
|
||||
|
||||
/**
|
||||
* http信息无可读
|
||||
*
|
||||
* @param e 异常
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(value = HttpMessageNotReadableException.class)
|
||||
public Result<String> httpMessageNotReadAbleException(HttpMessageNotReadableException e){
|
||||
log.warn("异常类型: {} 无可读信息: {}", e.getClass(), e.getMessage());
|
||||
public Result<String> httpMessageNotReadAbleException(HttpMessageNotReadableException e) {
|
||||
log.warn("异常类型: {} 无可读信息: ", e.getClass(), e);
|
||||
return Result.error(ErrorEnum.HTTP_MESSAGE_NOT_READABLE_EXCEPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时异常
|
||||
*
|
||||
* @param e 运行异常对象
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(value = RuntimeException.class)
|
||||
public Result<String> runtimeException(RuntimeException e){
|
||||
log.error("运行时异常:{}", e.getMessage());
|
||||
if (e instanceof RRException){
|
||||
public Result<String> runtimeException(RuntimeException e) {
|
||||
log.error("运行时异常:", e);
|
||||
if (e instanceof RRException) {
|
||||
RRException rrException = (RRException) e;
|
||||
return Result.error(rrException.getCode(), rrException.getMsg());
|
||||
}
|
||||
@@ -107,32 +102,35 @@ public class ControllerAdvice {
|
||||
|
||||
/**
|
||||
* 请求不支持
|
||||
*
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
|
||||
public Result<String> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e){
|
||||
log.warn("暂不支持该请求: {}", e.getMessage());
|
||||
public Result<String> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||
log.warn("暂不支持该请求: ", e);
|
||||
return Result.error("暂不支持此请求方式");
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数类型错误
|
||||
*
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public Result<String> methodArgument(MethodArgumentTypeMismatchException e){
|
||||
log.warn("参数发生错误: {}", e.getMessage());
|
||||
public Result<String> methodArgument(MethodArgumentTypeMismatchException e) {
|
||||
log.warn("参数发生错误: ", e);
|
||||
return Result.error("参数发生错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺少请求参数
|
||||
*
|
||||
* @param e 缺少请求参数异常
|
||||
* @return Result
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public Result<String> exception(MissingServletRequestParameterException e){
|
||||
log.warn("缺少请求参数: {}", e.getMessage());
|
||||
public Result<String> exception(MissingServletRequestParameterException e) {
|
||||
log.warn("缺少请求参数: ", e);
|
||||
return Result.error("缺少请求参数");
|
||||
}
|
||||
|
||||
|
||||
49
src/main/java/com/dpkj/common/utils/IDGenerator.java
Normal file
49
src/main/java/com/dpkj/common/utils/IDGenerator.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.dpkj.common.utils;
|
||||
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2025/3/19 20:01
|
||||
* @Description:
|
||||
*/
|
||||
public class IDGenerator {
|
||||
|
||||
private static Snowflake snowflake = IdUtil.getSnowflake(1, 1);
|
||||
|
||||
private static Snowflake snowflake2 = IdUtil.getSnowflake(1, 2);
|
||||
|
||||
/**
|
||||
* 取雪花算法ID
|
||||
*
|
||||
* @return java.lang.String
|
||||
* @author 萧道子 2025/3/19
|
||||
*/
|
||||
public static String getSnowflakeIdToStr() {
|
||||
return snowflake.nextIdStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取雪花算法ID
|
||||
*
|
||||
* @return java.lang.String
|
||||
* @author 萧道子 2025/3/19
|
||||
*/
|
||||
public static Long getSnowflakeIdToLong() {
|
||||
return snowflake.nextId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Traceid
|
||||
*
|
||||
* @return java.lang.String
|
||||
* @author 萧道子 2025/4/1
|
||||
*/
|
||||
public static String getTraceid() {
|
||||
return snowflake2.nextIdStr();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class TemplateUtils {
|
||||
resolver.setTemplateMode("HTML");
|
||||
// 设置模板引擎使用这个自定义的模板解析器
|
||||
templateEngine.setTemplateResolver(resolver);
|
||||
}else {
|
||||
} else {
|
||||
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
|
||||
resolver.setPrefix("classpath:/templates/");
|
||||
resolver.setSuffix(".html");
|
||||
@@ -111,9 +111,10 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 生成图片
|
||||
*
|
||||
* @param html html内容
|
||||
*/
|
||||
private BufferedImage generate(String html, int width, int height){
|
||||
private BufferedImage generate(String html, int width, int height) {
|
||||
try {
|
||||
// 转换为xhtml
|
||||
String xhtml = this.htmlToXhtml(html);
|
||||
@@ -132,21 +133,22 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 通过JsonObject进行context内容填充
|
||||
*
|
||||
* @param data jsonObject
|
||||
* @return context
|
||||
*/
|
||||
private Context getContext(JSONObject data) {
|
||||
// 创建Thymeleaf上下文
|
||||
Context context = new Context();
|
||||
if ( data != null) {
|
||||
if (data != null) {
|
||||
Set<String> keys = data.keySet();
|
||||
for (String key : keys) {
|
||||
// 判单是否有图片生成,统一后面采用的是_2base64Type
|
||||
String[] split = key.split("_");
|
||||
if (split.length > 1 && split[1].equals("2base64Type")) {
|
||||
int type = split.length > 2 ? Integer.parseInt(split[2]) : 1;
|
||||
int width = split.length > 2 ? Integer.parseInt(split[3]) : 100;
|
||||
int height = split.length > 3 ? Integer.parseInt(split[4]) : 100;
|
||||
int width = split.length > 2 ? (Integer.parseInt(split[3]) + 50) : 150;// 台式的二维码宽高需要比壁挂的多加50
|
||||
int height = split.length > 3 ? (Integer.parseInt(split[4]) + 50) : 150;// 台式的二维码宽高需要比壁挂的多加50
|
||||
// 如果是图片类型,需要进行base64转换
|
||||
String base64 = this.generateQRCode(type, String.valueOf(data.get(key)), width, height);
|
||||
context.setVariable(split[0], "data:image/jpeg;base64," + base64);
|
||||
@@ -168,8 +170,9 @@ public class TemplateUtils {
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码为 UTF-8
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 设置纠错级别
|
||||
// 设置外边距为 0 以去除白边
|
||||
hints.put(EncodeHintType.MARGIN, 0);
|
||||
// 设置外边距为1
|
||||
hints.put(EncodeHintType.MARGIN, 1);
|
||||
|
||||
|
||||
if (type == 1) {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
@@ -182,7 +185,7 @@ public class TemplateUtils {
|
||||
|
||||
qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
}
|
||||
if ( qrImage == null ){
|
||||
if (qrImage == null) {
|
||||
throw new RRException("图片渲染失败");
|
||||
}
|
||||
|
||||
@@ -212,6 +215,7 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* xhtml 转换为 Document
|
||||
*
|
||||
* @param xhtml xhtml
|
||||
* @return document
|
||||
* @throws Exception e
|
||||
@@ -232,6 +236,7 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 转换,将html转换为xhtml
|
||||
*
|
||||
* @param html html内容
|
||||
* @return xhtml
|
||||
*/
|
||||
@@ -259,9 +264,10 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 通过document转换为图片
|
||||
*
|
||||
* @param document doc
|
||||
* @param width 图片的宽度
|
||||
* @param height 图片的高度
|
||||
* @param width 图片的宽度
|
||||
* @param height 图片的高度
|
||||
* @return bufferedImage
|
||||
*/
|
||||
private BufferedImage createImageToDocument(Document document, int width, int height) {
|
||||
@@ -309,15 +315,16 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 校验这个模板内容是不是html字符串,而非模板名称
|
||||
*
|
||||
* @param template template
|
||||
* @return 是否是html字符串
|
||||
*/
|
||||
private boolean checkIsHtml(String template){
|
||||
private boolean checkIsHtml(String template) {
|
||||
try {
|
||||
String pattern = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>";
|
||||
Pattern r = Pattern.compile(pattern);
|
||||
return r.matcher(template).find();
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -325,6 +332,7 @@ public class TemplateUtils {
|
||||
|
||||
/**
|
||||
* 将document对象转换为字符串
|
||||
*
|
||||
* @param doc document
|
||||
* @return document转换为的字符串
|
||||
*/
|
||||
|
||||
45
src/main/java/com/dpkj/common/vo/ResultData.java
Normal file
45
src/main/java/com/dpkj/common/vo/ResultData.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package com.dpkj.common.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 接口返回数据格式
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ResultData implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 请求时间
|
||||
*/
|
||||
private String requestTime;
|
||||
|
||||
/**
|
||||
* 请求内容
|
||||
*/
|
||||
private String requestContent;
|
||||
|
||||
/**
|
||||
* 响应时间
|
||||
*/
|
||||
private String responseTime;
|
||||
|
||||
/**
|
||||
* 响应内容
|
||||
*/
|
||||
private String responseContent;
|
||||
|
||||
/**
|
||||
* 患者ID
|
||||
*/
|
||||
private String patientId;
|
||||
|
||||
/**
|
||||
* 处理后的响应内容
|
||||
*/
|
||||
private Object result;
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
package com.dpkj.modules.chs.controller;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.config.ChsConfig;
|
||||
import com.dpkj.common.constant.ChsConst;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.chs.dll.AlipayDll;
|
||||
import com.dpkj.modules.chs.entity.AlipayEcRequestData;
|
||||
import com.dpkj.modules.chs.service.IAlipayService;
|
||||
import com.dpkj.common.vo.ResultData;
|
||||
import com.dpkj.modules.chs.service.IHispayService;
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.dpkj.modules.chs.vo.OutpatientBeginModel;
|
||||
import com.dpkj.modules.chs.vo.OutpatientFinalModel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -30,21 +26,92 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/chs/hispay")
|
||||
public class HispayController {
|
||||
|
||||
private final IHispayService hispayService;
|
||||
|
||||
private final ChsConfig charsConfig;
|
||||
private final IHispayService iHispayService;
|
||||
|
||||
|
||||
@GetMapping("test")
|
||||
public Result<?> test() {
|
||||
/**
|
||||
* 通过医保卡或医保电子凭证读卡
|
||||
*
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/20
|
||||
*/
|
||||
@PostMapping("findReadCode")
|
||||
public Result<?> findReadCode() {
|
||||
try {
|
||||
|
||||
return Result.ok("成功", null);
|
||||
JSONObject res = iHispayService.readCode();
|
||||
return Result.ok("成功", res);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("失败");
|
||||
log.error("[HispayController][getPatientInfo][按医保电子凭证读卡] ERR:{}", e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过医保卡获取患者信息
|
||||
*
|
||||
* @param data :
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/28
|
||||
*/
|
||||
@PostMapping("findReadCard")
|
||||
public Result<?> findReadCard(@RequestBody JSONObject data) {
|
||||
try {
|
||||
String password = data.getString("password");
|
||||
if (StrUtil.isEmpty(password)) {
|
||||
throw new RuntimeException("密码不可为空");
|
||||
}
|
||||
|
||||
JSONObject res = iHispayService.readCard(password);
|
||||
return Result.ok("成功", res);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("[HispayController][readCardByCard][按医保卡读卡] ERR:{}", e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 门诊缴费-预算
|
||||
*
|
||||
* @param data :
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/27
|
||||
*/
|
||||
@PostMapping("chsCodeAsOutpatientBegin")
|
||||
public Result<?> chsCodeAsOutpatientBegin(@RequestBody @Validated OutpatientBeginModel data) {
|
||||
try {
|
||||
log.info("[HispayController][chsCodeAsOutpatientBegin][门诊缴费-预算-电子医保凭证] 参数:{}", data);
|
||||
ResultData res = iHispayService.chsCodeAsOutpatientBegin(data);
|
||||
return Result.ok("成功", res);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.info("[HispayController][chsCodeAsOutpatientBegin][门诊缴费-预算-电子医保凭证] 失败:{}", e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 门诊缴费-结算
|
||||
*
|
||||
* @param data :
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/27
|
||||
*/
|
||||
@PostMapping("chsCodeAsOutpatientFinal")
|
||||
public Result<?> chsCodeAsOutpatientFinal(@RequestBody @Validated OutpatientFinalModel data) {
|
||||
try {
|
||||
log.info("[HispayController][chsCodeAsOutpatientFinal][门诊缴费-结算-电子医保凭证] 参数:{}", data);
|
||||
ResultData res = iHispayService.chsCodeAsOutpatientFinal(data);
|
||||
return Result.ok("成功", res);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.info("[HispayController][chsCodeAsOutpatientFinal][门诊缴费-结算-电子医保凭证] 失败:{}", e.getMessage());
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class AlipayDll {
|
||||
*/
|
||||
public static Dll instance() throws DllRegistrationException {
|
||||
try {
|
||||
return Native.load("NationECCode", Dll.class);
|
||||
return Native.load("AlipayChs", Dll.class);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
log.info("[AlipayDll][getPrintSDK][医保动态库] SDK注册失败:{}", e.getMessage());
|
||||
throw new DllRegistrationException("Failed to load AlipayDll library: ", e);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.dpkj.modules.chs.dll;
|
||||
|
||||
import com.jacob.activeX.ActiveXComponent;
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
@@ -13,47 +14,5 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class HispayDll {
|
||||
|
||||
/**
|
||||
* 获取 AlipayDll 实例,同时注册 AlipayDll 控件。
|
||||
*
|
||||
* @return AlipayDll 实例
|
||||
* @throws DllRegistrationException 如果注册控件失败,抛出此异常
|
||||
*/
|
||||
public static Dll instance() throws DllRegistrationException {
|
||||
try {
|
||||
return Native.load("PayClient", Dll.class);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
log.info("[HispayDll][getPrintSDK][医保动态库] SDK注册失败:{}", e.getMessage());
|
||||
throw new DllRegistrationException("Failed to load AlipayDll library: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义自定义异常类,用于表示注册控件时发生的错误
|
||||
*/
|
||||
public static class DllRegistrationException extends Exception {
|
||||
public DllRegistrationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DllRegistrationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定义接口映射本地库中的函数。
|
||||
*/
|
||||
public interface Dll extends Library {
|
||||
|
||||
/**
|
||||
* 设置打印端口和波特率。
|
||||
*
|
||||
* @return 返回操作结果代码
|
||||
*/
|
||||
String NationEcTrans(String strUrl, String InData, Pointer OutData);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,47 @@
|
||||
package com.dpkj.modules.chs.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.vo.ResultData;
|
||||
import com.dpkj.modules.chs.vo.OutpatientBeginModel;
|
||||
import com.dpkj.modules.chs.vo.OutpatientFinalModel;
|
||||
|
||||
public interface IHispayService {
|
||||
|
||||
/**
|
||||
* 通过医保电子凭证读卡
|
||||
*
|
||||
* @return com.alibaba.fastjson.JSONObject
|
||||
* @author 萧道子 2025/5/28
|
||||
*/
|
||||
JSONObject readCode();
|
||||
|
||||
|
||||
/**
|
||||
* 通过医保卡-读卡
|
||||
*
|
||||
* @return com.alibaba.fastjson.JSONObject
|
||||
* @author 萧道子 2025/5/28
|
||||
*/
|
||||
JSONObject readCard(String password);
|
||||
|
||||
|
||||
/**
|
||||
* 门诊缴费-预算 电子凭证支付
|
||||
*
|
||||
* @param data :
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/27
|
||||
*/
|
||||
ResultData chsCodeAsOutpatientBegin(OutpatientBeginModel data);
|
||||
|
||||
/**
|
||||
* 门诊缴费-结算 电子凭证支付
|
||||
*
|
||||
* @param data :
|
||||
* @return com.dpkj.common.vo.Result<?>
|
||||
* @author 萧道子 2025/5/27
|
||||
*/
|
||||
ResultData chsCodeAsOutpatientFinal(OutpatientFinalModel data);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
package com.dpkj.modules.chs.service.impl;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import com.dpkj.modules.chs.dll.HispayDll;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.XmlUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.config.ChsConfig;
|
||||
import com.dpkj.common.config.HisConfig;
|
||||
import com.dpkj.common.utils.IDGenerator;
|
||||
import com.dpkj.common.vo.ResultData;
|
||||
import com.dpkj.modules.chs.service.IHispayService;
|
||||
import com.dpkj.modules.chs.vo.OutpatientBeginModel;
|
||||
import com.dpkj.modules.chs.vo.OutpatientFinalModel;
|
||||
import com.jacob.activeX.ActiveXComponent;
|
||||
import com.jacob.com.ComThread;
|
||||
import com.jacob.com.Dispatch;
|
||||
import com.jacob.com.Variant;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
@@ -15,23 +31,307 @@ import javax.annotation.PostConstruct;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class HispayServiceImpl implements IHispayService {
|
||||
|
||||
private HispayDll.Dll dll = HispayDll.instance();
|
||||
private final HisConfig hisConfig;
|
||||
private final ChsConfig chsConfig;
|
||||
|
||||
|
||||
public HispayServiceImpl() throws HispayDll.DllRegistrationException {
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.info("[HispayServiceImpl][postConstruct][医保DLL-HIS] 初始化动态链接库");
|
||||
initPrinter();
|
||||
/**
|
||||
* 获取HIS医保实例
|
||||
*
|
||||
* @return com.jacob.activeX.ActiveXComponent
|
||||
* @author 萧道子 2025/5/21
|
||||
*/
|
||||
private ActiveXComponent instanceActive() {
|
||||
try {
|
||||
// 初始化
|
||||
ComThread.InitSTA();
|
||||
// 获取COM对象
|
||||
ActiveXComponent active = new ActiveXComponent("PayClient.clsPayClient");
|
||||
log.info("[HispayServiceImpl][instanceActive][HIS医保COM库] 加载成功");
|
||||
return active;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
log.info("[HispayServiceImpl][instanceActive][HIS医保COM库] 加载失败:{}", e.getMessage());
|
||||
throw new RuntimeException("HIS医保COM库加载失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initPrinter() {
|
||||
Console.log(dll);
|
||||
/**
|
||||
* 释放资源
|
||||
*
|
||||
* @return void
|
||||
* @author 萧道子 2025/5/21
|
||||
*/
|
||||
private void releaseActive() {
|
||||
ComThread.Release();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数
|
||||
*
|
||||
* @param params : 请求参数
|
||||
* @param password : 密码
|
||||
* @return java.lang.String
|
||||
* @author 萧道子 2025/5/27
|
||||
*/
|
||||
private String processParameters(JSONObject params, String password) {
|
||||
JSONObject req = new JSONObject() {{
|
||||
put("timestamp", DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss")); // 请求发送时间
|
||||
put("requestid", IDGenerator.getSnowflakeIdToStr()); // 唯一请求id
|
||||
put("operid", hisConfig.getOperationId()); // 调用者代码
|
||||
put("password", password); // 密码
|
||||
put("params", params);
|
||||
}};
|
||||
Document document = XmlUtil.mapToXml(req, "request");
|
||||
return XmlUtil.toStr(document, "UTF-8", false, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验结果
|
||||
*
|
||||
* @param resStr :
|
||||
* @return com.alibaba.fastjson.JSONObject
|
||||
* @author 萧道子 2025/5/28
|
||||
*/
|
||||
private JSONObject verifyResult(String resStr) {
|
||||
|
||||
if (StrUtil.isBlank(resStr)) {
|
||||
throw new RuntimeException("信息获取失败");
|
||||
}
|
||||
|
||||
Map<String, Object> resMap = XmlUtil.xmlToMap(resStr);
|
||||
JSONObject resJson = new JSONObject(resMap);
|
||||
|
||||
if (StrUtil.equals(resJson.getString("resultCode"), "1")) {
|
||||
throw new RuntimeException(resJson.getString("resultMessage"));
|
||||
}
|
||||
|
||||
if (!resJson.containsKey("result")) {
|
||||
throw new RuntimeException("result数据为空");
|
||||
}
|
||||
|
||||
return resJson.getJSONObject("result");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject readCode() {
|
||||
// 加载资源
|
||||
Dispatch dispatch = instanceActive();
|
||||
|
||||
/** 1、组装参数 */
|
||||
JSONObject val = new JSONObject()
|
||||
.fluentPut("cardtype", "9") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证
|
||||
.fluentPut("cardno", "") // 患者就诊卡号
|
||||
.fluentPut("sfzh", "") // 身份证号
|
||||
.fluentPut("hzxm", "") // 患者姓名
|
||||
.fluentPut("phone", ""); // 患者电话号码
|
||||
String params = processParameters(val, null);
|
||||
log.info("[HispayServiceImpl][readCode][医保读卡-电子凭证] 接口入参:{}", params);
|
||||
|
||||
/** 2、调用COM函数 */
|
||||
Variant vres = new Variant("", true);
|
||||
Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", params, vres);
|
||||
|
||||
String resStr = vres.getStringRef();
|
||||
log.info("[HispayServiceImpl][readCode][医保读卡-电子凭证] call返回值:{} 结果:{}", call, resStr);
|
||||
|
||||
// 释放资源
|
||||
releaseActive();
|
||||
|
||||
/** 3、处理读卡结果 */
|
||||
JSONObject result = verifyResult(resStr);
|
||||
if (!result.containsKey("item")) {
|
||||
throw new RuntimeException("item数据为空");
|
||||
}
|
||||
|
||||
return result.getJSONObject("item");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject readCard(String password) {
|
||||
// 加载资源
|
||||
Dispatch dispatch = instanceActive();
|
||||
|
||||
/** 1、组装参数 */
|
||||
JSONObject val = new JSONObject()
|
||||
.fluentPut("cardtype", "2") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证
|
||||
.fluentPut("cardno", "") // 患者就诊卡号
|
||||
.fluentPut("sfzh", "") // 身份证号
|
||||
.fluentPut("hzxm", "") // 患者姓名
|
||||
.fluentPut("phone", ""); // 患者电话号码
|
||||
String params = processParameters(val, password);
|
||||
log.info("[HispayServiceImpl][readCard][医保读卡-医保卡] 接口入参:{}", params);
|
||||
|
||||
/** 2、调用COM函数 */
|
||||
Variant vres = new Variant("", true);
|
||||
Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", params, vres);
|
||||
|
||||
String resStr = vres.getStringRef();
|
||||
log.info("[HispayServiceImpl][readCard][医保读卡-医保卡] call返回值:{} 结果:{}", call, resStr);
|
||||
|
||||
// 释放资源
|
||||
releaseActive();
|
||||
|
||||
/** 3、处理读卡结果 */
|
||||
JSONObject result = verifyResult(resStr);
|
||||
if (!result.containsKey("item")) {
|
||||
throw new RuntimeException("item数据为空");
|
||||
}
|
||||
|
||||
return result.getJSONObject("item");
|
||||
}
|
||||
|
||||
|
||||
// public JSONObject getPatientInfo_old(String type, String content) {
|
||||
// Dispatch dispatch = instanceActive();
|
||||
//
|
||||
// /**
|
||||
// * 1、删除保存HIS读卡内容的文件 避免读取到错误信息
|
||||
// */
|
||||
// // 获取HIS-CHS医保库路径
|
||||
// String chsPath = System.getProperty("java.library.path");
|
||||
// String filePath = chsPath + "/" + chsConfig.getFileName();
|
||||
// // 删除文件
|
||||
// FileUtil.del(FileUtil.file(filePath));
|
||||
//
|
||||
// /**
|
||||
// * 2、组装参数
|
||||
// */
|
||||
// JSONObject val = new JSONObject()
|
||||
// .fluentPut("cardtype", type) // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证
|
||||
// .fluentPut("cardno", "") // 患者就诊卡号
|
||||
// .fluentPut("sfzh", "") // 身份证号
|
||||
// .fluentPut("hzxm", "") // 患者姓名
|
||||
// .fluentPut("phone", ""); // 患者电话号码
|
||||
// String params = processParameters(val, content);
|
||||
// log.info("[HispayServiceImpl][getPatientInfo][医保读卡] 接口入参:{}", params);
|
||||
//
|
||||
// /**
|
||||
// * 3、调用COM函数
|
||||
// */
|
||||
// Variant rest = new Variant();
|
||||
// Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", params, rest);
|
||||
// log.info("[HispayServiceImpl][getPatientInfo][医保读卡] call返回值:{}", call);
|
||||
//
|
||||
// // 释放资源
|
||||
// releaseActive();
|
||||
//
|
||||
// /**
|
||||
// * 4、处理读卡结果
|
||||
// */
|
||||
// // COM函数调用之后会生成新的文件 需要判断
|
||||
// File file = FileUtil.file(filePath);
|
||||
// if (!file.exists()) {
|
||||
// throw new RuntimeException("读卡失败:数据未读取");
|
||||
// }
|
||||
//
|
||||
// // 获取读卡结果
|
||||
// FileReader fileReader = new FileReader(file, "GBK");
|
||||
// String data = fileReader.readString();
|
||||
//
|
||||
// JSONObject result;
|
||||
// try {
|
||||
// result = JSONObject.parseObject(data);
|
||||
// log.info("[HispayServiceImpl][getPatientInfo][医保读卡] 读卡值:{}", result);
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException("读卡失败:" + data);
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
private ResultData outpatientBudget(OutpatientBeginModel data) {
|
||||
// 加载资源
|
||||
ActiveXComponent dispatch = instanceActive();
|
||||
|
||||
/** 1、组装参数 */
|
||||
JSONObject val = new JSONObject()
|
||||
.fluentPut("patid", data.getPatientId()) // 病人ID
|
||||
.fluentPut("cfxhhj", data.getPrescriptionNo()) // 划价单据,格式:单据1,单据2,单据3
|
||||
.fluentPut("czksfbz", "0") // 是否扣院内账户,0不使用院内账户,1使用院内账户(默认不使用院内账户)
|
||||
.fluentPut("zfjsbz", "0") // 是否自费结算,0根据医保代码缴费,1自费结算(默认自费结算)
|
||||
.fluentPut("ybrc", ""); // 医保入参,xml节点
|
||||
String params = processParameters(val, data.getPassword());
|
||||
log.info("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] 接口入参:{}", params);
|
||||
|
||||
String requestTime = DateUtil.now();
|
||||
|
||||
/** 2、调用COM函数 */
|
||||
Variant resVariant = new Variant("", true);
|
||||
Variant call = Dispatch.call(dispatch, "fRun", "BMZJF001", params, resVariant);
|
||||
|
||||
String responseTime = DateUtil.now();
|
||||
String resStr = resVariant.getStringRef();
|
||||
log.info("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] call返回值:{} 结果:{}", call, resStr);
|
||||
|
||||
// 释放资源
|
||||
releaseActive();
|
||||
|
||||
/** 3、处理结果 */
|
||||
JSONObject result = verifyResult(resStr);
|
||||
|
||||
return new ResultData()
|
||||
.setRequestTime(requestTime)
|
||||
.setRequestContent(params)
|
||||
.setResponseTime(responseTime)
|
||||
.setPatientId(data.getPatientId())
|
||||
.setResponseContent(resStr)
|
||||
.setResult(result);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResultData chsCodeAsOutpatientBegin(OutpatientBeginModel data) {
|
||||
// 用户读卡-生成token
|
||||
JSONObject userInfo = readCode();
|
||||
String patientId = userInfo.getString("patid");
|
||||
data.setPatientId(patientId);
|
||||
return outpatientBudget(data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResultData chsCodeAsOutpatientFinal(OutpatientFinalModel data) {
|
||||
// 加载资源
|
||||
ActiveXComponent dispatch = instanceActive();
|
||||
|
||||
/** 1、组装参数 */
|
||||
JSONObject val = (JSONObject) JSON.toJSON(data);
|
||||
String params = processParameters(val, null);
|
||||
log.info("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] 接口入参:{}", params);
|
||||
|
||||
String requestTime = DateUtil.now();
|
||||
|
||||
/** 2、调用COM函数 */
|
||||
Variant resVariant = new Variant("", true);
|
||||
Variant call = Dispatch.call(dispatch, "fRun", "BMZJF002", params, resVariant);
|
||||
|
||||
String responseTime = DateUtil.now();
|
||||
String resStr = resVariant.getStringRef();
|
||||
log.info("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] call返回值:{} 结果:{}", call, resStr);
|
||||
|
||||
// 释放资源
|
||||
releaseActive();
|
||||
|
||||
// TODO BEGIN 萧道子 2025/6/10 : 模拟参数
|
||||
// String resStr = "<response><resultCode>0</resultCode><resultMessage/><result><sjh>123456789</sjh><sfrq>2025-06-10 10:23:14</sfrq><bz/><pyckjh/><fyckjh/><yflshjh/><ybcc/></result></response>";
|
||||
// TODO END 萧道子 2025/6/10 : 模拟参数
|
||||
|
||||
/** 3、处理结果 */
|
||||
JSONObject result = verifyResult(resStr);
|
||||
|
||||
return new ResultData()
|
||||
.setRequestTime(requestTime)
|
||||
.setRequestContent(params)
|
||||
.setResponseTime(responseTime)
|
||||
.setPatientId(data.getPatid())
|
||||
.setResponseContent(resStr)
|
||||
.setResult(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.dpkj.modules.chs.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2025/5/27 16:52
|
||||
* @Description:
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OutpatientBeginModel implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 病人ID
|
||||
*/
|
||||
// @NotEmpty(message = "患者ID不可为空!")
|
||||
private String patientId;
|
||||
|
||||
/**
|
||||
* 处方单号 - 多个逗号拼接
|
||||
*/
|
||||
@NotEmpty(message = "处方单号不可为空!")
|
||||
private String prescriptionNo;
|
||||
|
||||
/**
|
||||
* 病人ID
|
||||
*/
|
||||
// @NotEmpty(message = "密码不可为空!")
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.dpkj.modules.chs.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2025/5/27 16:52
|
||||
* @Description: 结算
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OutpatientFinalModel implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotEmpty(message = "患者ID不可为空!")
|
||||
private String patid;
|
||||
|
||||
@NotEmpty(message = "收据号不可为空!")
|
||||
private String sjh;
|
||||
|
||||
@NotEmpty(message = "总金额不可为空!")
|
||||
private String zje;
|
||||
|
||||
@NotEmpty(message = "应付金额不可为空!")
|
||||
private String ysje;
|
||||
|
||||
@NotEmpty(message = "支付方式不可为空!")
|
||||
private String paytype;
|
||||
|
||||
@NotEmpty(message = "支付金额不可为空!")
|
||||
private String paymoney;
|
||||
|
||||
@NotEmpty(message = "支付流水号不可为空!")
|
||||
private String paylsh;
|
||||
|
||||
@NotEmpty(message = "医保报销金额不可为空!")
|
||||
private String ybzf;
|
||||
|
||||
/**
|
||||
* 支付时间
|
||||
*/
|
||||
private String paytime;
|
||||
}
|
||||
@@ -5,9 +5,13 @@ import com.dpkj.common.dto.LexMarkResultDTO;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.print.request.ReceiptPrintRequest;
|
||||
import com.dpkj.modules.print.service.PrintService;
|
||||
import com.dpkj.modules.print.vo.PrinterStatus;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
@@ -8,7 +8,7 @@ import lombok.Getter;
|
||||
public enum ReceiptTemplateEnum {
|
||||
|
||||
/**
|
||||
* 自足挂号模板
|
||||
* 自助挂号模板
|
||||
*/
|
||||
REGISTER("1", "register", "自助挂号的"),
|
||||
|
||||
@@ -25,9 +25,7 @@ public enum ReceiptTemplateEnum {
|
||||
/**
|
||||
* 门诊缴费的模板
|
||||
*/
|
||||
OUTPATIENT_T2("4", "department", "门诊缴费的小票-T2"),
|
||||
|
||||
;
|
||||
OUTPATIENT_T2("4", "department", "门诊缴费的小票-T2");
|
||||
|
||||
private final String code;
|
||||
|
||||
@@ -36,7 +34,7 @@ public enum ReceiptTemplateEnum {
|
||||
private final String desc;
|
||||
|
||||
|
||||
ReceiptTemplateEnum(String code, String templateName, String desc){
|
||||
ReceiptTemplateEnum(String code, String templateName, String desc) {
|
||||
this.code = code;
|
||||
this.templateName = templateName;
|
||||
this.desc = desc;
|
||||
@@ -44,10 +42,11 @@ public enum ReceiptTemplateEnum {
|
||||
|
||||
/**
|
||||
* 通过code获取模板名称
|
||||
*
|
||||
* @param code code/也有可能直接是一个名称
|
||||
* @return 模板的名称
|
||||
*/
|
||||
public static String getTemplateName(String code){
|
||||
public static String getTemplateName(String code) {
|
||||
if (code == null || "".equals(code)) {
|
||||
throw new RRException("模板名称不能为空");
|
||||
}
|
||||
@@ -55,7 +54,7 @@ public enum ReceiptTemplateEnum {
|
||||
String name = null;
|
||||
for (ReceiptTemplateEnum enumEntity : ReceiptTemplateEnum.values()) {
|
||||
String enumCode = enumEntity.getCode();
|
||||
if ( enumCode.equals(code)){
|
||||
if (enumCode.equals(code)) {
|
||||
name = enumEntity.getTemplateName();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class ReceiptPrintRequest implements Serializable {
|
||||
/**
|
||||
* 生成的模板的高度,默认为:1200
|
||||
*/
|
||||
private Integer height = 1200;
|
||||
private Integer height = 1250;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.dpkj.modules.print.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.config.PrinterConfig;
|
||||
import com.dpkj.common.dto.LexMarkDTO;
|
||||
import com.dpkj.common.dto.LexMarkResultDTO;
|
||||
import com.dpkj.common.exception.RRException;
|
||||
@@ -8,12 +9,14 @@ import com.dpkj.common.utils.ThirdService;
|
||||
import com.dpkj.modules.print.enums.*;
|
||||
import com.dpkj.modules.print.request.MS439Request;
|
||||
import com.dpkj.modules.print.service.MS439PrintService;
|
||||
import com.dpkj.modules.print.utils.FolderUtils;
|
||||
import com.dpkj.modules.print.utils.PDFUtils;
|
||||
import com.dpkj.modules.print.vo.PrinterStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 利盟MS439打印机服务
|
||||
@@ -29,8 +32,19 @@ public class MS439PrintServiceImpl implements MS439PrintService {
|
||||
@Resource
|
||||
private ThirdService thirdService;
|
||||
|
||||
@Resource
|
||||
private PrinterConfig printerConfig;
|
||||
|
||||
@Override
|
||||
public LexMarkResultDTO<?> printImage(MS439Request request) {
|
||||
FolderUtils.checkFolderAndCreate(request.getFileDir());
|
||||
|
||||
// 校验是本地地址还是http开头的
|
||||
PDFUtils.AddressType addressType = PDFUtils.checkAddressType(request.getFileDir());
|
||||
if (addressType.equals(PDFUtils.AddressType.HTTP)) {
|
||||
request.setFileDir(PDFUtils.downloadPdf(request.getFileDir()));
|
||||
}
|
||||
|
||||
LexMarkResultDTO<PrinterStatus> status = this.getStatus(request.getPagesource());
|
||||
PrinterStatus ms439 = status.getData();
|
||||
|
||||
@@ -64,9 +78,20 @@ public class MS439PrintServiceImpl implements MS439PrintService {
|
||||
// param.put("prtData", String.format("PrintType=%d;pagesource=%s;copies=%d;file[0]=%s;stamp=%d;duplex=%d;color=%d;direction=%d",
|
||||
// request.getPagesource(), request.getCopies(), request.getFileDir(), request.getStamp(),
|
||||
// request.getDuplex(), request.getColor(), request.getDirection()));
|
||||
// 计算A4/A5归属
|
||||
int printType = 1; // 默认使用第一层
|
||||
if (printerConfig.getLevelOne().equals("A5")) {
|
||||
if (request.getPagesource().equals("A4")) {
|
||||
printType = 2;
|
||||
}
|
||||
} else if (printerConfig.getLevelOne().equals("A4")) {
|
||||
if (request.getPagesource().equals("A5")) {
|
||||
printType = 2;
|
||||
}
|
||||
}
|
||||
param.put("prtData", String.format("PaperNum=%d;PrintType=%d;Stamp=%d;File[0]=%s;WaitNum=%d;copies=%d;stamp=%d;duplex=%d;color=%d;direction=%d",
|
||||
request.getCopies(), // 盖章事件分数,和打印份数一致
|
||||
Objects.equals(request.getPagesource(), "A4") ? 1 : 2, // 打印类型1-A4或者2-A5
|
||||
printType, // 打印类型1-A4或者2-A5
|
||||
request.getStamp(), // 是否盖章
|
||||
request.getFileDir(), // 要打印的文件的路径
|
||||
request.getCopies(), // 盖章事件分数,和打印份数一致
|
||||
@@ -78,7 +103,23 @@ public class MS439PrintServiceImpl implements MS439PrintService {
|
||||
|
||||
lexMarkDTO.setParam(param.toString());
|
||||
LexMarkResultDTO<LexMarkResultDTO.Param> paramLexMarkResultDTO = thirdService.callDevice(lexMarkDTO, LexMarkResultDTO.Param.class);
|
||||
// thirdService.close("HtmPrinter");
|
||||
|
||||
if ( paramLexMarkResultDTO.getData().getResult() != 0){
|
||||
throw new RRException(500, paramLexMarkResultDTO.getData().getResult() + "");
|
||||
}
|
||||
|
||||
File file = new File(request.getFileDir());
|
||||
// 检查文件是否存在
|
||||
if (file.exists()) {
|
||||
// 尝试删除文件
|
||||
if (file.delete()) {
|
||||
log.info("文件删除成功: " + request.getFileDir());
|
||||
} else {
|
||||
log.info("文件删除失败: " + request.getFileDir());
|
||||
}
|
||||
} else {
|
||||
log.info("文件不存在: " + request.getFileDir());
|
||||
}
|
||||
return paramLexMarkResultDTO;
|
||||
}
|
||||
|
||||
@@ -107,11 +148,11 @@ public class MS439PrintServiceImpl implements MS439PrintService {
|
||||
String paperStatus = "";
|
||||
// 如果是A4,当前是取第一层纸盒作为A4
|
||||
if (papersource.equals("A4")) {
|
||||
paperStatus = papers[0];
|
||||
paperStatus = printerConfig.getLevelOne().equals("A4") ? papers[0] : papers[1];
|
||||
}
|
||||
// 如果是A5,当前是取第二层纸盒只作为A5
|
||||
if (papersource.equals("A5")) {
|
||||
paperStatus = papers[1];
|
||||
paperStatus = printerConfig.getLevelOne().equals("A5") ? papers[0] : papers[1];
|
||||
}
|
||||
if ( !(paperStatus.equals(MS439PaperStatusEnum.PAPERFULL.getPrintCode()) || paperStatus.equals(MS439PaperStatusEnum.PAPERLOW.getPrintCode()))){
|
||||
throw new RRException(500, paperStatus);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.dpkj.modules.print.service.impl;
|
||||
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.config.PrinterConfig;
|
||||
import com.dpkj.common.dto.LexMarkDTO;
|
||||
import com.dpkj.common.dto.LexMarkResultDTO;
|
||||
import com.dpkj.common.exception.RRException;
|
||||
@@ -9,6 +11,7 @@ import com.dpkj.common.utils.TemplateUtils;
|
||||
import com.dpkj.common.utils.ThirdService;
|
||||
import com.dpkj.modules.print.enums.*;
|
||||
import com.dpkj.modules.print.service.PrintService;
|
||||
import com.dpkj.modules.print.utils.FolderUtils;
|
||||
import com.dpkj.modules.print.vo.PrinterStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -19,6 +22,9 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 挂号服务打印
|
||||
@@ -31,49 +37,110 @@ import java.io.IOException;
|
||||
@Service("registerService")
|
||||
public class RegisterServiceImpl implements PrintService {
|
||||
|
||||
@Resource
|
||||
private PrinterConfig printerConfig;
|
||||
|
||||
@Resource
|
||||
private ThirdService thirdService;
|
||||
|
||||
private static final int MAX_HEIGHT = 1000;
|
||||
private static final int FIXED_WIDTH = 690;
|
||||
private static final int FIXED_WIDTH = 730;
|
||||
|
||||
@Override
|
||||
public LexMarkResultDTO<LexMarkResultDTO.Param> printImage(JSONObject data, String template, int width, int height, String saveDir) {
|
||||
if ( width > 690 || height <= 0){
|
||||
width = 690;
|
||||
FolderUtils.checkFolderAndCreate(saveDir);
|
||||
|
||||
if (height <= 0) {
|
||||
width = FIXED_WIDTH;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(printerConfig.getTimeType())) {
|
||||
throw new RRException("获取打印时间格式出错:" + printerConfig.getTimeType());
|
||||
}
|
||||
// if ( StringUtils.isEmpty(printerConfig.getTerminalNumber())){
|
||||
// throw new RRException("获取终端号出错:" + printerConfig.getTerminalNumber());
|
||||
// }
|
||||
|
||||
// 强行设置终端号
|
||||
// data.put("terminalNumber", printerConfig.getTerminalNumber());
|
||||
|
||||
// 强行设置终端号和打印时间
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(printerConfig.getTimeType());
|
||||
String formattedDate = sdf.format(new Date());
|
||||
data.put("printTime", formattedDate);
|
||||
|
||||
// 获取模板
|
||||
String templateName = ReceiptTemplateEnum.getTemplateName(template);
|
||||
|
||||
int dinyHeight = 0;
|
||||
if (templateName.equals("department")) {
|
||||
// 由于是使用的门诊小票-T2,那么默认的高度为1000,强行设置,通过动态修改渲染的图片的高度
|
||||
height = 1000;
|
||||
|
||||
// 单行最大长度为10
|
||||
int singleLineMaxLength = 10;
|
||||
// 这里的长度取自于department.html模板中的项目单个tr高度,并且略高于该高度
|
||||
int singleLineHeight = 40;
|
||||
// 动态计算长度
|
||||
JSONArray items = data.getJSONArray("items");
|
||||
for (Object item : items) {
|
||||
JSONObject itemEntity = (JSONObject) item;
|
||||
String projectName = String.valueOf(itemEntity.get("name"));
|
||||
int length = projectName.length();
|
||||
int count = (int) Math.ceil((double) length / singleLineMaxLength);
|
||||
dinyHeight += count * singleLineHeight;
|
||||
}
|
||||
|
||||
// 计算是否有门诊号
|
||||
String outpatientNumber = data.getString("outpatientNumber");
|
||||
if (!StringUtils.isEmpty(outpatientNumber)) {
|
||||
dinyHeight += 35;
|
||||
}
|
||||
|
||||
// 计算是否有就诊医生
|
||||
String doctor = data.getString("doctor");
|
||||
if (!StringUtils.isEmpty(doctor)) {
|
||||
dinyHeight += 35;
|
||||
}
|
||||
|
||||
// 计算是否有就诊科室
|
||||
String department = data.getString("department");
|
||||
if (!StringUtils.isEmpty(department)) {
|
||||
dinyHeight += 35;
|
||||
}
|
||||
|
||||
}
|
||||
height += dinyHeight;
|
||||
|
||||
this.getStatus();
|
||||
StringBuilder filePath = new StringBuilder(saveDir);
|
||||
// 校验是否选中了模板,如果没选中模板的话则不需要另外生成了
|
||||
if ( !StringUtils.isEmpty(templateName) && !StringUtils.isEmpty(saveDir)){
|
||||
if (!StringUtils.isEmpty(templateName) && !StringUtils.isEmpty(saveDir)) {
|
||||
byte[] image = new TemplateUtils().generateReceiptImage(data, templateName, width, height, filePath);
|
||||
}else {
|
||||
} else {
|
||||
throw new RRException("模板渲染错误");
|
||||
}
|
||||
|
||||
String[] deletePathList = new String[(int) Math.ceil((double) height / MAX_HEIGHT) + 1];
|
||||
// 计算切割的块数
|
||||
int numPieces = (int) Math.ceil((double) height / MAX_HEIGHT);
|
||||
|
||||
String[] deletePathList = new String[numPieces + 1];
|
||||
deletePathList[0] = filePath.toString();
|
||||
String[] filePathList = new String[(int) Math.ceil((double) height / MAX_HEIGHT)];
|
||||
String[] filePathList = new String[numPieces];
|
||||
// 对图片进行分块处理,当前台式打印机最大参数配置 宽度690,高度1200
|
||||
if ( height > MAX_HEIGHT){
|
||||
if (height > MAX_HEIGHT) {
|
||||
try {
|
||||
// 读取输入图片
|
||||
BufferedImage originalImage = ImageIO.read(new File(filePath.toString()));
|
||||
|
||||
// 计算切割的块数
|
||||
int numPieces = (int) Math.ceil((double) height / MAX_HEIGHT);
|
||||
|
||||
// 循环切割图片并保存每一块
|
||||
for (int i = 0; i < numPieces; i++) {
|
||||
int startY = i * MAX_HEIGHT;
|
||||
int pieceHeight = Math.min(MAX_HEIGHT, height - startY);
|
||||
|
||||
// 创建一个新的 BufferedImage 对象,用于存储切割的部分
|
||||
BufferedImage piece = new BufferedImage(FIXED_WIDTH, pieceHeight, BufferedImage.TYPE_INT_RGB);
|
||||
piece.getGraphics().drawImage(originalImage, 0, 0, FIXED_WIDTH, pieceHeight, 0, startY, FIXED_WIDTH, startY + pieceHeight, null);
|
||||
BufferedImage piece = new BufferedImage(width, pieceHeight, BufferedImage.TYPE_INT_RGB);
|
||||
piece.getGraphics().drawImage(originalImage, 0, 0, width, pieceHeight, 0, startY, width, startY + pieceHeight, null);
|
||||
|
||||
// 保存切割的图片块
|
||||
File outputFile = new File(saveDir + "/output_" + (i + 1) + ".jpg");
|
||||
@@ -83,10 +150,10 @@ public class RegisterServiceImpl implements PrintService {
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("图片分段错误");
|
||||
log.error("模板切割失败");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
filePathList[0] = "LOGO1=" + filePath;
|
||||
}
|
||||
|
||||
@@ -108,7 +175,7 @@ public class RegisterServiceImpl implements PrintService {
|
||||
LexMarkResultDTO<LexMarkResultDTO.Param> paramLexMarkResultDTO = thirdService.callDevice(lexMarkDTO, LexMarkResultDTO.Param.class);
|
||||
|
||||
for (String path : deletePathList) {
|
||||
if ( path != null && !path.isEmpty()) {
|
||||
if (path != null && !path.isEmpty()) {
|
||||
File file = new File(path);
|
||||
// 检查文件是否存在
|
||||
if (file.exists()) {
|
||||
@@ -124,7 +191,6 @@ public class RegisterServiceImpl implements PrintService {
|
||||
}
|
||||
}
|
||||
|
||||
// this.thirdService.close("ReceiptPrinter");
|
||||
return paramLexMarkResultDTO;
|
||||
}
|
||||
|
||||
@@ -137,7 +203,7 @@ public class RegisterServiceImpl implements PrintService {
|
||||
lexMarkDTO.setPluginMethod("getInfo");
|
||||
// lexMarkDTO.setMethodType(0);
|
||||
LexMarkResultDTO<PrinterStatus> status = thirdService.callDevice(lexMarkDTO, PrinterStatus.class);
|
||||
if ( status.getResult() != 0){
|
||||
if (status.getResult() != 0) {
|
||||
thirdService.open("ReceiptPrinter", 1);
|
||||
status = thirdService.callDevice(lexMarkDTO, PrinterStatus.class);
|
||||
}
|
||||
@@ -149,16 +215,16 @@ public class RegisterServiceImpl implements PrintService {
|
||||
throw new RRException("获取打印机纸张状态出问题");
|
||||
}
|
||||
String[] papers = stPaperEx.split("\\|");
|
||||
if ( papers.length < 1) {
|
||||
if (papers.length < 1) {
|
||||
throw new RRException("打印机纸盒数量不对");
|
||||
}
|
||||
String paperStatus = papers[0];
|
||||
if ( !(paperStatus.equals(MS439PaperStatusEnum.PAPERFULL.getPrintCode()) || paperStatus.equals(MS439PaperStatusEnum.PAPERLOW.getPrintCode()))){
|
||||
if (!(paperStatus.equals(MS439PaperStatusEnum.PAPERFULL.getPrintCode()) || paperStatus.equals(MS439PaperStatusEnum.PAPERLOW.getPrintCode()))) {
|
||||
throw new RRException(500, paperStatus);
|
||||
}
|
||||
|
||||
// 校验打印机是否正常,除了HEALTHY都抛异常
|
||||
if (!ms439.getStDeviceStatus().equals(MS439DeviceStatusEnum.HEALTHY.getPrintCode()) ) {
|
||||
if (!ms439.getStDeviceStatus().equals(MS439DeviceStatusEnum.HEALTHY.getPrintCode())) {
|
||||
throw new RRException(500, ms439.getStDeviceStatus());
|
||||
}
|
||||
|
||||
@@ -168,13 +234,13 @@ public class RegisterServiceImpl implements PrintService {
|
||||
}
|
||||
|
||||
// 校验磁带 满或者少才放行
|
||||
if ( false && !(ms439.getStToner().equals(MS439TonerStatusEnum.TONERFULL.getPrintCode()) || ms439.getStToner().equals(MS439TonerStatusEnum.TONERLOW.getPrintCode()))){
|
||||
if (false && !(ms439.getStToner().equals(MS439TonerStatusEnum.TONERFULL.getPrintCode()) || ms439.getStToner().equals(MS439TonerStatusEnum.TONERLOW.getPrintCode()))) {
|
||||
throw new RRException(500, ms439.getStToner());
|
||||
}
|
||||
|
||||
// 校验墨盒 满或者少才放行,当前获取为未知
|
||||
if ( false && !(ms439.getStInk().equals(MS439InkStatusEnum.INKFULL.getPrintCode()) || ms439.getStInk().equals(MS439InkStatusEnum.INKLOW.getPrintCode())
|
||||
|| ms439.getStInk().equals(MS439InkStatusEnum.INKUNKNOWN.getPrintCode()))){
|
||||
if (false && !(ms439.getStInk().equals(MS439InkStatusEnum.INKFULL.getPrintCode()) || ms439.getStInk().equals(MS439InkStatusEnum.INKLOW.getPrintCode())
|
||||
|| ms439.getStInk().equals(MS439InkStatusEnum.INKUNKNOWN.getPrintCode()))) {
|
||||
throw new RRException(500, ms439.getStInk());
|
||||
}
|
||||
|
||||
|
||||
40
src/main/java/com/dpkj/modules/print/utils/FolderUtils.java
Normal file
40
src/main/java/com/dpkj/modules/print/utils/FolderUtils.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.dpkj.modules.print.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 文件夹处理
|
||||
*
|
||||
* @author <a href="https://gitee.com/shi-chongli">石头人</a>
|
||||
* @version 1.0
|
||||
* @since 2025-03-31 17:28:34
|
||||
*/
|
||||
@Slf4j
|
||||
public class FolderUtils {
|
||||
|
||||
public static void checkFolderAndCreate(String folderPath){
|
||||
File fileOrFolder = new File(folderPath);
|
||||
String targetPath;
|
||||
if (fileOrFolder.isFile()) {
|
||||
// 如果是文件,获取文件所在目录路径
|
||||
targetPath = fileOrFolder.getParent();
|
||||
} else {
|
||||
// 如果不是文件(可能是不存在的文件夹或已存在的文件夹),使用原始路径
|
||||
targetPath = folderPath;
|
||||
}
|
||||
|
||||
File targetFolder = new File(targetPath);
|
||||
if (!targetFolder.exists()) {
|
||||
boolean success = targetFolder.mkdirs();
|
||||
if (success) {
|
||||
log.info("文件夹创建成功: " + targetPath);
|
||||
} else {
|
||||
log.error("文件夹创建失败: " + targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
88
src/main/java/com/dpkj/modules/print/utils/PDFUtils.java
Normal file
88
src/main/java/com/dpkj/modules/print/utils/PDFUtils.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.dpkj.modules.print.utils;
|
||||
|
||||
import com.dpkj.common.exception.RRException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class PDFUtils {
|
||||
|
||||
private static final String defaultPath = "D:/images";
|
||||
|
||||
public enum AddressType {
|
||||
HTTP,
|
||||
LOCAL,
|
||||
OTHER,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验地址类型
|
||||
* @param address 地址
|
||||
*/
|
||||
public static AddressType checkAddressType(String address) {
|
||||
if (address == null || address.isEmpty()) {
|
||||
return AddressType.UNKNOWN;
|
||||
}
|
||||
|
||||
// 检查是否为 HTTP 或 HTTPS 地址
|
||||
if (address.startsWith("http://") || address.startsWith("https://")) {
|
||||
return AddressType.HTTP;
|
||||
}
|
||||
|
||||
// 简单判断是否为本地地址
|
||||
// 本地地址可能以文件协议 file:// 开头,或者是一个相对路径或绝对路径
|
||||
if (address.startsWith("file://") ||
|
||||
(address.contains(":") && address.contains("\\")) ||
|
||||
(address.startsWith("/"))) {
|
||||
return AddressType.LOCAL;
|
||||
}
|
||||
|
||||
return AddressType.OTHER;
|
||||
}
|
||||
|
||||
public static String downloadPdf(String pdfUrl) {
|
||||
String savePath = defaultPath + "/genera_image_" + System.currentTimeMillis() + ".pdf";
|
||||
try {
|
||||
// 创建 URL 对象
|
||||
URL url = new URL(pdfUrl);
|
||||
// 打开 HTTP 连接
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
// 设置请求方法为 GET
|
||||
connection.setRequestMethod("GET");
|
||||
|
||||
// 获取响应码
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
// 获取输入流
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
// 创建文件输出流
|
||||
FileOutputStream outputStream = new FileOutputStream(savePath)) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
// 循环读取数据并写入文件
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
log.info("PDF 文件下载成功,保存路径: " + savePath);
|
||||
}
|
||||
} else {
|
||||
log.error("下载失败,响应码: " + responseCode);
|
||||
}
|
||||
// 断开连接
|
||||
connection.disconnect();
|
||||
}catch (Exception e) {
|
||||
throw new RRException("PDF文件下载失败");
|
||||
}
|
||||
|
||||
return savePath;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -295,7 +295,7 @@ public class ReadCardServiceImpl implements ReadCardService {
|
||||
socialSecurityCardReader.setCallID(19256);
|
||||
socialSecurityCardReader.setDevName(LexMarkConst.CARD_READER);
|
||||
JSONObject socialSecurityCardReaderParam = new JSONObject();
|
||||
socialSecurityCardReaderParam.put("iType", 3);
|
||||
socialSecurityCardReaderParam.put("iType", 1);
|
||||
socialSecurityCardReader.setParam(socialSecurityCardReaderParam.toJSONString());
|
||||
log.info("[ReadCardServiceImpl][connectAndSocialSecurityCardReader][300]: 调用读取社保卡信息方法,参数: {}", socialSecurityCardReader);
|
||||
IDCardReadResultVO socialSecurityCardReaderResult = thirdServiceUtil.callDevice(socialSecurityCardReader, IDCardReadResultVO.class);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.dpkj.modules.scanface.ali.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @description: 支付宝刷脸配置信息
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/4/16 20:23
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "dpkj.ali.face")
|
||||
public class AliFaceConfig {
|
||||
|
||||
/**
|
||||
* ABCP_SDK部署后DLL文件路径
|
||||
*/
|
||||
private String dllPath;
|
||||
|
||||
/**
|
||||
* 应用管理-appid
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* IOT应用版本
|
||||
*/
|
||||
private String appVersion;
|
||||
|
||||
/**
|
||||
* 签约商家的 PID,以 2088 开头,企业主体
|
||||
*/
|
||||
private String merchantId;
|
||||
|
||||
/**
|
||||
* 商家机具终端编号,每台设备保持唯一
|
||||
*/
|
||||
private String deviceNum;
|
||||
|
||||
/**
|
||||
* 服务商的 PID
|
||||
*/
|
||||
private String partnerId;
|
||||
|
||||
/**
|
||||
* 核心入参 serviceId
|
||||
*/
|
||||
private String serviceId;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.dpkj.modules.scanface.ali.constants;
|
||||
|
||||
/**
|
||||
* @description: 支付宝刷脸模块常量
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/4/24 9:25
|
||||
*/
|
||||
public final class AliFaceConstants {
|
||||
|
||||
/**
|
||||
* 刷脸去初始化服务
|
||||
* https://ant-iot.alipay.com/open/iotbpaas/service/serviceManage/serviceDetail
|
||||
*/
|
||||
public static final String SMILEVERIFYNIN_V1 = "BPaaSSmileVerifyNonInitV1";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 刷脸支付服务
|
||||
* <a href="https://ant-iot.alipay.com/open/iotbpaas/app/appManage/newAppPublish/serviceDetail">...</a>
|
||||
*/
|
||||
public static final String SMILEPAY = "BPaaSFaceSmilePayVerify";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.dpkj.modules.scanface.ali.controller;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.scanface.ali.config.AliFaceConfig;
|
||||
import com.dpkj.modules.scanface.ali.constants.AliFaceConstants;
|
||||
import com.dpkj.modules.scanface.ali.dll.AbcpInvoke;
|
||||
import com.dpkj.modules.scanface.ali.service.IAliScanFaceService;
|
||||
import com.dpkj.modules.scanface.ali.vo.AliOrderVo;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2025/4/16 16:47
|
||||
* @Description:
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/scanface/ali")
|
||||
public class AliScanFaceController {
|
||||
|
||||
private final IAliScanFaceService aliScanFaceService;
|
||||
|
||||
@Resource
|
||||
private AliFaceConfig aliFaceConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 完整的统一的支付宝刷脸支付
|
||||
* 1、调用硬件刷脸获取到ftoken
|
||||
* 2、调用后端的支付宝支付、存入hispay
|
||||
* @param aliOrderVo
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@PostMapping("/aliFacePay")
|
||||
public Result<JSONObject> aliFacePay(@RequestBody AliOrderVo aliOrderVo) throws Exception {
|
||||
return aliScanFaceService.aliFacePay(aliOrderVo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ABCP初始化,
|
||||
* 文档地址:https://opendocs.alipay.com/iot/05e9ye
|
||||
* ABCP初始化:商家App 启动时通过调用接口 abcp_init 执行 ABCP SDK 的初始化,初始化动作只需启动时调用一次,初始化的时候需保证机具网络处于联网状态。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("iniAbcp")
|
||||
Result<Object> iniAbcp() {
|
||||
return aliScanFaceService.iniAbcpAbsolute();
|
||||
}
|
||||
|
||||
/**
|
||||
* ABCP服务调用 刷脸去初始化服务,获取ftoken
|
||||
* 文档地址:https://opendocs.alipay.com/iot/05e9ye
|
||||
* 初始化成功后,商家App 可根据业务需求,调用接口 abcp_start_service 执行 ABCP 所提供的服务。
|
||||
* 上述服务调用过程可重复多次调用,通过传入不同的 service_code 来调用不同的 ABCP 服务。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("abcpStartServiceIni")
|
||||
Result<Object> abcpStartServiceIni() {
|
||||
aliScanFaceService.iniAbcpAbsolute();
|
||||
|
||||
//参数
|
||||
JSONObject zolozConfig = new JSONObject().fluentPut("installAngle",90);
|
||||
JSONObject params = new JSONObject()
|
||||
.fluentPut("serviceId", aliFaceConfig.getServiceId())
|
||||
.fluentPut("zolozConfig",zolozConfig);
|
||||
String json = params.toJSONString();
|
||||
|
||||
String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码:初始化
|
||||
return aliScanFaceService.startServiceIni(json, service_code);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ABCP服务停用
|
||||
* 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用
|
||||
* 如处于支付环节,此次服务调用将不接受停止。
|
||||
* @param traceId
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("stopService")
|
||||
Result<Object> stopService(@RequestParam String traceId) {
|
||||
aliScanFaceService.iniAbcpAbsolute();
|
||||
//参数
|
||||
JSONObject params = new JSONObject()
|
||||
.fluentPut("traceId",traceId);
|
||||
//.fluentPut("service_code",AliFaceConstants.SMILEVERIFYNIN_V1); //
|
||||
String json = params.toJSONString();
|
||||
|
||||
String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码
|
||||
return aliScanFaceService.stopService(json, service_code);
|
||||
}
|
||||
}
|
||||
288
src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpInvoke.java
Normal file
288
src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpInvoke.java
Normal file
@@ -0,0 +1,288 @@
|
||||
package com.dpkj.modules.scanface.ali.dll;
|
||||
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Native;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Slf4j
|
||||
public class AbcpInvoke {
|
||||
|
||||
public interface CallbackStr {
|
||||
public void OnString(String result);
|
||||
}
|
||||
|
||||
public interface CallbackRsp {
|
||||
public void OnProcess(int code, String subCode, String subMsg, String result);
|
||||
|
||||
public void OnFinish(int code, String subCode, String subMsg, String result);
|
||||
}
|
||||
|
||||
|
||||
public static void SetAPIPathFile(String fileDylib) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
File file = new File(fileDylib);
|
||||
if (file.exists()) {
|
||||
try {
|
||||
msAbcpNativeDll = (AbcpNativeDll) Native.load(fileDylib, AbcpNativeDll.class);
|
||||
} catch (Throwable e) {
|
||||
msAbcpNativeDll = null;
|
||||
log.error("## 本地库未加载,返回错误 ERROR : load. ## [%s][%s] %n", fileDylib, e.getMessage());
|
||||
}
|
||||
} else {
|
||||
log.error("## ERROR : dylib NOT exist ## %s %n", fileDylib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpInit(String appId, String appVersion, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
appVersion = FixNullString(appVersion); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
try {
|
||||
msAbcpNativeDll.bp_api_set_isv_lan(3);// 0:cpp | 1:cshap | 2:jni | 3:jna
|
||||
} catch (Throwable e) {
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
try {
|
||||
msAbcpNativeDll.abcp_init(seedId, appId, appVersion, json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpGetMetaInfo(String appId, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
|
||||
try {
|
||||
msAbcpNativeDll.abcp_get_meta_info(seedId, appId, json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpStartService(String appId, String serviceCode, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
serviceCode = FixNullString(serviceCode); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
|
||||
Integer seedId;
|
||||
msLock.lock();
|
||||
try {
|
||||
seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
log.info("注册回调: seedId={}", seedId);
|
||||
} finally {
|
||||
msLock.unlock();
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("调用 abcp_start_service: seedId={}, appId={}, serviceCode={}, jsonLength={}", seedId, appId, serviceCode, json.length());
|
||||
msAbcpNativeDll.abcp_start_service(seedId, appId, serviceCode, json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
log.error("##本地方法调用异常 ERROR ## %s %n", e.getMessage());
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpTaskStopService(String appId, String serviceCode, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
serviceCode = FixNullString(serviceCode); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
|
||||
try {
|
||||
log.info("[调用abcp_stop_service]:seedId={}, appId={}, serviceCode={}, json={}", seedId, appId, serviceCode, json);
|
||||
msAbcpNativeDll.abcp_stop_service(seedId, appId, serviceCode, json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpZimIdInitTest(String metainfo, String smileType, String smileFlag, CallbackStr callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnString("{}");
|
||||
return;
|
||||
}
|
||||
metainfo = FixNullString(metainfo); // involved null ptr!
|
||||
smileType = FixNullString(smileType); // involved null ptr!
|
||||
smileFlag = FixNullString(smileFlag); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpString.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
|
||||
try {
|
||||
msAbcpNativeDll.abcp_zimid_init_for_test(seedId, metainfo, smileType, smileFlag, msABCPString);
|
||||
} catch (Throwable e) {
|
||||
callback.OnString("{}");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpTaskSubscribeEvent(String appId, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
|
||||
try {
|
||||
msAbcpNativeDll.abcp_call(seedId, appId, "SubscribeEvent", json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void AbcpTaskUnsubscribeEvent(String appId, String json, CallbackRsp callback) {
|
||||
if (msAbcpNativeDll == null) {
|
||||
callback.OnFinish(3, "E69001", "[java] api load failed!", "");
|
||||
return;
|
||||
}
|
||||
appId = FixNullString(appId); // involved null ptr!
|
||||
json = FixNullString(json); // involved null ptr!
|
||||
msLock.lock();
|
||||
Integer seedId = msSeedId++;
|
||||
msMapAbcpReponse.put(seedId, callback);
|
||||
msLock.unlock();
|
||||
|
||||
try {
|
||||
msAbcpNativeDll.abcp_call(seedId, appId, "UnsubscribeEvent", json, msABCPProcess, msABCPFinish);
|
||||
} catch (Throwable e) {
|
||||
callback.OnFinish(3, "E69001", e.getMessage(), "");
|
||||
log.error("## ERROR ## %s %n", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
private static AbcpNativeDll msAbcpNativeDll = null;
|
||||
private static ABCPProcess msABCPProcess = new ABCPProcess();
|
||||
private static ABCPFinish msABCPFinish = new ABCPFinish();
|
||||
private static ABCPString msABCPString = new ABCPString();
|
||||
private static Lock msLock = new ReentrantLock();
|
||||
private static Integer msSeedId = Integer.valueOf(256);
|
||||
private static Map<Integer, CallbackStr> msMapAbcpString = new HashMap<Integer, CallbackStr>();
|
||||
private static Map<Integer, CallbackRsp> msMapAbcpReponse = new HashMap<Integer, CallbackRsp>();
|
||||
|
||||
private static String FixNullString(String str) {
|
||||
if (str.equals(null)) {
|
||||
return "";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static Integer getMsSeedId() {
|
||||
return msSeedId;
|
||||
}
|
||||
|
||||
// =====================================
|
||||
public interface FuncProcess extends Callback {
|
||||
public void onProcess(int seed_id, int code, String subCode, String subMsg, String result);
|
||||
}
|
||||
|
||||
public interface FuncFinish extends Callback {
|
||||
public void onFinish(int seed_id, int code, String subCode, String subMsg, String result);
|
||||
}
|
||||
|
||||
public interface FuncString extends Callback {
|
||||
public void onString(int seed_id, String result);
|
||||
}
|
||||
|
||||
public static class ABCPProcess implements FuncProcess {
|
||||
public void onProcess(int seed_id, int _code, String subCode, String subMsg, String result) {
|
||||
log.info("[ABCPProcess][onProcess][243][onProcess:][seed_id:{}],[_code:{}],[subMsg:{}],[result:{}]", seed_id, _code, subCode, subMsg, result);
|
||||
Integer seedId = Integer.valueOf(seed_id);
|
||||
CallbackRsp callback = null;
|
||||
msLock.lock();
|
||||
if (msMapAbcpReponse.containsKey(seedId)) {
|
||||
callback = msMapAbcpReponse.get(seedId);
|
||||
}
|
||||
msLock.unlock();
|
||||
|
||||
if (callback != null) {
|
||||
Integer code = Integer.valueOf(_code);
|
||||
callback.OnProcess(code, subCode, subMsg, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ABCPFinish implements FuncFinish {
|
||||
public void onFinish(int seed_id, int _code, String subCode, String subMsg, String result) {
|
||||
//log.info("[ABCPFinish][onFinish][261][ABCPFinish数据:][seed_id:{}],[_code:{}],[subMsg:{}],[result:{}]", seed_id, _code, subCode, subMsg, result);
|
||||
Integer seedId = Integer.valueOf(seed_id);
|
||||
CallbackRsp callback = null;
|
||||
|
||||
msLock.lock();
|
||||
if (msMapAbcpReponse.containsKey(seedId)) {
|
||||
callback = msMapAbcpReponse.remove(seedId);
|
||||
}
|
||||
msLock.unlock();
|
||||
|
||||
if (callback != null) {
|
||||
Integer code = Integer.valueOf(_code);
|
||||
callback.OnFinish(code, subCode, subMsg, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ABCPString implements FuncString {
|
||||
public void onString(int seed_id, String result) {
|
||||
Integer seedId = Integer.valueOf(seed_id);
|
||||
CallbackStr callback = null;
|
||||
|
||||
msLock.lock();
|
||||
if (msMapAbcpString.containsKey(seedId)) {
|
||||
callback = msMapAbcpString.remove(seedId);
|
||||
}
|
||||
msLock.unlock();
|
||||
|
||||
if (callback != null) {
|
||||
callback.OnString(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.dpkj.modules.scanface.ali.dll;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Library;
|
||||
|
||||
public interface AbcpNativeDll extends Library {
|
||||
void bp_api_set_isv_lan(Integer type_isv_language);// 0:cpp | 1:cshap | 2:jni | 3:jna
|
||||
|
||||
void abcp_init(Integer arg, String app_id, String app_version, String json_param, Callback on_process, Callback on_finish);
|
||||
|
||||
void abcp_start_service(Integer arg, String app_id, String service_code, String json_param, Callback on_process, Callback on_finish);
|
||||
|
||||
void abcp_stop_service(Integer arg, String app_id, String service_code, String json_param, Callback on_process, Callback on_finish);
|
||||
|
||||
void abcp_get_meta_info(Integer arg, String app_id, String json_param, Callback on_process, Callback on_finish);
|
||||
|
||||
void abcp_stop_smile(Integer arg, String app_id, String json_param, Callback on_process, Callback on_finish);
|
||||
|
||||
void abcp_zimid_init_for_test(Integer arg, String zimmetainfo, String smileType, String smileFlag, Callback callback);
|
||||
|
||||
void abcp_call(Integer arg, String app_id, String call_method, String json_param, Callback on_process, Callback on_finish);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.dpkj.modules.scanface.ali.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.scanface.ali.vo.AliOrderVo;
|
||||
|
||||
public interface IAliScanFaceService {
|
||||
|
||||
/**
|
||||
* 完整的统一的支付宝刷脸支付
|
||||
* 1、调用硬件刷脸获取到ftoken
|
||||
* 2、调用后端的支付宝支付、存入hispay记录
|
||||
* @param aliOrderVo
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
Result<JSONObject> aliFacePay(AliOrderVo aliOrderVo);
|
||||
|
||||
/**
|
||||
* 初始化,调用阿里的ABCP_SDK部署出来的代码中的API文件
|
||||
* https://opendocs.alipay.com/iot/05e9ye#ABCP%E5%88%9D%E5%A7%8B%E5%8C%96
|
||||
*/
|
||||
Result<Object> iniAbcpAbsolute();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ABCP服务调用 刷脸去初始化服务,获取ftoken
|
||||
* 接受调用者传入的参数信息,执行指定服务(service_code),然后通过回调返回服务结果。
|
||||
* https://opendocs.alipay.com/iot/05e9ye#ABCP%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8
|
||||
* @param json 组装参数
|
||||
* @param service_code 所要调用的组件编码
|
||||
* @return
|
||||
*/
|
||||
Result<Object> startServiceIni(String json,String service_code);
|
||||
|
||||
|
||||
/**
|
||||
* ABCP服务停用
|
||||
* 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用,然后通过回调返回服务终止结果
|
||||
* 如处于支付环节,此次服务调用将不接受停止。
|
||||
* @param json 组装参数
|
||||
* @param service_code 组件编码
|
||||
* @return
|
||||
*/
|
||||
Result<Object> stopService(String json,String service_code);
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package com.dpkj.modules.scanface.ali.service.impl;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.scanface.ali.config.AliFaceConfig;
|
||||
import com.dpkj.modules.scanface.ali.constants.AliFaceConstants;
|
||||
import com.dpkj.modules.scanface.ali.dll.AbcpInvoke;
|
||||
import com.dpkj.modules.scanface.ali.service.IAliScanFaceService;
|
||||
import com.dpkj.modules.scanface.ali.vo.AliOrderVo;
|
||||
import jodd.util.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
||||
/**
|
||||
* @Auther: 萧道子
|
||||
* @Date: 2025/4/16 16:47
|
||||
* @Description:
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@Component
|
||||
public class AliScanFaceServiceImpl implements IAliScanFaceService {
|
||||
|
||||
@Value("${dpkj.serverurl}")
|
||||
private String serverUrl;
|
||||
|
||||
@Resource
|
||||
private AliFaceConfig aliFaceConfig;
|
||||
|
||||
|
||||
/**
|
||||
* @description: * 完整的统一的支付宝刷脸支付
|
||||
* 1、调用硬件刷脸获取到ftoken
|
||||
* 2、调用后端的支付宝支付、存入hispay
|
||||
* @author: zhangxue
|
||||
* @date: 2025/5/7 9:45
|
||||
* @Param [aliOrderVo]
|
||||
* @return: com.dpkj.common.vo.Result<com.alibaba.fastjson.JSONObject>
|
||||
*/
|
||||
@Override
|
||||
public Result<JSONObject> aliFacePay(AliOrderVo aliOrderVo) {
|
||||
try {
|
||||
/**
|
||||
* 1、获取刷脸"刷脸去初始化服务"的ftoken返回值
|
||||
*/
|
||||
this.iniAbcpAbsolute();
|
||||
//参数
|
||||
JSONObject zolozConfig = new JSONObject().fluentPut("installAngle", 90);
|
||||
JSONObject params = new JSONObject()
|
||||
.fluentPut("serviceId", aliFaceConfig.getServiceId())
|
||||
.fluentPut("zolozConfig", zolozConfig);
|
||||
String json = params.toJSONString();
|
||||
String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码:初始化
|
||||
Result<Object> startServiceIniResult = this.startServiceIni(json, service_code);
|
||||
log.info("[AliScanFaceServiceImpl][aliFacePay][72][调用ABCP调用刷脸的结果] :{}", startServiceIniResult.toString());
|
||||
if (startServiceIniResult.isSuccess()) {
|
||||
Map<String, String> res = (Map<String, String>) startServiceIniResult.getResult();
|
||||
String ftoken = res.get("ftoken");
|
||||
log.info("[AliScanFaceServiceImpl][aliFacePay][76][调用ABCP调用刷脸的结果的ftoken] :{}", ftoken.toString());
|
||||
|
||||
/**
|
||||
* 2、调用后端的支付宝统一收单交易支付接口、存入hisPay
|
||||
*/
|
||||
aliOrderVo.setAuthCode(ftoken);//Demo值:"fp128d26333fa66e66e7f34c493d30cdh76"
|
||||
JSONObject serverParams = (JSONObject) JSON.toJSON(aliOrderVo);
|
||||
|
||||
String url = serverUrl + "openapi/aliPayOrderApi/createOrder";
|
||||
log.info("[AliScanFaceServiceImpl][aliFacePay][85][调用后端的支付宝统一收单交易支付接口路径] :{}", serverUrl);
|
||||
log.info("[AliScanFaceServiceImpl][aliFacePay][85][调用后端的支付宝统一收单交易支付接口参数] :{}", serverParams.toJSONString());
|
||||
String req = HttpRequest.post(url)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.body(serverParams.toJSONString())
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject serverResult = JSONObject.parseObject(req);
|
||||
log.info("[AliScanFaceServiceImpl][aliFacePay][93][调用后端的支付宝统一收单交易支付接口结果] :{}", serverResult.toString());
|
||||
|
||||
return Result.ok(serverResult);
|
||||
} else {
|
||||
//调用ABCP 刷脸初始化服务失败
|
||||
log.error("[AliScanFaceServiceImpl][aliFacePay][299]调用ABCP 刷脸初始化服务失败 :{}", startServiceIniResult.getMessage());
|
||||
return Result.error(startServiceIniResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("[AliScanFaceServiceImpl][aliFacePay][302][整个支付宝刷脸模块出现失败:] :{}", e.getMessage());
|
||||
return Result.error("支付宝刷脸失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description: 使用绝对路径调用ABCP_SDK部署出来的AbcpInvoke类
|
||||
* @author: zhangxue
|
||||
* @date: 2025/5/7 9:45
|
||||
* @Param []
|
||||
* @return: com.dpkj.common.vo.Result<java.lang.Object>
|
||||
*/
|
||||
@Override
|
||||
public Result<Object> iniAbcpAbsolute() {
|
||||
try {
|
||||
File dllFile = new File(aliFaceConfig.getDllPath());
|
||||
System.out.println("DLL 存在: " + dllFile.exists());
|
||||
if (dllFile.exists()) {
|
||||
|
||||
//组装参数
|
||||
JSONObject params = new JSONObject()
|
||||
.fluentPut("appId", aliFaceConfig.getAppId()) //应用ID
|
||||
.fluentPut("merchantId", aliFaceConfig.getMerchantId()) //签约商家的 PID,以 2088 开头
|
||||
.fluentPut("deviceNum", aliFaceConfig.getDeviceNum())//商家机具终端编号,每台设备保持唯一
|
||||
.fluentPut("partnerId", aliFaceConfig.getPartnerId());//服务商的 PID
|
||||
String json = params.toJSONString();
|
||||
|
||||
//指定支付宝LOT SDK的本地库路径
|
||||
AbcpInvoke.SetAPIPathFile(aliFaceConfig.getDllPath());
|
||||
|
||||
//创建回调实例
|
||||
AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() {
|
||||
@Override
|
||||
public void OnProcess(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][OnProcess][123][ABCP调用iniAbcpAbsolute][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnFinish(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][OnFinish][128][ABCP调用iniAbcpAbsolute][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result);
|
||||
}
|
||||
};
|
||||
|
||||
//初始化
|
||||
AbcpInvoke.AbcpInit(aliFaceConfig.getAppId(), aliFaceConfig.getAppVersion(), json, callbackRsp);
|
||||
|
||||
return Result.ok("支付宝ABCP初始化成功");
|
||||
} else {
|
||||
log.info("[AliScanFaceServiceImpl][iniAbcpAbsolute][55][{} :文件不存在]", aliFaceConfig.getDllPath());
|
||||
return Result.error("支付宝ABCP初始化失败:" + aliFaceConfig.getDllPath() + "不存在");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("初始化失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ABCP服务调用 刷脸初始化服务,获取ftoken
|
||||
* 接受调用者传入的参数信息,执行指定服务(service_code),然后通过回调返回服务结果。
|
||||
* https://opendocs.alipay.com/iot/05e9ye#ABCP%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8
|
||||
*
|
||||
* @param json 组装参数
|
||||
* @param service_code 所要调用的组件编码
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Result<Object> startServiceIni(String json, String service_code) {
|
||||
try {
|
||||
String appId = aliFaceConfig.getAppId();
|
||||
|
||||
// 使用 CountDownLatch 实现线程同步
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latchFinish = new CountDownLatch(1);
|
||||
AtomicReference<Result<Object>> finishResultRef = new AtomicReference<>();
|
||||
|
||||
//获取返回数据
|
||||
AtomicReference<Integer> processCode = new AtomicReference<>();
|
||||
AtomicReference<String> processResult = new AtomicReference<>();
|
||||
AtomicReference<Integer> finishCode = new AtomicReference<>();
|
||||
AtomicReference<String> finishResult = new AtomicReference<>();
|
||||
|
||||
|
||||
//接收结果
|
||||
Map<String, String> res = new HashMap<>();
|
||||
|
||||
AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() {
|
||||
@Override
|
||||
public void OnProcess(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][OnProcess][189][service_code:{}][code:{}][subCode:{}][subMsg:{}][result:{}]", service_code, code, subCode, subMsg, result);
|
||||
try {
|
||||
processCode.set(code);
|
||||
processResult.set(result);
|
||||
} finally {
|
||||
latch.countDown(); // 确保无论如何都释放锁
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnFinish(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][OnFinish][200][ABCP服务调用 刷脸初始化服务,获取ftoken:][service_code:{}][code:{}][subCode:{}][subMsg:{}][result:{}]", service_code, code, subCode, subMsg, result);
|
||||
|
||||
/**Demo示例记录
|
||||
* [service_code:BPaaSSmileVerifyNonInitV1][code:1000][subCode:E00000][subMsg:SUCCESS][result:{"code":1000,"subCode":"OK_SUCCESS","subMessage":"SUCCESS","barCode":"281215320962898068","ftoken":"fp1efd3d4c0230a28f5261efe7c5050eh28","alipayUid":"2088812449506047","accountList":"[\"104***@qq.com\"]","authToken":"44686f7195c77ee2e09c09bcdc657dd5h28i","result":{"accountList":["104***@qq.com"],"alipayUid":"2088812449506047","allowRetry":false,"authToken":"44686f7195c77ee2e09c09bcdc657dd5h28i","barCode":"281215320962898068","certName":"您好,*雪","ftoken":"fp1efd3d4c0230a28f5261efe7c5050eh28","type":"selectUid"},"easterEgg":false,"zolozConfig":{"installAngle":90},"serviceId":"pay","traceId":"2444-44-1745802328","callStartTimeMs":1745802328114,"localTime":"2025-04-28-09-05-41-791"}]
|
||||
*/
|
||||
try {
|
||||
finishCode.set(code);
|
||||
finishResult.set(result);
|
||||
} finally {
|
||||
latchFinish.countDown(); // 确保无论如何都释放锁
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//调用
|
||||
log.info("[AliScanFaceServiceImpl][startService][141][ABCP调用刷脸初始化服务-调用AbcpStartService参数][appId:{}][service_code:{}][json:{}] ", appId, service_code, json.toString());
|
||||
AbcpInvoke.AbcpStartService(appId, service_code, json, callbackRsp);
|
||||
|
||||
// 等待process回调完成(设置超时避免死锁)
|
||||
boolean awaitSuccess = latch.await(10, TimeUnit.SECONDS);
|
||||
if (!awaitSuccess) {
|
||||
log.info("[AliScanFaceServiceImpl][startServiceIni][221][service_code:{}][ABCP调用刷脸:等待process回调超时]", service_code);
|
||||
return Result.error("等待process回调超时");
|
||||
} else {
|
||||
if (processCode.get() == 0) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(processResult.get());
|
||||
log.info("[AliScanFaceServiceImpl][startServiceIni][225][service_code:{}][ABCP调用刷脸-等待process回调完成结果] :{}", service_code, jsonObject.toString());
|
||||
if (jsonObject.containsKey("traceId")) {
|
||||
res.put("traceId", jsonObject.getString("traceId"));
|
||||
} else {
|
||||
return Result.error("ABCP调用刷脸初始化服务process失败,返回结果无traceId");
|
||||
}
|
||||
} else {
|
||||
return Result.error("ABCP调用刷脸初始化服务process失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 等待finish回调完成(设置超时避免死锁)
|
||||
boolean awaitFinishSuccess = latchFinish.await(120, TimeUnit.SECONDS);
|
||||
if (!awaitFinishSuccess) {
|
||||
return Result.error("等待finish回调超时");
|
||||
} else {
|
||||
if (finishCode.get() == 1000) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(finishResult.get());
|
||||
log.info("[AliScanFaceServiceImpl][startServiceIni][244][service_code:{}] [ABCP调用刷脸-等待finish回调完成结果]:{}", service_code, jsonObject.toString());
|
||||
if (jsonObject.containsKey("ftoken")) {
|
||||
res.put("ftoken", jsonObject.getString("ftoken"));//ftoken参数的有效期为2分钟
|
||||
res.put("barCode", jsonObject.getString("barCode"));
|
||||
finishResultRef.set(Result.ok("ABCP调用刷脸初始化finish服务成功", res));
|
||||
} else {
|
||||
return Result.ok("ABCP调用刷脸初始化服务finish失败,返回结果无ftoken");
|
||||
}
|
||||
} else {
|
||||
return Result.error("ABCP调用刷脸初始化服务finish失败");
|
||||
}
|
||||
}
|
||||
|
||||
//结果返回
|
||||
return finishResultRef.get();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.info("[AliScanFaceServiceImpl][startServiceIni][261][ABCP调用刷脸初始化服务失败] :{}", e.getMessage());
|
||||
return Result.error("ABCP调用刷脸初始化服务失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ABCP服务停用
|
||||
* 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用,然后通过回调返回服务终止结果
|
||||
* 如处于支付环节,此次服务调用将不接受停止。
|
||||
*
|
||||
* @param json 组装参数
|
||||
* @param service_code 组件编码
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Result<Object> stopService(String json, String service_code) {
|
||||
try {
|
||||
String appId = aliFaceConfig.getAppId();
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<Result<Object>> resultRef = new AtomicReference<>();
|
||||
AtomicReference<Integer> returnCode = new AtomicReference<>();
|
||||
AtomicReference<String> returnResult = new AtomicReference<>();
|
||||
|
||||
AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() {
|
||||
@Override
|
||||
public void OnProcess(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][261][ABCP服务停用-OnProcess][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnFinish(int code, String subCode, String subMsg, String result) {
|
||||
log.info("[AliScanFaceServiceImpl][OnFinish][266][ABCP服务停用-OnFinish] [code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result);
|
||||
try {
|
||||
returnCode.set(code);
|
||||
returnResult.set(result);
|
||||
} finally {
|
||||
latch.countDown(); // 确保无论如何都释放锁
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//调用
|
||||
log.info("[AliScanFaceServiceImpl][stopService][222][调用AbcpTaskStopService参数][appId:{}][service_code:{}][json:{}] ", appId, service_code, json.toString());
|
||||
AbcpInvoke.AbcpTaskStopService(appId, service_code, json, callbackRsp);
|
||||
|
||||
// 等待回调完成(设置超时避免死锁)
|
||||
boolean awaitSuccess = latch.await(60, TimeUnit.SECONDS);
|
||||
if (!awaitSuccess) {
|
||||
return Result.error("等待回调超时");
|
||||
} else {
|
||||
if (returnCode.get() == 0) {
|
||||
System.out.println("停止结果:" + returnResult.get());
|
||||
} else {
|
||||
return Result.error("ABCP停止服务失败");
|
||||
}
|
||||
}
|
||||
|
||||
//结果返回
|
||||
return resultRef.get();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("ABCP服务停用失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.dpkj.modules.scanface.ali.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @description: 支付宝支付参数,传入统一下单接口
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/4/29 14:24
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class AliOrderVo implements Serializable {
|
||||
|
||||
/**
|
||||
* 付款模块
|
||||
*/
|
||||
private String eventModule;
|
||||
|
||||
/**
|
||||
* 患者Id
|
||||
*/
|
||||
private String patientId;
|
||||
|
||||
/**
|
||||
* 商户机具终端编号
|
||||
*/
|
||||
private String terminalId;
|
||||
|
||||
/**
|
||||
* 系统订单编号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 用户支付金额
|
||||
*/
|
||||
//@ApiModelProperty(value = "用户支付金额")
|
||||
private String totalAmount;
|
||||
|
||||
/**
|
||||
* 订单标题
|
||||
*/
|
||||
//@ApiModelProperty(value = "订单标题")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 支付授权码
|
||||
* 刷脸标识串(fp开头的35位字符串)
|
||||
* 从调用"BPaaSSmileVerifyNonInitV1"服务中获取
|
||||
*/
|
||||
//@ApiModelProperty(value = "支付授权码")
|
||||
private String authCode;
|
||||
|
||||
/**
|
||||
* 支付场景
|
||||
* bar_code:当面付条码支付场景;默认值
|
||||
* security_code:当面付刷脸支付场景,对应的auth_code为fp开头的刷脸标识串;
|
||||
*/
|
||||
//@ApiModelProperty(value = "支付授权码")
|
||||
private String scene;
|
||||
|
||||
/**
|
||||
* 产品码:商家和支付宝签约的产品码
|
||||
* 当面付场景下,如果签约的是当面付快捷版,则传 OFFLINE_PAYMENT;
|
||||
* 其它支付宝当面付产品传 FACE_TO_FACE_PAYMENT;
|
||||
* 不传则默认使用FACE_TO_FACE_PAYMENT。
|
||||
*/
|
||||
//@ApiModelProperty(value = "订单标题")
|
||||
private String productCode;
|
||||
|
||||
/**
|
||||
* 卖家支付宝用户ID
|
||||
*/
|
||||
//@ApiModelProperty(value = "订单标题")
|
||||
private String sellerId;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.dpkj.modules.scanface.wx.config;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/28 9:38
|
||||
*/
|
||||
|
||||
public class WechatUrlConfig {
|
||||
|
||||
/**
|
||||
* 获取证书
|
||||
*/
|
||||
public static final String CERTIFICATESURL = "https://api.mch.weixin.qq.com/v3/certificates";
|
||||
|
||||
|
||||
/**
|
||||
* 退款地址
|
||||
*/
|
||||
public static final String REFUNDSURL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
|
||||
|
||||
/**
|
||||
* 创建支付分订单
|
||||
*/
|
||||
public static final String SERVICEORDER = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
|
||||
|
||||
|
||||
/**
|
||||
* 发起订单支付
|
||||
*/
|
||||
public static final String CREATEORDER = "https://api.mch.weixin.qq.com/pay/micropay";
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
*/
|
||||
public static final String ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
|
||||
|
||||
|
||||
/**
|
||||
* 撤销交易
|
||||
*/
|
||||
public static final String REVERSE = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
|
||||
|
||||
/**
|
||||
* 取消支付分订单前缀
|
||||
*/
|
||||
public static final String ORDERCANCELPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/";
|
||||
|
||||
/**
|
||||
* 取消支付分订单后缀
|
||||
*/
|
||||
public static final String ORDERCANCELSUFFIX = "/cancel";
|
||||
|
||||
/**
|
||||
* 完结支付分订单前缀
|
||||
*/
|
||||
public static final String SERVICEORDERCOMPLETEPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/";
|
||||
|
||||
|
||||
/**
|
||||
* 完结支付分订单后缀
|
||||
*/
|
||||
public static final String SERVICEORDERCOMPLETESUFFIX = "/complete";
|
||||
|
||||
/**
|
||||
* 获取调用凭证
|
||||
*/
|
||||
public static final String GET_WXPAYFACE_AUTHINFO = "https://payapp.weixin.qq.com/face/get_wxpayface_authinfo";
|
||||
|
||||
|
||||
/**
|
||||
* 查询订单
|
||||
*/
|
||||
public static final String FINDBYID = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
|
||||
|
||||
/**
|
||||
* 查询退款订单
|
||||
*/
|
||||
public static final String FINDREFUND = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.dpkj.modules.scanface.wx.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 公众号配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "dpkj.wx")
|
||||
public class WxMpProperties {
|
||||
|
||||
/**
|
||||
* 多个公众号配置信息
|
||||
*/
|
||||
private List<MpConfig> configs;
|
||||
@Data
|
||||
public static class MpConfig {
|
||||
/**
|
||||
* 设置微信公众号的appid
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 设置微信公众号的app secret
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 设置微信公众号的token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 设置微信公众号的EncodingAESKey
|
||||
*/
|
||||
private String aesKey;
|
||||
|
||||
/**
|
||||
* 微信:调用接口所需service_id
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 微信:商户号
|
||||
*/
|
||||
private String mchId;
|
||||
|
||||
/**
|
||||
* 微信支付商户密钥
|
||||
*/
|
||||
private String mchKey;
|
||||
|
||||
/**
|
||||
* 微信:报文解密V3密钥key
|
||||
*/
|
||||
private String v3Key;
|
||||
|
||||
/**
|
||||
* 微信: API密钥
|
||||
*/
|
||||
private String keyApi;
|
||||
|
||||
/**
|
||||
* 微信:密钥【apiclient_key.pem】地址
|
||||
p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头)
|
||||
*/
|
||||
private String keyPath;
|
||||
|
||||
/**
|
||||
* 微信:商家api序列号
|
||||
*/
|
||||
private String mchSerialNo;
|
||||
|
||||
/**
|
||||
* #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付
|
||||
*/
|
||||
private String tradeType;
|
||||
|
||||
/**
|
||||
* 微信:支付回调地址
|
||||
*/
|
||||
private String payNotifyUrl;
|
||||
|
||||
/**
|
||||
* 微信:退款回调地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 微信商户支付配置
|
||||
*/
|
||||
private MchConfig mchConfig;
|
||||
@Data
|
||||
public static class MchConfig{
|
||||
/**
|
||||
* 微信:APPID
|
||||
*/
|
||||
private String appId;
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 微信:商户号
|
||||
*/
|
||||
private String mchId;
|
||||
//微信支付商户密钥
|
||||
private String mchKey;
|
||||
|
||||
/**
|
||||
* 微信:报文解密V3密钥key
|
||||
*/
|
||||
private String v3Key;
|
||||
|
||||
/**
|
||||
* 微信: API密钥
|
||||
*/
|
||||
private String keyApi;
|
||||
|
||||
/**
|
||||
* 微信:密钥【apiclient_key.pem】地址
|
||||
p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头)
|
||||
*/
|
||||
private String keyPath;
|
||||
|
||||
/**
|
||||
* 退款证书 private-key-path V3
|
||||
* apiclient_key.pem 证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String privateKeyPath;
|
||||
|
||||
/**
|
||||
* 退款证书 private-cert-path
|
||||
* apiclient_cert.pem 证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String privateCertPath;
|
||||
|
||||
|
||||
/**
|
||||
* 微信:商家api序列号
|
||||
*/
|
||||
private String mchSerialNo;
|
||||
|
||||
/**
|
||||
* 微信:调用接口所需service_id
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
|
||||
/**
|
||||
* 微信:支付回调地址
|
||||
*/
|
||||
private String payNotifyUrl;
|
||||
|
||||
|
||||
/**
|
||||
* 微信:退款回调地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付
|
||||
*/
|
||||
private String tradeType;
|
||||
|
||||
/**
|
||||
* NATIVE--原生扫码支付
|
||||
*/
|
||||
private String tradeTypeNative;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
private MpUrl url;
|
||||
@Data
|
||||
public static class MpUrl {
|
||||
/**
|
||||
* H5地址
|
||||
*/
|
||||
private String h5;
|
||||
|
||||
/**
|
||||
* 接口地址
|
||||
*/
|
||||
private String server;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
package com.dpkj.modules.scanface.wx.controller;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.dpkj.common.constant.WxConstant;
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.scanface.wx.config.WxMpProperties;
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.service.WeChatPayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFaceOrderVo;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayReq;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayResp;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayOrderReverseRequest;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayOrderReverseResult;
|
||||
import jodd.util.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* @description: 微信刷脸支付
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/5/28 14:20
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/wxpayFace")
|
||||
public class WxFacePayController {
|
||||
@Resource
|
||||
private CallWxpayFaceService callWxpayFaceService;
|
||||
|
||||
@Resource
|
||||
private WeChatPayFaceService weChatPayFaceService;
|
||||
|
||||
@Value("${dpkj.serverurl}")
|
||||
private String serverUrl;
|
||||
|
||||
//赋值
|
||||
@Autowired
|
||||
private WxMpProperties wxMpProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 调用整个刷脸的完整流程
|
||||
*
|
||||
* @param wxFaceOrderVo
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping(value = "/doFacePay", method = RequestMethod.POST)
|
||||
public Result<String> doFacePay(@RequestBody WxFaceOrderVo wxFaceOrderVo) throws Exception {
|
||||
Result<String> result = new Result<>();
|
||||
try {
|
||||
//1 初始化
|
||||
this.initWxpayface();
|
||||
|
||||
//2 获取数据getWxpayfaceRawdata
|
||||
WxFacePayResp wxFacePayResp = this.getWxpayfaceRawdata();
|
||||
//String rawdata = wxFacePayResp.getRawdata();
|
||||
|
||||
//3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证)
|
||||
WxFacePayAuthinfoResp wxFacePayResp0 = this.getWxpayfaceAuthinfo(wxFacePayResp.getRawdata());
|
||||
|
||||
//4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
WxFacePayAuthinfoResp authinfoResp = this.getWxpayfaceCode(wxFaceOrderVo, wxFacePayResp0);
|
||||
|
||||
WxPayMicropayResult micropayResult = new WxPayMicropayResult();
|
||||
if (StringUtil.isNotBlank(authinfoResp.getFace_code())) {
|
||||
//5、调用后台人脸支付API发起支付
|
||||
wxFaceOrderVo.setOutTradeNo(getOutTradeNo());
|
||||
micropayResult = this.toCreateWxOrder(authinfoResp, wxFaceOrderVo, wxFacePayResp0);
|
||||
|
||||
//6、向后端查询订单状态 WxPayOrderQueryResult queryResult = queryOrderByNo(micropayResult);
|
||||
boolean isSuccess = pollOrderStatus(micropayResult);
|
||||
if (isSuccess) {
|
||||
//8、更新支付结果updateWxpayfacePayResult
|
||||
WxFacePayResp updFacePayResp = this.updateWxpayfacePayResult(wxFacePayResp0);
|
||||
|
||||
if (ObjUtil.isNotEmpty(updFacePayResp) && WxConstant.STATE_SUCCESS.equals(updFacePayResp.getReturn_code())) {
|
||||
//11、向后端发起更新订单,进行相应的操作、
|
||||
this.updatePayResult(micropayResult);
|
||||
|
||||
result = Result.ok("微信刷脸成功!", micropayResult.getOutTradeNo());
|
||||
}
|
||||
} else { //查询失败或者长时间未有结果
|
||||
//7、撤销交易reverse
|
||||
WxPayOrderReverseRequest reverseRequest = new WxPayOrderReverseRequest();
|
||||
reverseRequest.setTransactionId(micropayResult.getTransactionId());
|
||||
reverseRequest.setOutTradeNo(micropayResult.getOutTradeNo());
|
||||
WxPayOrderReverseResult reverseResult = this.toReverseOrder(reverseRequest);
|
||||
result = Result.error("微信刷脸失败后已经撤销订单!");
|
||||
}
|
||||
} else {
|
||||
result = Result.error("获取人脸识别支付凭证为空值");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.info("[WxFacePayController][doFacePay][84][微信刷脸完成流程出错] :{}", e.getMessage());
|
||||
result = Result.error("微信刷脸完成流程出错:" + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1 程序启动时初始化 :程序启动时初始化initWxpayface
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/initWxpayface", method = RequestMethod.POST)
|
||||
public WxFacePayResp initWxpayface() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("initWxpayface", "1", System.currentTimeMillis() / 1000, 1);
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
log.info("[WxFacePayController][initWxpayface][132] [1、程序启动时初始化:]:{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2 获取数据getWxpayfaceRawdata
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
public WxFacePayResp getWxpayfaceRawdata() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000);
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
log.info("[WxFacePayController][getWxpayfaceRawdata][146][2、获取数据getWxpayfaceRawdata:] :{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证)
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public WxFacePayAuthinfoResp getWxpayfaceAuthinfo(String rawdata) throws Exception {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayAuthinfoResp wxFacePayResp = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata);
|
||||
log.info("[WxFacePayController][getWxpayfaceAuthinfo][159][3、获取调用凭证get_wxpayface_authinfo:] :{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
*
|
||||
* @param wxFaceOrderVo
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST)
|
||||
public WxFacePayAuthinfoResp getWxpayfaceCode(@RequestBody WxFaceOrderVo wxFaceOrderVo, WxFacePayAuthinfoResp wxFacePayResp0) throws Exception {
|
||||
//4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
String outTradeNo = getOutTradeNo();//获取流水号
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000);
|
||||
wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo())
|
||||
.setOut_trade_no(outTradeNo)//订单流水号
|
||||
.setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分
|
||||
/**
|
||||
* 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付,
|
||||
* 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。
|
||||
*/
|
||||
.setFace_code_type("1");
|
||||
|
||||
WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq);
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][189][4、进行人脸识别结果:] :{}", authinfoResp.toString());
|
||||
|
||||
return authinfoResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从后端获取流水号,保证唯一性
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getOutTradeNo() {
|
||||
String url0 = serverUrl + "openapi/wxFacePayOrderApi/getOutTradeNo";
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][112][获取流水号地址] :{}", url0);
|
||||
String req0 = HttpRequest.post(url0)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject tradeNoResult = JSONObject.parseObject(req0);
|
||||
String outTradeNo = tradeNoResult.getString("result");
|
||||
log.info("[WxFacePayController][getOutTradeNo][208][获取流水号] :{}", outTradeNo);
|
||||
return outTradeNo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 5、向后端进行发起订单支付
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private WxPayMicropayResult toCreateWxOrder(WxFacePayAuthinfoResp authinfoResp, WxFaceOrderVo wxFaceOrderVo, WxFacePayAuthinfoResp wxFacePayResp0) {
|
||||
authinfoResp
|
||||
.setOut_trade_no(wxFaceOrderVo.getOutTradeNo())//订单流水号
|
||||
.setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分
|
||||
.setPatientId(wxFaceOrderVo.getPatientId())//患者Id
|
||||
.setEventModule(wxFaceOrderVo.getEventModule()) //付费模块
|
||||
.setTerminalId(wxFaceOrderVo.getTerminalId()) //商户机具终端编号
|
||||
.setAuthinfo(wxFacePayResp0.getAuthinfo())
|
||||
.setNonce_str(wxFacePayResp0.getNonce_str())
|
||||
.setSign(wxFacePayResp0.getSign());
|
||||
JSONObject serverParams = (JSONObject) JSON.toJSON(authinfoResp);
|
||||
String url = serverUrl + "openapi/wxFacePayOrderApi/createFaceOrder";
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][153][向后台发起微信刷脸订单创建路径] :{}", url);
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][153][调用后台人脸支付API发起支付参数] :{}", serverParams.toString());
|
||||
String req = HttpRequest.post(url)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.body(serverParams.toString())
|
||||
.execute()
|
||||
.body();
|
||||
log.info("[WxFacePayController][toCreateWxOrder][237][5、向后台发起进行发起订单支付 结果] :{}", req);
|
||||
JSONObject serverResult = JSONObject.parseObject(req);
|
||||
Map<String, Object> result = (Map<String, Object>) serverResult.get("result");
|
||||
log.info("[WxFacePayController][toCreateWxOrder][140][5、向后端进行发起订单支付请求结果] :{}", result.toString());
|
||||
|
||||
WxPayMicropayResult micropayResult = new WxPayMicropayResult();
|
||||
log.info("[WxFacePayController][toCreateWxOrder][243][5、调用后台人脸支付API发起支付结果:] :{}", result.toString());
|
||||
if (ObjUtil.isNotEmpty(result)) {
|
||||
micropayResult.setOutTradeNo(result.get("outTradeNo").toString());
|
||||
micropayResult.setTransactionId(result.get("transactionId").toString());
|
||||
micropayResult.setTotalFee(Integer.valueOf(result.get("totalFee").toString()));
|
||||
micropayResult.setReturnCode(result.get("returnCode").toString());
|
||||
}
|
||||
|
||||
return micropayResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 轮询逻辑封装方法
|
||||
* 查询微信刷脸订单状态
|
||||
* 30秒
|
||||
* 调用步骤6
|
||||
*
|
||||
* @param micropayResult
|
||||
* @return
|
||||
*/
|
||||
public boolean pollOrderStatus(WxPayMicropayResult micropayResult) {
|
||||
final long timeoutMillis = 30_000; // 30秒超时(毫秒)
|
||||
final long pollInterval = 2_000; // 轮询间隔2秒
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
while (true) {
|
||||
// 1. 查询订单状态
|
||||
WxPayOrderQueryResult queryResult = this.queryOrderByNo(micropayResult);
|
||||
log.info("[WxFacePayController][pollOrderStatus][272][1. 查询订单状态:] :{}", queryResult);
|
||||
|
||||
// 2. 检查成功状态
|
||||
if (WxConstant.STATE_SUCCESS.equals(queryResult.getReturnCode())) {
|
||||
log.info("[WxFacePayController][pollOrderStatus][275][订单支付成功!]");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 检查超时
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
if (elapsed >= timeoutMillis) {
|
||||
log.info("[WxFacePayController][pollOrderStatus][284][支付超时,未获取成功状态]");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 等待下一次轮询
|
||||
try {
|
||||
long nextPoll = Math.min(pollInterval, timeoutMillis - elapsed);
|
||||
TimeUnit.MILLISECONDS.sleep(nextPoll);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.info("[WxFacePayController][pollOrderStatus][293] [轮询被中断]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 6、向后端查询订单状态
|
||||
*
|
||||
* @param micropayResult
|
||||
* @return
|
||||
*/
|
||||
public WxPayOrderQueryResult queryOrderByNo(WxPayMicropayResult micropayResult) {
|
||||
WxPayOrderQueryResult queryResult = new WxPayOrderQueryResult();
|
||||
if (ObjUtil.isNotEmpty(micropayResult)) {
|
||||
JSONObject serverParams = (JSONObject) JSON.toJSON(micropayResult);
|
||||
String url = serverUrl + "openapi/wxFacePayOrderApi/queryOrderByNo";
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][184][6、向后台发起查询订单状态创建路径] :{}", url);
|
||||
log.info("[WxFacePayController][getWxpayfaceCode][186][6、向后端查询订单状态参数] :{}", serverParams.toString());
|
||||
String req = HttpRequest.post(url)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.body(serverParams.toString())
|
||||
.execute()
|
||||
.body();
|
||||
log.info("[WxFacePayController][queryOrderByNo][318][6、后台发起查询订单 结果:] :{}", req);
|
||||
|
||||
JSONObject serverResult = JSONObject.parseObject(req);
|
||||
Map<String, Object> result = (Map<String, Object>) serverResult.get("result");
|
||||
log.info("[WxFacePayController][toCreateWxOrder][140][6、后台发起查询订单 请求结果] :{}", result.toString());
|
||||
|
||||
if (ObjUtil.isNotEmpty(result)) {
|
||||
queryResult.setReturnCode(result.get("returnCode").toString());
|
||||
queryResult.setOutTradeNo(result.get("outTradeNo").toString());
|
||||
queryResult.setTransactionId(result.get("transactionId").toString());
|
||||
queryResult.setTotalFee(Integer.valueOf(result.get("totalFee").toString()));
|
||||
}
|
||||
|
||||
log.info("[WxFacePayController][toCreateWxOrder][140][ 6、向后端查询订单状态结果] :{}", micropayResult.toString());
|
||||
}
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 7、撤销交易reverse
|
||||
* 支付交易返回失败或支付系统超时,调用该接口撤销交易。
|
||||
* 如果此订单用户支付失败,微信支付系统会将此订单关闭;
|
||||
* 如果用户支付成功,微信支付系统会将此订单资金退还给用户。
|
||||
*
|
||||
* @param reverseRequest
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = "/toReverseOrder", method = RequestMethod.POST)
|
||||
public WxPayOrderReverseResult toReverseOrder(@RequestBody WxPayOrderReverseRequest reverseRequest) {
|
||||
WxPayOrderReverseResult reverseResult = new WxPayOrderReverseResult();
|
||||
if (ObjUtil.isNotEmpty(reverseRequest)) {
|
||||
JSONObject serverParams = (JSONObject) JSON.toJSON(reverseRequest);
|
||||
String url = serverUrl + "openapi/wxFacePayOrderApi/reverseOrder";
|
||||
log.info("[WxFacePayController][toReverseOrder][303][向后台发起撤销交易reverse路径] :{}", url);
|
||||
log.info("[WxFacePayController][toReverseOrder][304][调用后台发起撤销交易参数] :{}", serverParams.toString());
|
||||
String req = HttpRequest.post(url)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.body(serverParams.toString())
|
||||
.execute()
|
||||
.body();
|
||||
log.info("[WxFacePayController][toReverseOrder][359][向后台发起撤销交易 结果:] :{}", req);
|
||||
JSONObject serverResult = JSONObject.parseObject(req);
|
||||
reverseResult = (WxPayOrderReverseResult) serverResult.get("result");
|
||||
log.info("[WxFacePayController][toReverseOrder][314][7、向后台发起撤销交易请求结果] :{}", reverseResult.toString());
|
||||
}
|
||||
return reverseResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 8、更新支付结果updateWxpayfacePayResult
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/updateWxpayfacePayResult", method = RequestMethod.POST)
|
||||
public WxFacePayResp updateWxpayfacePayResult(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("updateWxpayfacePayResult", "1", System.currentTimeMillis() / 1000);
|
||||
wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId())
|
||||
.setMch_id(wxMpProperties.getMchConfig().getMchId())
|
||||
.setStore_id(WxConstant.STORE_ID)
|
||||
.setPayresult("SUCCESS")
|
||||
.setAuthinfo(wxFacePayResp0.getAuthinfo());
|
||||
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
log.info("[WxFacePayController][updateWxpayfacePayResult][385][8、更新支付结果updateWxpayfacePayResult] :{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 9、停止刷脸支付stopWxpayface
|
||||
* 仅在人脸凭证/付款码face_code【第4步骤】未返回前可用,face_code返回后不可停止
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/stopWxpayface", method = RequestMethod.POST)
|
||||
public WxFacePayResp stopWxpayface(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("stopWxpayface", "1", System.currentTimeMillis() / 1000);
|
||||
wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId())
|
||||
.setMch_id(wxMpProperties.getMchConfig().getMchId())
|
||||
.setStore_id(WxConstant.STORE_ID)
|
||||
.setAuthinfo(wxFacePayResp0.getAuthinfo());
|
||||
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
log.info("[WxFacePayController][stopWxpayface][407][9、停止刷脸支付stopWxpayface的结果:] :{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 10、释放资源releaseWxpayface
|
||||
* 首次刷脸需要initWxpayface,刷脸结束后不需要调用releaseWxpayface,否则下次刷脸需要重新initWxpayface,影响启动耗时;
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/releaseWxpayface", method = RequestMethod.POST)
|
||||
public WxFacePayResp releaseWxpayface(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("releaseWxpayface", "1", System.currentTimeMillis() / 1000);
|
||||
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
log.info("[WxFacePayController][releaseWxpayface][425][10、释放资源releaseWxpayface的结果:] :{}", wxFacePayResp.toString());
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 11、向后端发起更新状态为成功,进行相应的操作
|
||||
*
|
||||
* @param micropayResult
|
||||
* @return
|
||||
*/
|
||||
public WxPayOrderQueryResult updatePayResult(WxPayMicropayResult micropayResult) {
|
||||
WxPayOrderQueryResult queryResult = new WxPayOrderQueryResult();
|
||||
if (ObjUtil.isNotEmpty(micropayResult)) {
|
||||
JSONObject serverParams = (JSONObject) JSON.toJSON(micropayResult);
|
||||
String url = serverUrl + "openapi/wxFacePayOrderApi/updatePayResult";
|
||||
log.info("[WxFacePayController][updatePayResult][184][11、向后端发起更新状态为成功创建路径] :{}", url);
|
||||
log.info("[WxFacePayController][updatePayResult][186][11、向后端发起更新状态为成功参数] :{}", serverParams.toString());
|
||||
String req = HttpRequest.post(url)
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8))
|
||||
.body(serverParams.toString())
|
||||
.execute()
|
||||
.body();
|
||||
log.info("[WxFacePayController][updatePayResult][447][11、向后端发起更新状态为成功 结果:] :{}", req.toString());
|
||||
JSONObject jsonObject = JSONObject.parseObject(req);
|
||||
Map<String, Object> result = (Map<String, Object>) jsonObject.get("result");
|
||||
log.info("[WxFacePayController][updatePayResult][140][11、向后端发起更新状态为成功 请求结果] :{}", result.toString());
|
||||
}
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
/*@RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST)
|
||||
public Result<WxFacePayAuthinfoResp> getWxpayfaceCode(@RequestBody WxFaceOrderVo wxFaceOrderVo) throws Exception {
|
||||
//1 初始化
|
||||
this.initWxpayface();
|
||||
|
||||
//2 获取数据getWxpayfaceRawdata
|
||||
WxFacePayResp wxFacePayResp = this.getWxpayfaceRawdata();
|
||||
//String rawdata = wxFacePayResp.getRawdata();
|
||||
|
||||
//3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证)
|
||||
WxFacePayAuthinfoResp wxFacePayResp0 = this.getWxpayfaceAuthinfo(wxFacePayResp.getRawdata());
|
||||
|
||||
//4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
String outTradeNo = getOutTradeNo();//获取流水号
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000);
|
||||
wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo())
|
||||
.setOut_trade_no(outTradeNo)//订单流水号
|
||||
.setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分
|
||||
*/
|
||||
/**
|
||||
* 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付,
|
||||
* 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。
|
||||
*/
|
||||
/*
|
||||
.setFace_code_type("1");
|
||||
|
||||
WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq);
|
||||
System.out.println("**************4、进行人脸识别结果:" + authinfoResp.toString());
|
||||
if (StringUtil.isNotBlank(authinfoResp.getFace_code())) {
|
||||
//5、调用后台人脸支付API发起支付
|
||||
WxPayMicropayResult micropayResult = this.toCreateWxOrder(authinfoResp, wxFaceOrderVo, wxFacePayResp0);
|
||||
|
||||
//6、向后端查询订单状态
|
||||
//WxPayOrderQueryResult queryResult = queryOrderByNo(micropayResult);
|
||||
boolean isSuccess = pollOrderStatus(micropayResult);
|
||||
if (isSuccess) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(authinfoResp);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.dpkj.modules.scanface.wx.controller;
|
||||
|
||||
import com.dpkj.common.vo.Result;
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.service.WeChatPayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.service.impl.CallWxpayFaceServiceImpl;
|
||||
import com.dpkj.modules.scanface.wx.service.impl.WeChatPayFaceServiceImpl;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayReq;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayResp;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import jodd.util.StringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/28 16:43
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/wxpayFaceTest")
|
||||
public class WxpayFaceTestController {
|
||||
|
||||
@Resource
|
||||
private CallWxpayFaceService callWxpayFaceService;
|
||||
|
||||
@Resource
|
||||
private WeChatPayFaceService weChatPayFaceService;
|
||||
|
||||
/**
|
||||
* 1 程序启动时初始化 :程序启动时初始化initWxpayface
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/initWxpayface", method = RequestMethod.GET)
|
||||
public Result<WxFacePayResp> initWxpayface() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("initWxpayface", "1", System.currentTimeMillis() / 1000);
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
System.out.println("**************1、程序启动时初始化:" + wxFacePayResp.toString());
|
||||
return Result.ok(wxFacePayResp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 2 获取数据getWxpayfaceRawdata
|
||||
*
|
||||
* @return
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@RequestMapping(value = "/getWxpayfaceRawdata", method = RequestMethod.POST)
|
||||
public Result<WxFacePayResp> getWxpayfaceRawdata() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000);
|
||||
WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
System.out.println("**************2、获取数据:" + wxFacePayResp.toString());
|
||||
|
||||
String rawdata = wxFacePayResp.getRawdata();
|
||||
return Result.ok(wxFacePayResp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证)
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping(value = "/getWxpayfaceAuthinfo", method = RequestMethod.POST)
|
||||
public Result<WxFacePayAuthinfoResp> getWxpayfaceAuthinfo() throws Exception {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000);
|
||||
WxFacePayResp wxFacePayResp1 = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq);
|
||||
System.out.println("**************3、获取数据getWxpayfaceRawdata:" + wxFacePayResp1.toString());
|
||||
WxFacePayAuthinfoResp wxFacePayResp = new WxFacePayAuthinfoResp();
|
||||
if ("SUCCESS".equals(wxFacePayResp1.getReturn_code())) {
|
||||
String rawdata = wxFacePayResp1.getRawdata();
|
||||
// 构建请求参数的JSON字符串
|
||||
wxFacePayResp = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata);
|
||||
System.out.println("**************3、获取调用凭证get_wxpayface_authinfo:" + wxFacePayResp.toString());
|
||||
}
|
||||
return Result.ok(wxFacePayResp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST)
|
||||
public Result<WxFacePayAuthinfoResp> getWxpayfaceCode() throws Exception {
|
||||
WxFacePayAuthinfoResp wxFacePayResp0 = new WxFacePayAuthinfoResp();
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq0 = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000);
|
||||
WxFacePayResp wxFacePayResp1 = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq0);
|
||||
System.out.println("**************3、获取数据getWxpayfaceRawdata:" + wxFacePayResp1.toString());
|
||||
if ("SUCCESS".equals(wxFacePayResp1.getReturn_code())) {
|
||||
String rawdata = wxFacePayResp1.getRawdata();
|
||||
// 构建请求参数的JSON字符串
|
||||
wxFacePayResp0 = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata);
|
||||
System.out.println("**************3、获取调用凭证get_wxpayface_authinfo:" + wxFacePayResp0.toString());
|
||||
|
||||
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000);
|
||||
wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo())
|
||||
.setOut_trade_no("")//订单流水号
|
||||
.setTotal_fee(new String("1"));
|
||||
|
||||
WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq);
|
||||
System.out.println("**************4、进行人脸识别结果:" + authinfoResp.toString());
|
||||
if (StringUtil.isNotBlank(authinfoResp.getFace_code())) {
|
||||
//调用后台人脸支付API发起支付
|
||||
authinfoResp.setOut_trade_no(wxFacePayReq.getOut_trade_no())//订单流水号
|
||||
.setTotal_fee(wxFacePayReq.getTotal_fee());
|
||||
|
||||
|
||||
}
|
||||
|
||||
return Result.ok(authinfoResp);
|
||||
} else {
|
||||
return Result.error("微信获取调用凭证失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//5、进行发起订单支付
|
||||
|
||||
//可以打印 JDK 中的 Provider 列表,以及所有签名算法
|
||||
public void outProvider() {
|
||||
TreeSet<String> algorithms = new TreeSet<>();
|
||||
|
||||
Provider[] providers = Security.getProviders();
|
||||
System.out.println("-----Provider 列表如下:-----");
|
||||
for (Provider provider : providers) {
|
||||
System.out.println(provider.getName());
|
||||
}
|
||||
|
||||
System.out.println("-----支持的签名算法如下:-----");
|
||||
|
||||
for (Provider provider : providers) {
|
||||
for (Provider.Service service : provider.getServices())
|
||||
if (service.getType().equals("Signature")) {
|
||||
algorithms.add(service.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
for (String algorithm : algorithms) {
|
||||
System.out.println(algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.dpkj.modules.scanface.wx.dll;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @description: 32位的WxpayFaceSDK: /resources/win32-x86/WxpayFaceSDK.dll
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/4/17 17:02
|
||||
*/
|
||||
@Slf4j
|
||||
public class WxpayFaceSDKDll {
|
||||
|
||||
/**
|
||||
* 获取 Dll 实例,同时注册 Dll 控件。
|
||||
*
|
||||
* @return WxpayFaceSDKDll 实例
|
||||
* @throws DllRegistrationException 如果注册控件失败,抛出此异常
|
||||
*/
|
||||
public static Dll instance() throws DllRegistrationException {
|
||||
try {
|
||||
return Native.load("WxpayFaceSDK", Dll.class);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
log.info("[WxpayFaceSDK][instance][微信扫脸动态库] SDK注册失败:{}", e.getMessage());
|
||||
throw new DllRegistrationException("Failed to load WxpayFaceSDK library: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义自定义异常类,用于表示注册控件时发生的错误
|
||||
*/
|
||||
public static class DllRegistrationException extends Exception {
|
||||
public DllRegistrationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DllRegistrationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义接口映射本地库中的函数。
|
||||
*/
|
||||
public interface Dll extends Library {
|
||||
/**
|
||||
* 调用人脸服务
|
||||
* 注意:这里的方法签名需要与DLL中的C函数签名一致
|
||||
* req和resp都是JSON字符串,需要转换为Pointer类型
|
||||
* char * -->String; char ** -->String[]
|
||||
* int -->int; int* --> IntByReference
|
||||
* @param reqBuf 请求参数(JSON字符串)
|
||||
* @param reqSize 请求参数长度
|
||||
* @param pRespBuf 用来接收响应结果(JSON字符串)的char**指针
|
||||
* @param pRespSize 用来接收响应结果长度的unsigned int*指针
|
||||
* @return 如果成功返回0,失败则返回非0
|
||||
*/
|
||||
//int wxpayCallFaceService(String reqBuf, int reqSize, String[] pRespBuf, IntByReference pRespSize);
|
||||
int wxpayCallFaceService(String reqBuf, int reqSize, long[] pRespBuf, int[] pRespSize);
|
||||
|
||||
|
||||
/**
|
||||
* char ** -->String[]
|
||||
* 释放人脸服务的响应字符串,调用wxpayCallFaceService成功后务必调用此函数释放内存
|
||||
* @param pRespBuf 指向响应结果(JSON字符串)的指针
|
||||
*/
|
||||
void wxpayReleaseResponse(String[] pRespBuf);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.dpkj.modules.scanface.wx.service;
|
||||
|
||||
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
public interface CallWxpayFaceService {
|
||||
|
||||
/**
|
||||
* 调用SDK微信刷脸服务
|
||||
* @param reqJson
|
||||
* @param respJson
|
||||
* @return
|
||||
*/
|
||||
String callWxpayFaceService(String reqJson, List<String> respJson) throws UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.dpkj.modules.scanface.wx.service;
|
||||
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayReq;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayResp;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description: 调用微信刷脸方法
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/6 10:45
|
||||
*/
|
||||
public interface WeChatPayFaceService {
|
||||
|
||||
/**
|
||||
* 调用方法
|
||||
* 1、程序启动时初始化initWxpayface;2、获取数据getWxpayfaceRawdata;
|
||||
* 8、更新支付结果updateWxpayfacePayResult
|
||||
* 9、停止刷脸支付stopWxpayface
|
||||
* @param wxFacePayReq
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
WxFacePayResp doWxPayIniMethod(WxFacePayReq wxFacePayReq) throws UnsupportedEncodingException, JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description: 3、获取调用凭证get_wxpayface_authinfo(rawdata)
|
||||
* @author: zhangxue
|
||||
* @date: 2024/12/4 14:35
|
||||
* @Param rawData 初始化数据。由微信人脸SDK的接口返回。
|
||||
* @return: java.util.Map<java.lang.String,java.lang.String>
|
||||
*/
|
||||
WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 调用方法
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
* @param wxFacePayReq
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
WxFacePayAuthinfoResp getWxpayfaceCode(WxFacePayReq wxFacePayReq) throws UnsupportedEncodingException, JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.dpkj.modules.scanface.wx.service.impl;
|
||||
|
||||
import com.dpkj.modules.scanface.wx.config.WxMpProperties;
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService;
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description:
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/28 10:04
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CallWxpayFaceServiceImpl implements CallWxpayFaceService {
|
||||
//将 BouncyCastleProvider 添加到 Provider 列表中
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
//赋值
|
||||
@Autowired
|
||||
private WxMpProperties wxMpProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 调用SDK微信刷脸服务
|
||||
* @param reqJson
|
||||
* @param respJson
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String callWxpayFaceService(String reqJson, List<String> respJson) throws WxpayFaceSDKDll.DllRegistrationException {
|
||||
//组装请求数据
|
||||
Pointer reqPointer = new Memory(reqJson.length() + 1);
|
||||
reqPointer.setString(0, reqJson);
|
||||
//请求数据长度
|
||||
int reqSize = reqPointer.getString(0).length();
|
||||
System.out.println("-----------调用微信刷脸DLL请求数据----------"+reqPointer.getString(0));
|
||||
|
||||
//接收响应
|
||||
long[] pRespBuf = new long[1];
|
||||
int[] respSize = new int[1];
|
||||
String resStr = new String();
|
||||
|
||||
|
||||
// 调用本地方法
|
||||
WxpayFaceSDKDll.Dll dll = WxpayFaceSDKDll.instance();
|
||||
int result = dll.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize);
|
||||
//使用C:\Windows\System32目录下 int result = WxpayFaceSDK.INSTANCE.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize);
|
||||
|
||||
if (result == 0) {
|
||||
Pointer pointer = new Pointer(pRespBuf[0]);
|
||||
byte[] byteArray = pointer.getByteArray(0, respSize[0]);
|
||||
resStr = new String(byteArray, StandardCharsets.UTF_8);
|
||||
|
||||
System.out.println("-----------调用微信刷脸DLL请求结果: " + resStr);
|
||||
dll.wxpayReleaseResponse(new String[2]);
|
||||
//释放 C:\Windows\System32目录下 WxpayFaceSDK.INSTANCE.wxpayReleaseResponse(new String[2]);
|
||||
} else {
|
||||
Pointer pointer = new Pointer(pRespBuf[0]);
|
||||
byte[] byteArray = pointer.getByteArray(0, respSize[0]);
|
||||
resStr = new String(byteArray, StandardCharsets.UTF_8);
|
||||
System.err.println("-----------调用人脸服务失败: " +resStr);
|
||||
}
|
||||
return resStr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package com.dpkj.modules.scanface.wx.service.impl;
|
||||
|
||||
import com.dpkj.common.constant.WxConstant;
|
||||
import com.dpkj.modules.scanface.wx.config.WechatUrlConfig;
|
||||
import com.dpkj.modules.scanface.wx.config.WxMpProperties;
|
||||
import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll;
|
||||
import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.service.WeChatPayFaceService;
|
||||
import com.dpkj.modules.scanface.wx.util.WXPayUtil;
|
||||
import com.dpkj.modules.scanface.wx.util.WxRandomUtils;
|
||||
import com.dpkj.modules.scanface.wx.util.XmlParserUtil;
|
||||
import com.dpkj.modules.scanface.wx.util.XmlUtils;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayMicroPayResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayOrderResp;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayReq;
|
||||
import com.dpkj.modules.scanface.wx.vo.WxFacePayResp;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @description: 调用微信刷脸方法
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/6 10:45
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WeChatPayFaceServiceImpl implements WeChatPayFaceService {
|
||||
|
||||
@Autowired
|
||||
private CallWxpayFaceService callWxpayFaceService;
|
||||
|
||||
|
||||
//赋值
|
||||
@Autowired
|
||||
private WxMpProperties wxMpProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 调用方法
|
||||
* 1、程序启动时初始化initWxpayface;2、获取数据getWxpayfaceRawdata;
|
||||
*
|
||||
* @param wxFacePayReq
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@Override
|
||||
public WxFacePayResp doWxPayIniMethod(WxFacePayReq wxFacePayReq) throws JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
// 构建请求参数的JSON字符串
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String reqJson = mapper.writeValueAsString(wxFacePayReq);
|
||||
|
||||
// 创建一个Pointer来接收响应
|
||||
//List<String> pResp= new ArrayList<>();
|
||||
//String result = callWxpayFaceService.callWxpayFaceService(req,pResp);
|
||||
|
||||
//组装请求数据
|
||||
Pointer reqPointer = new Memory(reqJson.length() + 1);
|
||||
reqPointer.setString(0, reqJson);
|
||||
//请求数据长度
|
||||
int reqSize = reqPointer.getString(0).length();
|
||||
System.out.println("-----------调用微信刷脸DLL请求数据----------" + reqPointer.getString(0));
|
||||
|
||||
//接收响应
|
||||
long[] pRespBuf = new long[1];
|
||||
int[] respSize = new int[1];
|
||||
String resStr = new String();
|
||||
|
||||
|
||||
// 调用本地方法
|
||||
WxpayFaceSDKDll.Dll dll = WxpayFaceSDKDll.instance();
|
||||
int result = dll.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize);
|
||||
|
||||
//使用C:\Windows\System32目录下 int result = WxpayFaceSDK.INSTANCE.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize);
|
||||
log.info("[WeChatPayFaceServiceImpl][doWxPayIniMethod][95] DLL 返回码: {}", result);
|
||||
if (result == 0) {
|
||||
Pointer pointer = new Pointer(pRespBuf[0]);
|
||||
byte[] byteArray = pointer.getByteArray(0, respSize[0]);
|
||||
resStr = new String(byteArray, StandardCharsets.UTF_8);
|
||||
|
||||
System.out.println("-----------微信刷脸DLL调用服务成功结果: " + resStr);
|
||||
//释放
|
||||
dll.wxpayReleaseResponse(new String[2]);
|
||||
|
||||
//释放 C:\Windows\System32目录下 WxpayFaceSDK.INSTANCE.wxpayReleaseResponse(new String[2]);
|
||||
} else {
|
||||
Pointer pointer = new Pointer(pRespBuf[0]);
|
||||
byte[] byteArray = pointer.getByteArray(0, respSize[0]);
|
||||
resStr = new String(byteArray, StandardCharsets.UTF_8);
|
||||
System.err.println("-----------调用人脸服务失败: " + resStr);
|
||||
}
|
||||
|
||||
//响应结果
|
||||
WxFacePayResp wxFacePayResp = mapper.readValue(resStr, WxFacePayResp.class);
|
||||
return wxFacePayResp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description: 3、获取调用凭证get_wxpayface_authinfo(rawdata)
|
||||
* @author: zhangxue
|
||||
* @date: 2024/12/4 14:35
|
||||
* @Param rawData 初始化数据。由微信人脸SDK的接口返回。
|
||||
* @return: java.util.Map<java.lang.String, java.lang.String>
|
||||
*/
|
||||
@Override
|
||||
public WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception {
|
||||
try {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//门店编号, 由商户定义, 各门店唯一。
|
||||
map.put("store_id", WxConstant.STORE_ID);
|
||||
//门店名称,由商户定义。(可用于展示);中文会导致调用失败
|
||||
String text = WxConstant.STORE_TEXT;
|
||||
String storeName = Base64.getEncoder().encodeToString(text.getBytes());
|
||||
map.put("store_name", storeName);
|
||||
//终端设备编号,由商户定义
|
||||
map.put("device_id", WxConstant.DEVICE_ID);
|
||||
//初始化数据。由微信人脸SDK的接口返回。
|
||||
map.put("rawdata", rawData);
|
||||
//商户号绑定的公众号/小程序 appid
|
||||
map.put("appid", wxMpProperties.getMchConfig().getAppId());
|
||||
//商户号
|
||||
map.put("mch_id", wxMpProperties.getMchConfig().getMchId());
|
||||
//取当前时间,10位unix时间戳。
|
||||
long timeStampSec = System.currentTimeMillis() / 1000;
|
||||
String timestamp = String.format("%010d", timeStampSec);
|
||||
map.put("now", timestamp);
|
||||
//版本号。固定为1
|
||||
map.put("version", WxConstant.VERSION);
|
||||
//随机字符串,不长于32位:工具类微信随机数
|
||||
map.put("nonce_str", WxRandomUtils.getNonceStr());
|
||||
//参数签名,使用MD5
|
||||
map.put("sign_type", WxConstant.SING_TYPE);
|
||||
|
||||
//加密和生成微信v2指定的xml格式
|
||||
// WXPayUtil.createSign("UTF-8",map,wxMpProperties.getMchConfig().getKeyApi());
|
||||
String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5);
|
||||
log.info("--------3、获取调用凭证 构建微信获取刷脸授权XML参数:{}", sign);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
HttpEntity<String> stringHttpEntity = new HttpEntity<String>(sign, headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.GET_WXPAYFACE_AUTHINFO,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------3、获取调用凭证 发起http调用结果:{}", exchange.getBody());
|
||||
|
||||
XmlParserUtil.extractAuthInfo(exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml");
|
||||
log.info("------3、获取调用凭证 调用结果转换为map:{}", stringMap);
|
||||
|
||||
//转成返回对象
|
||||
WxFacePayAuthinfoResp wxFacePayAuthinfoResp = XmlUtils.mapToObject(
|
||||
stringMap,
|
||||
WxFacePayAuthinfoResp.class
|
||||
);
|
||||
log.info("--------发起http调用结果转换为WxFacePayAuthinfoResp", wxFacePayAuthinfoResp.toString());
|
||||
|
||||
return wxFacePayAuthinfoResp;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调用方法
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
*
|
||||
* @param wxFacePayReq
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
@Override
|
||||
public WxFacePayAuthinfoResp getWxpayfaceCode(WxFacePayReq wxFacePayReq) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException {
|
||||
//设置参数
|
||||
wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId())
|
||||
.setMch_id(wxMpProperties.getMchConfig().getMchId())
|
||||
.setStore_id(WxConstant.STORE_ID)
|
||||
.setFace_authtype(WxConstant.FACEPAY)
|
||||
.setAuthinfo(wxFacePayReq.getAuthinfo());
|
||||
|
||||
// 构建请求参数的JSON字符串
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String req = mapper.writeValueAsString(wxFacePayReq);
|
||||
|
||||
// 创建一个Pointer来接收响应
|
||||
List<String> pResp = new ArrayList<>();
|
||||
String result = callWxpayFaceService.callWxpayFaceService(req, pResp);
|
||||
System.out.println("4、进行人脸识别getWxpayfaceCode结果:"+result);
|
||||
log.info("[WeChatPayFaceServiceImpl][getWxpayfaceCode][220][4、进行人脸识别getWxpayfaceCode结果] :{}", result);
|
||||
|
||||
//响应结果
|
||||
WxFacePayAuthinfoResp authinfoResp = mapper.readValue(result, WxFacePayAuthinfoResp.class);
|
||||
return authinfoResp;
|
||||
}
|
||||
|
||||
|
||||
//5、进行发起订单支付
|
||||
public Map<String, String> createWxOrder() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信分配的公众账号ID(企业号corpid即为此appId)
|
||||
map.put("appid", wxMpProperties.getMchConfig().getAppId());
|
||||
//商户号
|
||||
map.put("mch_id", wxMpProperties.getMchConfig().getMchId());
|
||||
//随机字符串,不长于32位:工具类微信随机数
|
||||
map.put("nonce_str", WxRandomUtils.getNonceStr());
|
||||
//签名, map.put("sign", "");
|
||||
//商品简单描述,
|
||||
map.put("body", "");
|
||||
//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
|
||||
map.put("out_trade_no", "");
|
||||
//Int 订单总金额,单位为分,只能为整数
|
||||
map.put("total_fee", "");
|
||||
//支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
|
||||
map.put("spbill_create_ip", "");
|
||||
//扫码支付付款码,设备读取用户微信中的条码或者二维码信息
|
||||
map.put("auth_code", "");
|
||||
|
||||
//加密和生成微信v2指定的xml格式
|
||||
// WXPayUtil.createSign("UTF-8",map,wxMpProperties.getMchConfig().getKeyApi());
|
||||
String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5);
|
||||
log.info("--------构建微信获取刷脸授权XML参数:【{}】", sign);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
HttpEntity<String> stringHttpEntity = new HttpEntity<String>(sign, headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.CREATEORDER,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------5、进行发起订单支付:发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml");
|
||||
|
||||
//转成返回对象
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
WxFacePayMicroPayResp microPayResp = mapper.readValue(exchange.getBody(), WxFacePayMicroPayResp.class);
|
||||
log.info("--------5、进行发起订单支付,发起http调用结果转换", microPayResp.toString());
|
||||
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
|
||||
//6、查询订单状态
|
||||
public Map<String, String> orderquery() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信支付分配的公众账号ID(企业号corpid即为此appId)
|
||||
map.put("appid", wxMpProperties.getMchConfig().getAppId());
|
||||
//微信支付分配的商户号
|
||||
map.put("mch_id", wxMpProperties.getMchConfig().getMchId());
|
||||
//微信的订单号,建议优先使用;必填:否
|
||||
map.put("transaction_id", "");
|
||||
//商户系统内部订单号;必填:否
|
||||
map.put("out_trade_no", "");
|
||||
//随机字符串,不长于32位:工具类微信随机数
|
||||
map.put("nonce_str", WxRandomUtils.getNonceStr());
|
||||
//参数签名,使用MD5
|
||||
map.put("sign_type", "MD5");
|
||||
//sign:通过签名算法计算得出的签名值
|
||||
String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5);
|
||||
log.info("--------构建微信获取刷脸授权XML参数:【{}】", sign);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
HttpEntity<String> stringHttpEntity = new HttpEntity<String>("", headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.ORDERQUERY,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------6、查询订单状态,发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml");
|
||||
|
||||
//转成返回对象
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
WxFacePayOrderResp orderResp = mapper.readValue(exchange.getBody(), WxFacePayOrderResp.class);
|
||||
log.info("--------6、查询订单状态,发起http调用结果转换", orderResp.toString());
|
||||
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
|
||||
//7、撤销交易
|
||||
public Map<String, String> reverse() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信分配的公众账号ID(企业号corpid即为此appId)
|
||||
map.put("appid", wxMpProperties.getMchConfig().getAppId());
|
||||
//商户号
|
||||
map.put("mch_id", wxMpProperties.getMchConfig().getMchId());
|
||||
//微信的订单号,优先使用
|
||||
map.put("transaction_id", "");
|
||||
//商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no
|
||||
map.put("out_trade_no", "");
|
||||
//随机字符串,不长于32位。
|
||||
map.put("nonce_str", WxRandomUtils.getNonceStr());
|
||||
|
||||
//加密和生成微信v2指定的xml格式
|
||||
String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5);
|
||||
log.info("--------构建微信XML参数:【{}】", sign);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
HttpEntity<String> stringHttpEntity = new HttpEntity<String>("", headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.REVERSE,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------7、撤销交易,发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml");
|
||||
|
||||
//转成返回对象
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
WxFacePayOrderResp orderResp = mapper.readValue(exchange.getBody(), WxFacePayOrderResp.class);
|
||||
log.info("--------7、撤销交易,发起http调用结果转换", orderResp.toString());
|
||||
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
|
||||
//8、更新支付结果:通知人脸SDK更新支付结果
|
||||
public WxFacePayResp updateWxpayfacePayResult(String storeId, String authInfo, String payresult) throws Exception {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("updateWxpayfacePayResult", "1", System.currentTimeMillis() / 1000);
|
||||
//微信分配的公众账号ID(企业号corpid即为此appId)
|
||||
wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId());
|
||||
//商户号
|
||||
wxFacePayReq.setMch_id(wxMpProperties.getMchConfig().getMchId());
|
||||
//门店编号
|
||||
wxFacePayReq.setStore_id(storeId);
|
||||
//调用凭证
|
||||
wxFacePayReq.setAuthinfo(authInfo);
|
||||
//支付结果。可取值:SUCCESS: 支付成功、ERROR: 支付失败
|
||||
wxFacePayReq.setPayresult(payresult);
|
||||
|
||||
// 构建请求参数的JSON字符串
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String req = mapper.writeValueAsString(wxFacePayReq);
|
||||
|
||||
// 创建一个Pointer来接收响应
|
||||
List<String> pResp = new ArrayList<>();
|
||||
String result = callWxpayFaceService.callWxpayFaceService(req, pResp);
|
||||
|
||||
//响应结果
|
||||
WxFacePayResp authinfoResp = mapper.readValue(result, WxFacePayResp.class);
|
||||
return authinfoResp;
|
||||
}
|
||||
|
||||
|
||||
//9、停止刷脸支付
|
||||
public WxFacePayResp stopWxpayface(String storeId, String authInfo, String payresult) throws Exception {
|
||||
// 构建请求参数的JSON字符串
|
||||
WxFacePayReq wxFacePayReq = new WxFacePayReq("stopWxpayface", "1", System.currentTimeMillis() / 1000);
|
||||
//微信分配的公众账号ID(企业号corpid即为此appId)
|
||||
wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId());
|
||||
//商户号
|
||||
wxFacePayReq.setMch_id(wxMpProperties.getMchConfig().getMchId());
|
||||
//调用凭证
|
||||
wxFacePayReq.setAuthinfo(authInfo);
|
||||
|
||||
// 构建请求参数的JSON字符串
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String req = mapper.writeValueAsString(wxFacePayReq);
|
||||
|
||||
// 创建一个Pointer来接收响应
|
||||
List<String> pResp = new ArrayList<>();
|
||||
String result = callWxpayFaceService.callWxpayFaceService(req, pResp);
|
||||
|
||||
//响应结果
|
||||
WxFacePayResp authinfoResp = mapper.readValue(result, WxFacePayResp.class);
|
||||
return authinfoResp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
48
src/main/java/com/dpkj/modules/scanface/wx/util/MD5Util.java
Normal file
48
src/main/java/com/dpkj/modules/scanface/wx/util/MD5Util.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.dpkj.modules.scanface.wx.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* @description: MD5工具类
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/5 11:34
|
||||
*/
|
||||
public class MD5Util {
|
||||
|
||||
private static String byteArrayToHexString(byte b[]) {
|
||||
StringBuffer resultSb = new StringBuffer();
|
||||
for (int i = 0; i < b.length; i++)
|
||||
resultSb.append(byteToHexString(b[i]));
|
||||
|
||||
return resultSb.toString();
|
||||
}
|
||||
|
||||
private static String byteToHexString(byte b) {
|
||||
int n = b;
|
||||
if (n < 0)
|
||||
n += 256;
|
||||
int d1 = n / 16;
|
||||
int d2 = n % 16;
|
||||
return hexDigits[d1] + hexDigits[d2];
|
||||
}
|
||||
|
||||
public static String MD5Encode(String origin, String charsetname) {
|
||||
String resultString = null;
|
||||
try {
|
||||
resultString = new String(origin);
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
if (charsetname == null || "".equals(charsetname))
|
||||
resultString = byteArrayToHexString(md.digest(resultString
|
||||
.getBytes()));
|
||||
else
|
||||
resultString = byteArrayToHexString(md.digest(resultString
|
||||
.getBytes(charsetname)));
|
||||
} catch (Exception exception) {
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
|
||||
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
|
||||
|
||||
}
|
||||
115
src/main/java/com/dpkj/modules/scanface/wx/util/WXPayUtil.java
Normal file
115
src/main/java/com/dpkj/modules/scanface/wx/util/WXPayUtil.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package com.dpkj.modules.scanface.wx.util;
|
||||
|
||||
import org.jdom2.Content;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @description: 生成签名
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/4 14:27
|
||||
*/
|
||||
public class WXPayUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 生成微信API请求的MD5签名,并返回XML格式的字符串
|
||||
*
|
||||
* @param params 参数Map
|
||||
* @param apiKey 微信API密钥
|
||||
* @return 微信v2指定的XML格式字符串
|
||||
*/
|
||||
public static String generateSignedXml(Map<String, String> params, String apiKey,String signType) {
|
||||
// 将参数Map按照key的字典顺序排序
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
|
||||
// 构建签名原文
|
||||
StringBuilder signSrc = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
||||
if (!"sign".equals(entry.getKey())) {
|
||||
signSrc.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
}
|
||||
signSrc.append("key=").append(apiKey);
|
||||
|
||||
// 计算MD5签名
|
||||
String sign = getMD5(signSrc.toString(),signType).toUpperCase();
|
||||
|
||||
// 构建XML格式的字符串
|
||||
StringBuilder xmlBuilder = new StringBuilder();
|
||||
xmlBuilder.append("<xml>");
|
||||
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
||||
xmlBuilder.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
|
||||
}
|
||||
xmlBuilder.append("<sign><![CDATA[").append(sign).append("]]></sign>");
|
||||
xmlBuilder.append("</xml>");
|
||||
|
||||
return xmlBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用MD5算法计算字符串的签名
|
||||
*
|
||||
* @param input 输入字符串
|
||||
* @return MD5签名字符串
|
||||
*/
|
||||
private static String getMD5(String input,String signType) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(signType);
|
||||
byte[] digest = md.digest(input.getBytes());
|
||||
return DatatypeConverter.printHexBinary(digest).toLowerCase();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("MD5算法不支持", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, String> xmlToMap(String xml) throws Exception {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
Document document = new Document((List<? extends Content>) new StringReader(xml));
|
||||
Element root = document.getRootElement();
|
||||
|
||||
for (Element child : root.getChildren()) {
|
||||
map.put(child.getName(), child.getText());
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
//定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序
|
||||
public static String createSign(String characterEncoding, SortedMap<String,String> parameters,String key){
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Set es = parameters.entrySet();
|
||||
Iterator it = es.iterator();
|
||||
while(it.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry)it.next();
|
||||
String k = (String)entry.getKey();
|
||||
Object v = entry.getValue();
|
||||
if(null != v && !"".equals(v)
|
||||
&& !"sign".equals(k) && !"key".equals(k)) {
|
||||
sb.append(k + "=" + v + "&");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("key=" + key);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
|
||||
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
|
||||
return sign;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.dpkj.modules.scanface.wx.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
public class WxRandomUtils {
|
||||
|
||||
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private static final Random RANDOM = new SecureRandom();
|
||||
|
||||
|
||||
/**
|
||||
* 获取随机字符串 Nonce Str
|
||||
*
|
||||
* @return String 随机字符串
|
||||
*/
|
||||
public static String getNonceStr() {
|
||||
char[] nonceChars = new char[32];
|
||||
for (int index = 0; index < nonceChars.length; ++index) {
|
||||
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
|
||||
}
|
||||
return new String(nonceChars);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
||||
private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
|
||||
return appId + "\n"
|
||||
+ timestamp + "\n"
|
||||
+ nonceStr + "\n"
|
||||
+ packag + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.dpkj.modules.scanface.wx.util;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
/**
|
||||
* @description: 解析微信返回的xml数据
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/5/28 10:10
|
||||
*/
|
||||
public class XmlParserUtil {
|
||||
|
||||
/**
|
||||
* 解析微信返回的xml数据获取到authinfo
|
||||
* @param xmlResponse
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String extractAuthInfo(String xmlResponse) throws Exception {
|
||||
// 1. 创建DocumentBuilder解析XML
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
|
||||
// 2. 将字符串转为输入流
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(xmlResponse.getBytes("UTF-8"));
|
||||
Document doc = builder.parse(input);
|
||||
|
||||
// 3. 创建XPath表达式定位<authinfo>节点
|
||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||
String expression = "//authinfo"; // 使用XPath查找所有authinfo节点
|
||||
|
||||
// 4. 提取节点文本内容(自动处理CDATA)
|
||||
Node authInfoNode = (Node) xpath.evaluate(expression, doc, XPathConstants.NODE);
|
||||
if (authInfoNode != null) {
|
||||
return authInfoNode.getTextContent();
|
||||
} else {
|
||||
throw new RuntimeException("未找到<authinfo>节点");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
114
src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java
Normal file
114
src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package com.dpkj.modules.scanface.wx.util;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dom4j.Attribute;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.Element;
|
||||
import org.dom4j.io.SAXReader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
/**
|
||||
* @description: xml解析为Map
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/5 11:42
|
||||
*/
|
||||
public class XmlUtils {
|
||||
|
||||
|
||||
/**
|
||||
* xml解析器
|
||||
*
|
||||
* @param xml xml字符串
|
||||
* @param filterRootEleName 匹配的根节点名
|
||||
* @return
|
||||
* @返回map,格式:键:值
|
||||
* @返回属性值:->节点名=>属性名:属性值
|
||||
* @返回节点值:->节点名:值
|
||||
* @返回子节点属性值:->节点名->子节点名=>属性名:属性值
|
||||
* @返回子节点值:->节点名->子节点名:属性值
|
||||
*/
|
||||
public static Map<String, String> xmlParser(String xml, String filterRootEleName) {
|
||||
Map<String, String> retMap = new HashMap<>();
|
||||
//1.创建Reader对象
|
||||
try {
|
||||
SAXReader reader = new SAXReader();
|
||||
InputStream targetStream = IOUtils.toInputStream(xml, StandardCharsets.UTF_8.name());
|
||||
Document document = reader.read(targetStream);
|
||||
//3.获取根节点
|
||||
Element rootElement = document.getRootElement();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
parser(rootElement, builder, filterRootEleName, retMap);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return retMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* xml递归解析器
|
||||
*
|
||||
* @param ele 解析节点
|
||||
* @param eleKey 上级节点key
|
||||
* @param retMap 返回map
|
||||
*/
|
||||
private static void parser(Element ele, StringBuilder eleKey, String firstEleName, Map<String, String> retMap) {
|
||||
StringBuilder builder = new StringBuilder();//eleKey.toString()
|
||||
if (StringUtils.isEmpty(firstEleName)
|
||||
|| firstEleName.equals(ele.getName())) {
|
||||
firstEleName = null;
|
||||
//builder.append("->" + ele.getName());
|
||||
if (StringUtils.isNotEmpty(StringUtils.stripToEmpty(ele.getData() + ""))) {
|
||||
retMap.put(ele.getName(), StringUtils.stripToEmpty(ele.getData() + ""));
|
||||
}
|
||||
List<Attribute> attributes = ele.attributes();
|
||||
for (Attribute attribute : attributes) {
|
||||
StringBuilder builder1 = new StringBuilder(builder.toString());
|
||||
builder1.append("=>" + attribute.getName());
|
||||
if (StringUtils.isNotEmpty(attribute.getValue())) {
|
||||
retMap.put(builder1.toString(), attribute.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
Iterator iterator1 = ele.elementIterator();
|
||||
while (iterator1.hasNext()) {
|
||||
Element eleChild = (Element) iterator1.next();
|
||||
parser(eleChild, builder, firstEleName, retMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通用方法:将Map转换为Java对象
|
||||
*/
|
||||
public static <T> T mapToObject(Map<String, String> map, Class<T> clazz) throws Exception {
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(true);
|
||||
String fieldName = field.getName();
|
||||
String value = map.get(fieldName);
|
||||
|
||||
if (value != null) {
|
||||
Class<?> type = field.getType();
|
||||
// 类型转换逻辑
|
||||
if (type == int.class || type == Integer.class) {
|
||||
field.set(obj, Integer.parseInt(value));
|
||||
} else if (type == boolean.class || type == Boolean.class) {
|
||||
field.set(obj, Boolean.parseBoolean(value));
|
||||
} else {
|
||||
field.set(obj, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description: 硬件提供参数
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/28 10:01
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class WxFaceAuthInfoReq {
|
||||
|
||||
private String deviceId;
|
||||
|
||||
private Map<String,String> rawData;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: 微信刷脸订单参数
|
||||
* @author: Zhangxue
|
||||
* @time: 2025/5/28 15:35
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFaceOrderVo {
|
||||
/**
|
||||
* 付款模块
|
||||
*/
|
||||
private String eventModule;
|
||||
|
||||
/**
|
||||
* 患者Id
|
||||
*/
|
||||
private String patientId;
|
||||
|
||||
/**
|
||||
* 商户机具终端编号
|
||||
*/
|
||||
private String terminalId;
|
||||
|
||||
/**
|
||||
* 系统订单编号
|
||||
*/
|
||||
private String outTradeNo;
|
||||
|
||||
/**
|
||||
* 用户支付金额 分
|
||||
*/
|
||||
private String totalAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: 3、获取调用凭证返回值
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/6 9:23
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFacePayAuthinfoResp {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private String return_code;
|
||||
|
||||
/**
|
||||
* 对错误码的描述
|
||||
*/
|
||||
private String return_msg;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 初始化数据。用于接口调用获取authinfo
|
||||
*/
|
||||
private String rawdata;
|
||||
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:是
|
||||
* SDK调用凭证。用于调用SDK的人脸识别接口。
|
||||
*/
|
||||
private String authinfo;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:否
|
||||
* authinfo的有效时间, 单位秒。
|
||||
* 在有效时间内, 对于同一台终端设备,相同的参数的前提下(如:相同的公众号、商户号、 门店编号等),可以用同一个authinfo,多次调用SDK的getWxpayfaceCode接口。
|
||||
*/
|
||||
private int expires_in;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:是
|
||||
* 随机字符串
|
||||
*/
|
||||
private String nonce_str;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:是
|
||||
* 响应结果签名
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:是
|
||||
* 公众号
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:否
|
||||
* 商户号
|
||||
*/
|
||||
private String mch_id;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:否
|
||||
* 子商户公众账号ID(服务商模式)
|
||||
*/
|
||||
private String sub_appid;
|
||||
|
||||
/**
|
||||
* 3、获取调用凭证
|
||||
* 必填:是
|
||||
* 子商户号(服务商模式)
|
||||
*/
|
||||
private String sub_mch_id;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 是否必填:S
|
||||
* 人脸凭证, 用于刷脸支付。
|
||||
*/
|
||||
private String face_code;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 是否必填:S
|
||||
* openid
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 是否必填:否
|
||||
* 子商户号下的openid(服务商模式)
|
||||
*/
|
||||
private String sub_openid;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 是否必填:否
|
||||
* 用户身份信息查询凭证
|
||||
*/
|
||||
private String face_sid;
|
||||
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 订单金额(数字), 单位分,该字段在在face_code_type为"1"时可不填,为"0"时必填
|
||||
*/
|
||||
private String total_fee;
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 商户订单号,须与调用支付接口时字段一致,该字段在在face_code_type为"1"时可不填,为"0"时必填
|
||||
*/
|
||||
private String out_trade_no;
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 商品描述
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 患者Id
|
||||
*/
|
||||
private String patientId;
|
||||
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 付款模块
|
||||
*/
|
||||
private String eventModule;
|
||||
|
||||
/**
|
||||
* 传参到后台发起微信订单方法
|
||||
* 商户机具终端编号
|
||||
*/
|
||||
private String terminalId;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: 5、进行发起订单支付 返回值
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/6 9:23
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFacePayMicroPayResp extends WxFacePayOrderResp{
|
||||
|
||||
/************当return_code 和result_code都为SUCCESS的时,还会包括以下字段:*******************/
|
||||
|
||||
/**
|
||||
* 用户在商户appid 下的唯一标识
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 用户是否关注公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注
|
||||
*/
|
||||
private String is_subscribe;
|
||||
|
||||
/**
|
||||
* MICROPAY 付款码支付
|
||||
*/
|
||||
private String trade_type;
|
||||
|
||||
/**
|
||||
* 银行类型,采用字符串类型的银行标识
|
||||
*/
|
||||
private String bank_type;
|
||||
|
||||
/**
|
||||
* 符合ISO 4217标准的三位字母代码,默认人民币:CNY
|
||||
*/
|
||||
private String fee_type;
|
||||
|
||||
/**
|
||||
* 订单总金额,单位为分,只能为整数
|
||||
*/
|
||||
private Integer total_fee;
|
||||
|
||||
/**
|
||||
* 当订单使用了免充值型优惠券后返回该参数,应结订单金额=订单金额-免充值优惠券金额。
|
||||
*/
|
||||
private Integer settlement_total_fee;
|
||||
|
||||
/**
|
||||
* “代金券”金额<=订单金额,订单金额-“代金券”金额=现金支付金额,
|
||||
*/
|
||||
private Integer coupon_fee;
|
||||
|
||||
/**
|
||||
* 符合ISO 4217标准的三位字母代码,默认人民币:CNY,
|
||||
*/
|
||||
private String cash_fee_type;
|
||||
|
||||
/**
|
||||
* 订单现金支付金额,
|
||||
*/
|
||||
private Integer cash_fee;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
private String transaction_id;
|
||||
|
||||
/**
|
||||
* 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。
|
||||
*/
|
||||
private String out_trade_no;
|
||||
|
||||
/**
|
||||
* 商家数据包,原样返回
|
||||
*/
|
||||
private String attach;
|
||||
|
||||
/**
|
||||
* 订单生成时间,格式为yyyyMMddHHmmss
|
||||
*/
|
||||
private String time_end;
|
||||
|
||||
/**
|
||||
* 新增返回,单品优惠功能字段
|
||||
*/
|
||||
private String promotion_detail;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description: 5、进行发起订单支付 返回值;6、查询订单状态
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/12/10 15:30
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFacePayOrderResp {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
* 文档:https://pay.weixin.qq.com/wiki/doc/wxfacepay/develop/windows/facepay.html#_5%E3%80%81%E8%BF%9B%E8%A1%8C%E5%8F%91%E8%B5%B7%E8%AE%A2%E5%8D%95%E6%94%AF%E4%BB%98
|
||||
*/
|
||||
private String return_code;
|
||||
|
||||
/**
|
||||
* 对错误码的描述
|
||||
*/
|
||||
private String return_msg;
|
||||
|
||||
/**
|
||||
* 调用接口提交的公众账号ID
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 调用接口提交的商户号
|
||||
*/
|
||||
private String mch_id;
|
||||
|
||||
/**
|
||||
* 调用接口提交的终端设备号,
|
||||
*/
|
||||
private String device_info;
|
||||
|
||||
/**
|
||||
* 微信返回的随机字符串,
|
||||
*/
|
||||
private String nonce_str;
|
||||
|
||||
/**
|
||||
* 微信返回的签名,
|
||||
*/
|
||||
private String sign;
|
||||
|
||||
/**
|
||||
* SUCCESS/FAIL
|
||||
*/
|
||||
private String result_code;
|
||||
|
||||
/**
|
||||
* 详细参见错误列表
|
||||
*/
|
||||
private String err_code;
|
||||
|
||||
/**
|
||||
* 错误返回的信息描述
|
||||
*/
|
||||
private String err_code_des;
|
||||
|
||||
/**
|
||||
* SUCCESS—支付成功,
|
||||
* REFUND—转入退款当result_code为FAIL时返回错误代码,
|
||||
* NOTPAY—未支付当result_code为FAIL时返回错误描述,
|
||||
* CLOSED—已关闭,REVOKED—已撤销(付款码支付),
|
||||
* USERPAYING--用户支付中(付款码支付),
|
||||
* PAYERROR--支付失败(其他原因,如银行返回失败),
|
||||
*/
|
||||
private String trade_state;
|
||||
|
||||
/**
|
||||
* 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
|
||||
*/
|
||||
private String out_trade_no;
|
||||
|
||||
/**
|
||||
* 附加数据,原样返回
|
||||
*/
|
||||
private String attach;
|
||||
|
||||
/**
|
||||
* 用户在商户appid下的唯一标识
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 用户是否关注公众账号,Y-关注,N-未关注
|
||||
*/
|
||||
private String is_subscribe;
|
||||
|
||||
/**
|
||||
* 调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP,MICROPAY
|
||||
*/
|
||||
private String trade_type;
|
||||
|
||||
/**
|
||||
* 银行类型,采用字符串类型的银行标识
|
||||
*/
|
||||
private String bank_type;
|
||||
|
||||
/**
|
||||
* 订单总金额,单位为分
|
||||
*/
|
||||
private Integer total_fee;
|
||||
|
||||
/**
|
||||
* 现金支付金额订单现金支付金额
|
||||
*/
|
||||
private Integer cash_fee;
|
||||
|
||||
/**
|
||||
* 微信支付订单号
|
||||
*/
|
||||
private String transaction_id;
|
||||
|
||||
/**
|
||||
* 订单支付时间,格式为yyyyMMddHHmmss,如20091225091010
|
||||
*/
|
||||
private String time_end;
|
||||
|
||||
/**
|
||||
* 对当前查询订单状态的描述和下一步操作的指引
|
||||
*/
|
||||
private int trade_state_desc;
|
||||
|
||||
|
||||
}
|
||||
169
src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java
Normal file
169
src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description:WxFacePayReq
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/29 17:18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFacePayReq {
|
||||
|
||||
/**
|
||||
* 命令字,即各接口名:
|
||||
* initWxpayface
|
||||
* getWxpayfaceRawdata
|
||||
* getWxpayfaceCode
|
||||
* releaseWxpayface
|
||||
*/
|
||||
private String cmd;
|
||||
|
||||
/**
|
||||
* 版本号, 固定填写1
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 取当前时间的10位unix时间戮
|
||||
*/
|
||||
private long now;
|
||||
|
||||
|
||||
/**
|
||||
* 对摄像头画面进行旋转,1为+90度,2为180度,3为-90度,
|
||||
*/
|
||||
private int camera_rotation;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:是
|
||||
* 商户号绑定的公众号/小程序 appid
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:是
|
||||
* 商户号
|
||||
*/
|
||||
private String mch_id;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 子商户绑定的公众号/小程序 appid(可不填)
|
||||
*/
|
||||
//private String sub_appid;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 子商户号(非服务商模式不填)
|
||||
*/
|
||||
//private String sub_mch_id;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:是
|
||||
* 门店编号
|
||||
*/
|
||||
private String store_id;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 通过getWxpayfaceUserInfo获取的openid,传入后可使用快捷支付模式。1.24版本以上支持该参数
|
||||
*/
|
||||
//private String openid;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付,
|
||||
* 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。1.16版本以上支持该参数
|
||||
*/
|
||||
private String face_code_type;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 订单金额(数字), 单位分,该字段在在face_code_type为"1"时可不填,为"0"时必填
|
||||
*/
|
||||
private String total_fee;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 商户订单号,须与调用支付接口时字段一致,该字段在在face_code_type为"1"时可不填,为"0"时必填
|
||||
*/
|
||||
private String out_trade_no;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:是
|
||||
* FACEPAY: 人脸凭证(face_code),用于刷脸支付 FACE_AUTH: 实名认证(需联系微信支付开通权限)
|
||||
*/
|
||||
private String face_authtype;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:是
|
||||
* 调用凭证
|
||||
*/
|
||||
private String authinfo;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 指定刷脸界面显示的屏幕编号。
|
||||
* 编号1为主屏幕,其余屏幕按系统设置中的顺序从2开始编号,常用场景举例:双屏机器上传"2"即可显示在副屏上。如果不填写则默认显示在主屏幕。1.18版本以上支持该参数
|
||||
*/
|
||||
//private String screen_index;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 设置不接受外接键盘输入,可选值:"1",禁用。1.23版本以上支持该参数
|
||||
*/
|
||||
//private String disable_keyboard;
|
||||
|
||||
/**
|
||||
* 4、进行人脸识别
|
||||
* 必填:否
|
||||
* 设置刷脸窗口以无焦点方式启动,可选值:"1",无焦点启动窗口。1.26版本以上支持该参数
|
||||
*/
|
||||
//private String use_window_nofocus;
|
||||
|
||||
|
||||
/**
|
||||
* 8、更新支付结果
|
||||
* 支付结果。可取值:SUCCESS: 支付成功、ERROR: 支付失败
|
||||
*/
|
||||
private String payresult;
|
||||
|
||||
|
||||
|
||||
//构建
|
||||
public WxFacePayReq(String cmd, String version, long now) {
|
||||
this.cmd= cmd;
|
||||
this.version =version;
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
//构建
|
||||
public WxFacePayReq(String cmd, String version, long now,int camera_rotation) {
|
||||
this.cmd= cmd;
|
||||
this.version =version;
|
||||
this.now = now;
|
||||
this.camera_rotation = camera_rotation;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.dpkj.modules.scanface.wx.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @description:WxFacePayResp 调用微信刷脸的返回值
|
||||
* @author: Zhangxue
|
||||
* @time: 2024/11/29 17:18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class WxFacePayResp {
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private String return_code;
|
||||
|
||||
/**
|
||||
* 对错误码的描述
|
||||
*/
|
||||
private String return_msg;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 2、获取数据
|
||||
* 初始化数据。用于接口调用获取authinfo
|
||||
*/
|
||||
private String rawdata;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
server:
|
||||
port: 5948
|
||||
|
||||
dpkj:
|
||||
#后端项目访问地址 #https://yinyitong.yzqingyan.cn/ http://172.16.11.13:15946/ ttps://yinyitong.yzqingyan.cn
|
||||
serverurl: http://localhost:5946/api/
|
||||
# 医保配置
|
||||
chs:
|
||||
# 医保机构编码
|
||||
@@ -10,8 +15,81 @@ dpkj:
|
||||
printer:
|
||||
# 打印机连接方式 USB: usb连接 | BTMS:串口连接
|
||||
connection-type: USB
|
||||
# 终端号设置
|
||||
terminal-Number: PORT-000000001
|
||||
# 打印时间格式化
|
||||
time-Type: yyyy-MM-dd HH:mm:ss
|
||||
# 打印端口 串口连接下使用
|
||||
port-name:
|
||||
# 波特率 串口连接下使用
|
||||
baud-rate:
|
||||
# 指定打印机第一层是a4还是a5
|
||||
level-one: A5
|
||||
#支付宝刷脸
|
||||
ali:
|
||||
face:
|
||||
#dll文件路径
|
||||
dll-path: C:/opt/ant-abcp/bpaas_api.dll
|
||||
#IOT 应用管理-appid
|
||||
app-id: 2021005138656502
|
||||
#IOT应用版本
|
||||
app-version: 1.0.0.0
|
||||
#签约商家的 PID,以 2088 开头,企业主体
|
||||
merchant-id: 2088641941653700
|
||||
#商家机具终端编号,每台设备保持唯一
|
||||
device-num: P060003750
|
||||
#服务商的 PID
|
||||
partner-id: 2088641941653700
|
||||
# 核心入参 serviceId
|
||||
service-id: pay
|
||||
#微信模块
|
||||
wx:
|
||||
configs: #清研家
|
||||
- app-id: wxe8334dd2140bb0e1
|
||||
# 公众号的appsecret
|
||||
secret: f83420d79cc6ecd1d7fe9684ac9cdfe4
|
||||
# 接口配置里的Token值
|
||||
token: dpkjylwjvote
|
||||
# 接口配置里的EncodingAESKey值
|
||||
aes-key: go2uM3ASe2rEyeoNsZHoPCiKGgpku0Bi49P5IypdQWT
|
||||
mch-config: #商户信息 清研家关联的商户:驿路万家-扬州清研软件科技
|
||||
app-id: wxe8334dd2140bb0e1
|
||||
secret: f83420d79cc6ecd1d7fe9684ac9cdfe4
|
||||
#调用接口所需service_id
|
||||
service-id: service_id
|
||||
#商户号
|
||||
mch-id: 1557642321
|
||||
#商户秘钥
|
||||
mch-key: yndpkj15288216506YndpkjKsjytZx12
|
||||
#报文解密 APIv3密钥
|
||||
v3-key: yndpkj15288216506YndpkjKsjytZx12
|
||||
#微信: 商户APIv2密钥
|
||||
key-api: yndpkj15288216506YndpkjKsjytZx12
|
||||
#p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头)
|
||||
key-path: classpath:test\apiclient_cert.p12
|
||||
private-key-path: classpath:test\apiclient_key.pem
|
||||
private-cert-path: classpath:test\apiclient_cert.pem
|
||||
#微信:商家api序列号
|
||||
mch-serial-no:
|
||||
#JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付
|
||||
trade-type: JSAPI
|
||||
trade-type-native: NATIVE
|
||||
#支付回调方法地址
|
||||
pay-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/notify
|
||||
#退款回调方法地址
|
||||
refund-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/refundNotify
|
||||
# 路径
|
||||
url:
|
||||
# 后端接口地址
|
||||
# server: https://102760424tfyw.vicp.fun/api
|
||||
server: ${dpkj.url.server-url}
|
||||
# h5地址
|
||||
h5: ${dpkj.url.h5-url}
|
||||
|
||||
|
||||
# 自定义app参数
|
||||
app:
|
||||
custom:
|
||||
lexMarkServiceIp: http://127.0.0.1
|
||||
lexMarkServicePort: 12346
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
server:
|
||||
port: 5946
|
||||
|
||||
dpkj:
|
||||
#后端项目访问地址
|
||||
#TODO 改为正式的地址 http://www.lczyyy.com/api http://127.0.0.1:15946/api
|
||||
serverurl: http://www.lczyyy.com/api
|
||||
# 医保配置
|
||||
chs:
|
||||
# 医保机构编码
|
||||
@@ -10,8 +16,79 @@ dpkj:
|
||||
printer:
|
||||
# 打印机连接方式 USB: usb连接 | BTMS:串口连接
|
||||
connection-type: USB
|
||||
# 终端号设置
|
||||
terminal-Number: PORT-000000001
|
||||
# 打印时间格式化
|
||||
time-Type: yyyy-MM-dd HH:mm:ss
|
||||
# 打印端口 串口连接下使用
|
||||
port-name:
|
||||
# 波特率 串口连接下使用
|
||||
baud-rate:
|
||||
# 指定打印机第一层是a4还是a5
|
||||
level-one: A5
|
||||
#支付宝刷脸 中医院配置
|
||||
ali:
|
||||
face:
|
||||
#dll文件路径
|
||||
dll-path: C:/opt/ant-abcp/bpaas_api.dll
|
||||
#IOT 应用管理-appid
|
||||
app-id: 2021005151604729
|
||||
#IOT应用版本
|
||||
app-version: 1.0.0.0
|
||||
#签约商家的 PID,以 2088 开头,企业主体
|
||||
merchant-id: 2088170977486823
|
||||
#商家机具终端编号,每台设备保持唯一
|
||||
device-num: P060003750
|
||||
#服务商的 PID
|
||||
partner-id: 2088170977486823
|
||||
# 核心入参 serviceId
|
||||
service-id: pay
|
||||
#微信模块
|
||||
wx:
|
||||
configs: # 清研家
|
||||
- app-id: wxc12fa4977f66974d #wxc12fa4977f66974d #wx024903d1d2e1a55a
|
||||
# 公众号的appsecret
|
||||
secret: a51dbcac25a73d7e4812a43cf550c5fc #f83420d79cc6ecd1d7fe9684ac9cdfe4
|
||||
# 接口配置里的Token值 DPKJ-YINYITONG #DPKJ-YINYITONG#dpkjylwjvote
|
||||
token: DPKJ-YINYITONG
|
||||
# 接口配置里的EncodingAESKey值
|
||||
aes-key: yIBgBrHwRGjO2L3CLIE9hmnlf1FrXQQ7qJZVIg4r6Dx
|
||||
mch-config: #商户信息 驿路万家-扬州清研软件科技
|
||||
app-id: wxc12fa4977f66974d
|
||||
secret: f83420d79cc6ecd1d7fe9684ac9cdfe4
|
||||
#调用接口所需service_id
|
||||
service-id: service_id
|
||||
#商户号
|
||||
mch-id: 1557642321
|
||||
#商户秘钥
|
||||
mch-key: yndpkj15288216506YndpkjKsjytZx12
|
||||
#报文解密 APIv3密钥
|
||||
v3-key: yndpkj15288216506YndpkjKsjytZx12
|
||||
#微信: 商户APIv2密钥
|
||||
key-api: yndpkj15288216506YndpkjKsjytZx12
|
||||
#p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头)
|
||||
key-path: classpath:test\apiclient_cert.p12
|
||||
private-key-path: classpath:test\apiclient_key.pem
|
||||
private-cert-path: classpath:test\apiclient_cert.pem
|
||||
#微信:商家api序列号
|
||||
mch-serial-no:
|
||||
#JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付
|
||||
trade-type: JSAPI
|
||||
trade-type-native: NATIVE
|
||||
#支付回调方法地址
|
||||
pay-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/notify
|
||||
#退款回调方法地址
|
||||
refund-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/refundNotify
|
||||
# 路径
|
||||
url:
|
||||
# 后端接口地址
|
||||
# server: https://102760424tfyw.vicp.fun/api
|
||||
server: ${dpkj.url.server-url}
|
||||
# h5地址
|
||||
h5: ${dpkj.url.h5-url}
|
||||
|
||||
# 自定义app参数
|
||||
app:
|
||||
custom:
|
||||
lexMarkServiceIp: http://127.0.0.1
|
||||
lexMarkServicePort: 12346
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
resource:
|
||||
static-locations: classpath:/static/,classpath:/public/
|
||||
application:
|
||||
name: ems-express-bridge
|
||||
profiles:
|
||||
#active: '@profile.name@'
|
||||
active: pro
|
||||
|
||||
server:
|
||||
port: 5946
|
||||
servlet:
|
||||
context-path: /api
|
||||
tomcat:
|
||||
@@ -26,11 +12,18 @@ server:
|
||||
min-response-size: 1024
|
||||
mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.dpkj: debug
|
||||
|
||||
# 自定义app参数
|
||||
app:
|
||||
custom:
|
||||
lexMarkServiceIp: http://127.0.0.1
|
||||
lexMarkServicePort: 12346
|
||||
|
||||
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
resource:
|
||||
static-locations: classpath:/static/,classpath:/public/
|
||||
application:
|
||||
name: ems-express-bridge
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!--定义日志文件的存储地址 -->
|
||||
<property name="LOG_HOME" value="./logs/java"/>
|
||||
<property name="LOG_HOME" value="${log.path:-.}/logs"/>
|
||||
|
||||
<!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
|
||||
<!-- 控制台输出 -->
|
||||
@@ -70,8 +70,8 @@
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="HTML"/>
|
||||
<appender-ref ref="FILE_HTML"/>
|
||||
<!-- <appender-ref ref="HTML"/>-->
|
||||
<!-- <appender-ref ref="FILE_HTML"/>-->
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -2,202 +2,71 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>门诊缴费凭证</title>
|
||||
<style>
|
||||
.bold-text {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.text-35 {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
.text-30 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.margin-top-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.margin-top-40 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.header-terminal {
|
||||
font-size: 38px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.header-receipt {
|
||||
font-size: 38px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
font-size: 35px;
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
.patient-info .info-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.patient-info .info-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.payment-info {
|
||||
font-size: 35px;
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
.payment-info .info-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.payment-info .info-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
.table-container table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
||||
.table-container .first-th {
|
||||
width: 300px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table-container .df-th {
|
||||
width: calc(25% - 5px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-container .first-td {
|
||||
text-align: left;
|
||||
width: 300px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.table-container .df-td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terminal-info {
|
||||
margin-left: 20px;
|
||||
font-weight: 700;
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-left: 20px;
|
||||
font-size: 35px;
|
||||
font-weight: 700;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
<title>门诊缴费凭证T2</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bold-text">
|
||||
<div class="center-text margin-top-40">
|
||||
<div><span class="header-title">******<span th:text="${hospitalName}"></span>******</span></div>
|
||||
<div class="header-terminal"><span th:text="${registeTerminalName}"></span></div>
|
||||
<div class="header-receipt"><span th:text="${registeType}"></span></div>
|
||||
<div style="font-size: 32px;font-weight: bold;">
|
||||
<div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${hospitalName}"></span>
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 28px;"><span>****<span
|
||||
th:text="${registeTerminalName}"></span>****</span>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${registeType}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<div style="font-size: 35px;word-break: break-all;margin: 10px 20px;">
|
||||
<div style="display: flex;">
|
||||
<span style="margin-right: 25px;">姓名:<span th:text="${name}"></span></span>
|
||||
<span style="margin-right: 25px;">性别:<span th:text="${gender}"></span></span>
|
||||
<span>年龄:<span th:text="${age}"></span></span>
|
||||
</div>
|
||||
<div style="word-break: break-all;margin-top: 10px;">
|
||||
<span style="margin-right: 25px;" th:if="${outpatientNumber}">
|
||||
门诊号:<span th:text="${outpatientNumber}"></span></span>
|
||||
<span th:if="${doctor}">
|
||||
就诊医生:<span th:text="${doctor}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" th:if="${department}">就诊科室:<span th:text="${department}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 32px;">
|
||||
-----------------------------------------------------------------------------
|
||||
</div>
|
||||
<div class="patient-info">
|
||||
<div class="info-row">
|
||||
<div class="info-item">姓名:<span th:text="${name}"></span></div>
|
||||
<div class="info-item">性别:<span th:text="${gender}"></span></div>
|
||||
<div class="info-item">年龄:<span th:text="${age}"></span></div>
|
||||
<div style="font-size: 35px; margin: 10px 20px">
|
||||
<div style="word-break: break-all;">
|
||||
<span style="margin-right: 25px;">费用总额:<span th:text="${totalFee}"></span></span>
|
||||
<span>个人支付:<span th:text="${personalPayment}"></span></span>
|
||||
</div>
|
||||
<div class="info-row margin-top-10">
|
||||
<div class="info-item" th:if="${outpatientNumber}">门诊号:<span th:text="${outpatientNumber}"></span></div>
|
||||
<div class="info-item" th:if="${doctor}">就诊医生:<span th:text="${doctor}"></span></div>
|
||||
</div>
|
||||
<div class="margin-top-10" th:if="${department}">就诊科室:<span th:text="${department}"></span></div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">
|
||||
<span>实收金额:</span><span
|
||||
th:text="${actualReceiptAmount}"></span></div>
|
||||
<div style="margin-top: 10px;"><span>实收金额:</span><span th:text="${actualReceiptAmountChinese}"></span></div>
|
||||
</div>
|
||||
<div class="divider">
|
||||
-----------------------------------------------------------------------------
|
||||
</div>
|
||||
<div class="payment-info">
|
||||
<div class="info-row">
|
||||
<div class="info-item">费用总额:<span th:text="${totalFee}"></span></div>
|
||||
<div class="info-item">个人支付:<span th:text="${personalPayment}"></span></div>
|
||||
</div>
|
||||
<div class="margin-top-10">实收金额:<span th:text="${actualReceiptAmount}"></span></div>
|
||||
<div class="margin-top-10">实收金额:<span th:text="${actualReceiptAmountChinese}"></span></div>
|
||||
</div>
|
||||
<div class="divider">
|
||||
-----------------------------------------------------------------------------
|
||||
</div>
|
||||
<div style="margin-left: 20px; margin-top: 10px; ">
|
||||
<table border="0" style="font-size: 30px;">
|
||||
<tr style="font-size: 34px; ">
|
||||
<th>项目名称</th>
|
||||
<th>数量</th>
|
||||
<th>单价</th>
|
||||
<th>小计</th>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="margin: 10px 20px">
|
||||
<table style="width: 100%; table-layout: fixed; border-collapse: collapse;">
|
||||
<tr style="font-size: 35px;">
|
||||
<th style="width: 300px;text-align: left;">项目名称</th>
|
||||
<th style="width: calc(25% - 5px);text-align: center;">数量</th>
|
||||
<th style="width: calc(25% - 5px);text-align: center;">单价</th>
|
||||
<th style="width: calc(25% - 5px);text-align: center;">小计</th>
|
||||
</tr>
|
||||
<tr th:each="item : ${items}" style="height: 50px;">
|
||||
<td style=" width: 310px; word-break: break-all;" th:text="${item.name}"></td>
|
||||
<td th:text="${item.quantity}"></td>
|
||||
<td th:text="${item.unitPrice}"></td>
|
||||
<td th:text="${item.subtotal}"></td>
|
||||
<tr style="font-size: 30px;word-break: break-all;" th:each="item : ${items}">
|
||||
<td style="text-align: left;width: 300px;" th:text="${item.name}"></td>
|
||||
<td style="text-align: center;" th:text="${item.quantity}"></td>
|
||||
<td style="text-align: center;" th:text="${item.unitPrice}"></td>
|
||||
<td style="text-align: center;" th:text="${item.subtotal}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<tr class="text-35">
|
||||
<th class="first-th">项目名称</th>
|
||||
<th class="df-th">数量</th>
|
||||
<th class="df-th">单价</th>
|
||||
<th class="df-th">小计</th>
|
||||
</tr>
|
||||
<tr class="text-30" th:each="item : ${items}">
|
||||
<td class="first-td" th:text="${item.name}"></td>
|
||||
<td class="df-td" th:text="${item.quantity}"></td>
|
||||
<td class="df-td" th:text="${item.unitPrice}"></td>
|
||||
<td class="df-td" th:text="${item.subtotal}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="divider">
|
||||
-----------------------------------------------------------------------------
|
||||
</div>
|
||||
<div class="terminal-info">
|
||||
<div>终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div>打印时间:<span th:text="${printTime}"></span></div>
|
||||
</div>
|
||||
<div class="tips">
|
||||
<span>温馨提示</span><br>
|
||||
<span>1.请取走全部凭条、并妥善保管</span><br>
|
||||
<span>2.如果对缴费结算存在疑问,请到人工窗口咨询</span>
|
||||
<div style="border-bottom: 1px dashed #000;margin-bottom: 3px;"></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;margin-top: 10px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -4,15 +4,18 @@
|
||||
<title>挂号单</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-size: 32px; margin-top: 20px;">
|
||||
<div style="">
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${hospitalName}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; font-size: 28px;">****<span th:text="${registeTerminalName}"></span></span>****</div>
|
||||
<div style="text-align: center; font-weight: 700; margin-bottom: -10px; margin-top: -10px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${registeType}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; margin-top: -15px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="font-size: 32px;font-weight: bold;">
|
||||
<div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${hospitalName}"></span>
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 28px;"><span>****<span
|
||||
th:text="${registeTerminalName}"></span>****</span>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${registeType}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
</div>
|
||||
<div style="font-weight: 700; font-size: 37px;">
|
||||
<div style=" font-size: 37px;">
|
||||
<div style="margin-left: 35px;">姓  名:<span th:text="${name}"></span></div>
|
||||
<div style="margin-left: 35px;">性  别:<span th:text="${gender}"></span></div>
|
||||
<div style="margin-left: 35px;">年  龄:<span th:text="${age}"></span></div>
|
||||
@@ -21,13 +24,14 @@
|
||||
<div style="margin-left: 35px;">入院科室:<span th:text="${department}"></span></div>
|
||||
<div style="margin-left: 35px;">总 费 用:<span th:text="${totalFee}"></span> 元</div>
|
||||
<div style="margin-left: 35px;">支付方式:<span th:text="${paymentMethod}"></span></div>
|
||||
<div style="margin-left: 35px;">交易流水:<span style="font-size: 35px;" th:text="${transactionNumber}"></span></div>
|
||||
<div style="margin-left: 35px;">交易流水:<span style="font-size: 35px;" th:text="${transactionNumber}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-weight: 700; margin-top: 8px;">
|
||||
<!-- <div style="margin-top: -40px; text-align: center; ">-----------------------------------------------------------------------------</div>-->
|
||||
<div style="font-size: 36px;margin-left: 35px; ">备注:缴费凭证,请妥善保管!</div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">终 端 号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin-bottom: 3px;"></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;margin-top: 10px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>门诊缴费</title>
|
||||
<title>门诊缴费凭证T1</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-size: 32px; margin-top: 20px;">
|
||||
<div style="">
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${hospitalName}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; font-size: 28px;">****<span th:text="${registeTerminalName}"></span></span>****</div>
|
||||
<div style="text-align: center; font-weight: 700; margin-bottom: -10px; margin-top: -10px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${registeType}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; margin-top: -15px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="font-size: 32px;font-weight: bold;">
|
||||
<div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${hospitalName}"></span>
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 28px;"><span>****<span
|
||||
th:text="${registeTerminalName}"></span>****</span>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${registeType}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
</div>
|
||||
<div style="font-weight: 700; font-size: 37px;">
|
||||
<div style="margin-left: 35px;">姓  名:<span th:text="${name}"></span></div>
|
||||
@@ -19,13 +22,14 @@
|
||||
<div style="margin-left: 35px;">出生日期:<span th:text="${birthDate}"></span></div>
|
||||
<div style="margin-left: 35px;">总 费 用:<span th:text="${totalFee}"></span> 元</div>
|
||||
<div style="margin-left: 35px;">支付方式:<span th:text="${paymentMethod}"></span></div>
|
||||
<div style="margin-left: 35px;">交易流水:<span style="font-size: 35px;" th:text="${transactionNumber}"></span></div>
|
||||
<div style="margin-left: 35px;">交易流水:<span style="font-size: 35px;" th:text="${transactionNumber}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-weight: 700; margin-top: 8px;">
|
||||
<!-- <div style="margin-top: -40px; text-align: center; ">-----------------------------------------------------------------------------</div>-->
|
||||
<div style="font-size: 36px;margin-left: 35px; ">备注:缴费凭证,请妥善保管!</div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">终 端 号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin-bottom: 3px;"></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;margin-top: 10px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -3,38 +3,23 @@
|
||||
<head>
|
||||
<title>挂号单</title>
|
||||
</head>
|
||||
<style>
|
||||
.x-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.x-start {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.x-end {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.x-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div style="font-size: 32px; margin-top: 20px;">
|
||||
<div style="">
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${hospitalName}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; font-size: 28px;">****<span th:text="${registeTerminalName}"></span></span>****</div>
|
||||
<div style="text-align: center; font-weight: 700; margin-bottom: -10px; margin-top: -10px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="text-align: center; font-size: 40px; font-weight: 700;"><span th:text="${registeType}"></span></div>
|
||||
<div style="text-align: center; font-weight: 700; margin-top: -15px;">-----------------------------------------------------------------------------</div>
|
||||
<div style="font-size: 32px;font-weight: bold;">
|
||||
<div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${hospitalName}"></span>
|
||||
</div>
|
||||
<div style="text-align: center; font-size: 28px;"><span>****<span
|
||||
th:text="${registeTerminalName}"></span>****</span>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="text-align: center; font-size: 40px; "><span th:text="${registeType}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
</div>
|
||||
<div style="font-weight: 700; font-size: 37px;">
|
||||
<div style=" font-size: 37px;">
|
||||
<div style="margin-left: 35px;">姓  名:<span th:text="${name}"></span></div>
|
||||
<div style="margin-left: 35px;">性  别:<span th:text="${gender}"></span></div>
|
||||
<div style="margin-left: 35px;">年  龄:<span th:text="${age}"></span></div>
|
||||
<div style="margin-left: 35px;">出生日期:<span th:text="${birthDate}"></span></div>
|
||||
<!-- <div style="margin-left: 35px;">卡  号:<span th:text="${cardNumber}"></span></div>-->
|
||||
<div style="margin-left: 35px;">门 诊 号:<span th:text="${outpatientNumber}"></span></div>
|
||||
<div style="margin-left: 35px;">就诊科室:<span th:text="${department}"></span></div>
|
||||
<div style="margin-left: 35px;">出诊级别:<span th:text="${visitLevel}"></span></div>
|
||||
@@ -43,20 +28,18 @@
|
||||
<div style="margin-left: 35px;">挂号日期:<span th:text="${registerDate}"></span></div>
|
||||
<div style="margin-left: 35px;">总 费 用:<span th:text="${totalFee}"></span> 元</div>
|
||||
<div style="margin-left: 35px;">支付方式:<span th:text="${paymentMethod}"></span></div>
|
||||
<!-- <div style="margin-left: 35px;">订 单 号:<span style="font-size: 36px;" th:text="${orderNumber}"></span></div>-->
|
||||
<div style="margin-left: 35px;">交易流水:<span style="font-size: 35px;"
|
||||
th:text="${transactionNumber}"></span></div>
|
||||
<div style="width: 100%; text-align: center; margin-bottom: -20px;">
|
||||
<img style="display: inline-block; "
|
||||
th:src="${qrCodeBase64}"
|
||||
alt="QR Code" />
|
||||
<div style="width:100%;text-align: center;">
|
||||
<img th:src="${qrCodeBase64}"
|
||||
alt="#"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-weight: 700; margin-top: 8px;">
|
||||
<div style="margin-top: -40px; text-align: center; ">-----------------------------------------------------------------------------</div>
|
||||
<div style="font-size: 36px;margin-left: 35px; ">备注:凭此条退费,请妥善保管!</div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">终 端 号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="font-size: 36px;margin-left: 35px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="border-bottom: 1px dashed #000;margin-bottom: 3px;"></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 35px;font-size: 36px;margin-top: 10px;">
|
||||
<span>温馨提示:请取走挂号凭证,并妥善保管。如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
BIN
src/main/resources/win32-x86/AliScanFace.dll
Normal file
BIN
src/main/resources/win32-x86/AliScanFace.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/win32-x86/WxpayFaceSDK.dll
Normal file
BIN
src/main/resources/win32-x86/WxpayFaceSDK.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user