提交1111

This commit is contained in:
邱波 2025-03-26 19:11:22 +08:00
parent 7591340195
commit 3c47a0b3f3
20 changed files with 576 additions and 29 deletions

View File

@ -1,14 +0,0 @@
package com.face.attachment.controller;
import com.face.global.controller.ApiBaseController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/api/customer/v1.0/attachment")
@Slf4j
@Tag(name = "人脸融合基础管理")
public class ApiFaceInfoCustomerController extends ApiBaseController {
}

View File

@ -1,12 +0,0 @@
package com.face.attachment.controller;
import com.face.global.controller.ApiBaseController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/api/manage/v1.0/attachment")
@Slf4j
public class ApiFaceInfoManageController extends ApiBaseController {
}

View File

@ -42,7 +42,7 @@ import java.util.Base64;
import java.util.List;
@RestController
@RequestMapping("/manage/v1.0/attachment")
@RequestMapping("/face/attachment")
@Slf4j
@Tag(name = "人脸融合基础管理")
public class FaceInfoManagerController extends ManageBaseController {
@ -170,7 +170,7 @@ public class FaceInfoManagerController extends ManageBaseController {
//活动里面的素材ID
req.setModelId(materialId);
//图片返回方式
//图片返回方式 base64 url
req.setRspImgType("url");
MergeInfo mergeInfo=new MergeInfo();

View File

@ -0,0 +1,14 @@
package com.face.attachment.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@GetMapping("/MP_verify_DHFcsMfp7X1J9K71.txt")
@ResponseBody
public String config() {
return "DHFcsMfp7X1J9K71";
}
}

View File

@ -0,0 +1,128 @@
package com.face.attachment.controller;
import com.face.attachment.entity.FaceWeixin;
import com.face.attachment.mapper.FaceWeixinMapper;
import com.face.attachment.service.IFaceInfoService;
import com.face.attachment.vo.FaceInfoDetailResponseVO;
import com.face.attachment.vo.FaceInfoTxRequestVO;
import com.face.global.controller.ManageBaseController;
import com.face.global.vo.ApiResult;
import com.face.util.ApiResultBuilder;
import com.face.weixin.WXProperties;
import com.face.weixin.WeiXinRemoteService;
import com.tencentcloudapi.common.AbstractModel;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.facefusion.v20220927.FacefusionClient;
import com.tencentcloudapi.facefusion.v20220927.models.FuseFaceRequest;
import com.tencentcloudapi.facefusion.v20220927.models.FuseFaceResponse;
import com.tencentcloudapi.facefusion.v20220927.models.MergeInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
@RestController
@RequestMapping("/face/weixin")
@Slf4j
@Tag(name = "微信信息管理")
public class WeiXinManagerController extends ManageBaseController {
@Autowired
WeiXinRemoteService weiXinRemoteService;
@Autowired
protected WXProperties wxProperties;
@Autowired
protected FaceWeixinMapper faceWeixinMapper;
/**
* 获取配置信息
*
* @param url 当前网页的url
* @return wx.config需要的配置参数
*/
@GetMapping("/config")
@ResponseBody
public Map<String, Object> config(String url) {
FaceWeixin oldfaceWeixin = faceWeixinMapper.selectById(wxProperties.getAppId());
Long nowTime =System.currentTimeMillis() / 1000;
Long hisTime= null!=oldfaceWeixin ? Long.parseLong(oldfaceWeixin.getNowTimestamp()): nowTime;
if(null== oldfaceWeixin ){
FaceWeixin faceWeixin =new FaceWeixin();
HashMap<String,Object> map =getJsDk (url);
faceWeixin.setAppId(wxProperties.getAppId());
faceWeixin.setNonceStr((String) map.get("nonceStr"));
faceWeixin.setNowSignature((String) map.get("signature"));
faceWeixin.setNowTimestamp((String) map.get("timestamp"));
faceWeixinMapper.insert(faceWeixin);
return map;
}else if(((nowTime-hisTime) >6000)){
//已经100分钟了
HashMap<String,Object> map =getJsDk (url);
//faceWeixin.setAppId(wxProperties.getAppId());
oldfaceWeixin.setNonceStr((String) map.get("nonceStr"));
oldfaceWeixin.setNowSignature((String) map.get("signature"));
oldfaceWeixin.setNowTimestamp((String) map.get("timestamp"));
faceWeixinMapper.updateById(oldfaceWeixin);
return map;
}else{
return new HashMap<>() {{
put("appId", wxProperties.getAppId());
put("timestamp", oldfaceWeixin.getNowTimestamp());
put("nonceStr", oldfaceWeixin.getNonceStr());
put("signature", oldfaceWeixin.getNowSignature());
}};
}
}
private HashMap<String,Object> getJsDk(String url){
// 拿到token
String accessToken = weiXinRemoteService.getOfficialAccessToken().getAccess_token();
// 拿到ticket
String ticket = weiXinRemoteService.getTicket(accessToken).getTicket();
// 生成时间戳
String timestamp = System.currentTimeMillis() / 1000 + "";
// 生成随机字符串
String nonceStr = UUID.randomUUID().toString().replace("-", "");
// 进行签名
String signature = this.getSignature(ticket, nonceStr, timestamp, url);
return new HashMap<>() {{
put("appId", wxProperties.getAppId());
put("timestamp", timestamp);
put("nonceStr", nonceStr);
put("signature", signature);
}};
}
/**
* 获取签名
*
* @param ticket 刚刚获取的ticket
* @param nonceStr 随机字符串
* @param timestamp 以秒为单位的时间戳
* @param url 当前网页的URL不包含#及其后面部分
* @return 签名
*/
private String getSignature(String ticket, String nonceStr, String timestamp, String url) {
String concat = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;
return DigestUtils.sha1Hex(concat);
}
}

View File

@ -0,0 +1,36 @@
package com.face.attachment.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
*
*
* @author admin
* @date 2025/03/25
*/
@Data
@TableName("t_face_weixin")
public class FaceWeixin {
/**
*
*/
@TableId
private String appId;
/**
*
*/
private String nowTimestamp;
/**
*
*/
private String nonceStr;
/**
*
*/
private String nowSignature;
}

View File

@ -0,0 +1,14 @@
package com.face.attachment.mapper;
import com.face.attachment.entity.FaceWeixin;
import com.face.global.mapper.GlobalBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface FaceWeixinMapper extends GlobalBaseMapper<FaceWeixin, String> {
}

View File

@ -42,7 +42,7 @@ public class SwaggerConfig {
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/manage/**")
.pathsToMatch("/face/**")
.build();
}

View File

@ -0,0 +1,10 @@
package com.face.weixin;
import lombok.Data;
@Data
public class OfficialProperties {
private String accessTokenUrl;
private String getticketUrl;
}

View File

@ -0,0 +1,113 @@
package com.face.weixin;/*
* 微信公众平台(JAVA) SDK
*
* Copyright (c) 2016, Ansitech Network Technology Co.,Ltd All rights reserved.
* http://www.ansitech.com/weixin/sdk/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.security.MessageDigest;
import java.util.Arrays;
/**
* <p>Title: SHA1算法</p>
*
* @author levi
*/
public final class SHA1 {
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes the raw bytes from the digest.
* @return the formatted bytes.
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文转换成十六进制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
public static String encode(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 用SHA1算法生成安全签名
*
* @param token 票据
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param encrypt 密文
* @return 安全签名
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) {
try {
String[] array = new String[]{token, timestamp, nonce, encrypt};
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexStr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexStr.append(0);
}
hexStr.append(shaHex);
}
return hexStr.toString();
} catch (Exception e) {
//log.error("sha加密生成签名失败:", e);
return null;
}
}
}

View File

@ -0,0 +1,9 @@
package com.face.weixin;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(WXProperties.class)
public class WXInfoConfig {
}

View File

@ -0,0 +1,12 @@
package com.face.weixin;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "wx" )
@Data
public class WXProperties {
private OfficialProperties official;
private String appId;
private String appSecret;
}

View File

@ -0,0 +1,11 @@
package com.face.weixin;
import lombok.Data;
@Data
public class WXRefreshAccessTokenResult {
private String errcode;
private String errmsg;
private String access_token;
private String expires_in;
}

View File

@ -0,0 +1,11 @@
package com.face.weixin;
import lombok.Data;
@Data
public class WXRefreshTicketResult {
private String errcode;
private String errmsg;
private String ticket;
private String expires_in;
}

View File

@ -0,0 +1,12 @@
package com.face.weixin;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public enum WXResponseCode {
USER_REFUSE(43101);
@Getter
private int code;
}

View File

@ -0,0 +1,130 @@
package com.face.weixin;
import com.face.global.enums.CommonCode;
import com.face.util.JsonUtils;
import com.face.util.SystemExceptionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class WeiXinRemoteService {
@Autowired
protected WXProperties wxProperties;
@Autowired
@Qualifier("proxyRestTemplate")
protected RestTemplate restTemplate;
@Autowired
private Environment environment;
public WXRefreshAccessTokenResult getOfficialAccessToken() {
Map<String, String> params = new HashMap<>();
params.put("appid", wxProperties.getAppId());
params.put("secret", wxProperties.getAppSecret());
return restTemplate.getForObject(wxProperties.getOfficial().getAccessTokenUrl(), WXRefreshAccessTokenResult.class, params);
}
public WXRefreshTicketResult getTicket(String accessToken) {
Map<String, String> params = new HashMap<>();
params.put("accessToken", accessToken);
return restTemplate.getForObject(wxProperties.getOfficial().getGetticketUrl(), WXRefreshTicketResult.class, params);
}
public boolean checkResultVO(String function, boolean throwWxError, WxResultVO wxResultVO) {
if (wxResultVO == null || wxResultVO.getErrcode() != WxResultEnum.SUCCESS.getCode()) {
log.error("call function {} fail {}", function, wxResultVO != null ? JsonUtils.toJsonString(wxResultVO) : "no data");
if (throwWxError && wxResultVO != null && StringUtils.isNotBlank(wxResultVO.getErrmsg())) {
throw SystemExceptionUtils.buildBusinessException("callWxMiniNotResultFail", wxResultVO.getErrmsg());
}
if (throwWxError) {
throw SystemExceptionUtils.buildBusinessException("callWxMiniNotResultFail");
}
return false;
}
return true;
}
protected HttpEntity buildJsonRequestBody(Object body, Map<String, String> headers) {
HttpHeaders requestHeaders = new HttpHeaders();
if (headers != null) {
headers.entrySet().forEach(entry -> requestHeaders.add(entry.getKey(), entry.getValue()));
}
requestHeaders.add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
return new HttpEntity(body, requestHeaders);
}
protected <T> T httpPostForStr(String url, Object requstBody, Map<String, String> headers, Map params, TypeReference<T> typeReference) {
log.info("startHttpPostForStr {}", url);
long startTime = System.currentTimeMillis();
try {
HttpEntity httpEntity = null;
if (requstBody != null) {
httpEntity = this.buildJsonRequestBody(requstBody, headers);
}
ResponseEntity<String> response = this.restTemplate.postForEntity(url, httpEntity, String.class, params);
String body = response.getBody();
if (response.getStatusCode().is2xxSuccessful()) {
try {
return JsonUtils.parseObject(body, typeReference);
} catch (Exception e) {
log.error("httpPostForStrError {} {}", response.getStatusCode().value(), body, e);
throw SystemExceptionUtils.buildBusinessException(CommonCode.ERROR.getCode(), body, e);
}
} else {
log.error("httpPostForStrError {} {}", url, response.getStatusCode().value(), body);
throw SystemExceptionUtils.buildBusinessException(CommonCode.ERROR.getCode(), body);
}
} finally {
log.info("endHttpPostForStr {} {}", url, System.currentTimeMillis() - startTime);
}
}
protected <T> T httpGetForStr(String url, Map<String, String> headers, Map params, TypeReference<T> typeReference) {
log.info("startHttpGetForStr {}", url);
long startTime = System.currentTimeMillis();
try {
HttpHeaders httpHeaders = new HttpHeaders();
if (!CollectionUtils.isEmpty(headers)) {
headers.entrySet().forEach(entry -> {
httpHeaders.add(entry.getKey(), entry.getValue());
});
}
ResponseEntity<String> response = this.restTemplate.exchange(url, HttpMethod.GET, null, String.class, params);
String body = response.getBody();
if (response.getStatusCode().is2xxSuccessful()) {
try {
return JsonUtils.parseObject(body, typeReference);
} catch (Exception e) {
log.error("httpGetForStrError {} {}", response.getStatusCode().value(), body, e);
throw SystemExceptionUtils.buildBusinessException(CommonCode.ERROR.getCode(), body);
}
} else {
log.error("httpGetForStrError {} {}", url, response.getStatusCode().value(), body);
throw SystemExceptionUtils.buildBusinessException(CommonCode.ERROR.getCode(), body);
}
} finally {
log.info("endHttpGetForStr {} {}", url, System.currentTimeMillis() - startTime);
}
}
}

View File

@ -0,0 +1,17 @@
package com.face.weixin;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public enum WxResultEnum {
SUCCESS(0, "SUCCESS","OK"), COMMON_ERROR(-1, "ERROR", "error");
@Getter
private int code;
@Getter
private String name;
@Getter
private String msg;
}

View File

@ -0,0 +1,9 @@
package com.face.weixin;
import lombok.Data;
@Data
public class WxResultVO {
private int errcode;
private String errmsg;
}

View File

@ -0,0 +1,35 @@
package com.face.weixin;
import com.face.global.validator.EnumValidator;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public enum WxTypeEnum implements EnumValidator {
OFFICIAL("1"), MINI("2");
@Getter
private String code;
public static WxTypeEnum getByCode(String type) {
WxTypeEnum[] values = WxTypeEnum.values();
for (WxTypeEnum value : values) {
if (value.getCode().equals(type)) {
return value;
}
}
return null;
}
@Override
public String toString() {
return this.code;
}
@Override
public Object getValue() {
return this.code;
}
}

View File

@ -64,4 +64,16 @@ mybatis-plus:
global-config:
db-config:
update-strategy: always
wx:
#appId (到时候换成自己公众号的)
appId: wx8716163374cb8e6f
#appSecret到时候换成自己公众号的
appSecret: 2fee63925e908986348f5a59c39b34a5
official:
#参考以下文档获取access_token有效期7200秒开发者必须在自己的服务全局缓存access_token
getticketUrl: https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={accessToken}&type=jsapi
#用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket有效期7200秒开发者必须在自己的服务全局缓存jsapi_ticket
accessTokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}