Compare commits

...

24 Commits

Author SHA1 Message Date
da20a44049 景区设置添加一个,返回status状态 2025-04-17 10:49:31 +08:00
877f37b6f9 系统支付对接、避免返回过多数据 2025-04-17 10:49:31 +08:00
f6f847e41c 支付 2025-04-17 10:49:11 +08:00
8ac386242d 多给一天 2025-04-15 03:17:32 +08:00
3f11aadd75 1 2025-04-15 03:17:20 +08:00
d620bde5fa 补救措施没结果时,不使用补救措施结果 2025-04-15 03:17:14 +08:00
5a7c39429e bug 2025-04-15 03:13:13 +08:00
7234e08616 定时任务时支持补救措施 2025-04-12 19:08:23 +08:00
45409ba1ab 渲染机支持仅渲染几个景区的工作 2025-04-12 14:35:26 +08:00
b5b9064f30 区分临时和本地存储 2025-04-12 14:34:59 +08:00
d0d4e37526 修改 2025-04-11 16:58:52 +08:00
73d393b436 entityId格式 2025-04-09 10:05:39 +08:00
2835346447 多个wxMpConfig兼容 2025-04-08 16:24:33 +08:00
75eb3732fc 价格配置加个状态 2025-04-08 16:24:33 +08:00
692df3a1a2 避免null 2025-04-08 14:40:57 +08:00
fe0397af8e 统计:基础 2025-04-08 14:40:57 +08:00
f17e2364b6 entityId重入一致性 2025-04-08 14:40:57 +08:00
59978b6be5 修整代码 2025-04-08 14:40:57 +08:00
20d78cb487 不存request了 2025-04-08 14:32:45 +08:00
4e9d6807d6 再改改 2025-04-08 01:28:12 +08:00
8af8bd6bcf pc后台统计 2025-04-08 01:28:04 +08:00
248c06a30c 速率限制 2025-04-08 01:27:40 +08:00
dabdde33a6 空! 2025-04-07 16:36:45 +08:00
c9a4116ed6 捯饬捯饬代码 2025-04-07 16:36:45 +08:00
105 changed files with 1915 additions and 1732 deletions

View File

@ -1,11 +0,0 @@
package com.ycwl.basic.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
public @interface RequestToFile {
}

View File

@ -1,69 +0,0 @@
package com.ycwl.basic.aspectj;
import com.ycwl.basic.annotation.RequestToFile;
import com.ycwl.basic.config.CachedBodyHttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.stream.Collectors;
@Aspect
@Component
@Slf4j
public class HttpSaver {
@Pointcut("@annotation(com.ycwl.basic.annotation.RequestToFile)")
public void requestToFilePointCut() {
}
@After("requestToFilePointCut()")
public void requestToFile() throws IOException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
saveRequestToFile(request);
}
public static void saveRequestToFile(HttpServletRequest request) throws IOException {
File file = new File("./request/"+System.currentTimeMillis()+".http");
// 写入文件
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
file.createNewFile();
}
try (java.io.FileWriter writer = new java.io.FileWriter(file, true)) {
writer.append(request.getMethod()).append(" ").append(request.getRequestURL());
String queryString = request.getQueryString();
if (queryString != null) {
writer.append("?").append(queryString);
}
writer.append("\r\n");
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
writer.append(headerName).append(": ").append(request.getHeader(headerName)).append("\r\n");
}
writer.append("\r\n");
// 获取body
CachedBodyHttpServletRequest cachedRequest = (CachedBodyHttpServletRequest) request;
writer.append(new String(cachedRequest.getCachedBody()));
writer.append("\r\n");
}
}
}

View File

@ -1,59 +0,0 @@
package com.ycwl.basic.config;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
// 缓存请求体内容
InputStream requestInputStream = request.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read;
while ((read = requestInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, read);
}
cachedBody = byteArrayOutputStream.toByteArray();
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 不需要实现
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public byte[] getCachedBody() {
return cachedBody;
}
}

View File

@ -3,8 +3,7 @@ package com.ycwl.basic.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.ycwl.basic.interceptor.AuthInterceptor;
import com.ycwl.basic.xss.XssJacksonDeserializer;
import com.ycwl.basic.xss.XssJacksonSerializer;
import com.ycwl.basic.stats.interceptor.StatsInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -29,13 +28,17 @@ public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private StatsInterceptor statsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
// 拦截除指定接口外的所有请求通过判断 注解 来决定是否需要做登录验证
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/api-docs", "/doc.html/**", "/error", "/csrf", "/");
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/api-docs", "/doc.html/**", "/error", "/");
registry.addInterceptor(statsInterceptor)
.addPathPatterns("/api/mobile/**");
}
/**
@ -78,25 +81,6 @@ public class WebMvcConfig implements WebMvcConfigurer {
}
@Bean
public WebMvcConfigurer createConvert() {
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper mapper = builder.build();
/*注入自定义的序列化工具将RequestBody的参数进行转译后传输*/
SimpleModule simpleModule = new SimpleModule();
// XSS序列化
simpleModule.addSerializer(String.class, new XssJacksonSerializer());
simpleModule.addDeserializer(String.class, new XssJacksonDeserializer());
mapper.registerModule(simpleModule);
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
};
}
@Autowired
private StringHttpMessageConverter stringHttpMessageConverter;
@Autowired

View File

@ -1,73 +0,0 @@
package com.ycwl.basic.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信小程序配置
*
* @author songmingsong
**/
@Data
@Component
@ConfigurationProperties(prefix = "wx")
public class WechatConfig {
/**
* 公众号的appId
*/
private String appId;
/**
* 公众号的密钥
*/
private String appSecret;
/**
* 小程序的AppId
*/
private String miniProgramAppId;
/**
* 小程序的secret
*/
private String miniProgramSecret;
/**
* 申请openid授权
*/
private String grandType;
/**
* 商户号
*/
private String mchId;
/**
* 商户证书序列号
*/
private String mchSerialNo;
/**
* 支付回调接口地址
*/
private String payNotifyUrl;
/**
* 退款回调接口地址
*/
private String refundNotifyUrl;
/**
* 商户API私钥路径
*/
private String keyPath;
/**
* 商户APIV3密钥
*/
private String apiV3;
}

View File

@ -6,7 +6,6 @@ import com.ycwl.basic.biz.PriceBiz;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.goods.GoodsPriceQueryReq;
import com.ycwl.basic.model.mobile.order.IsBuyBatchRespVO;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.order.OrderAppPageReq;
@ -14,12 +13,7 @@ import com.ycwl.basic.model.mobile.order.RefundOrderReq;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.order.req.CreateBatchOrderReqVO;
import com.ycwl.basic.model.pc.order.req.CreateOrderReqVO;
import com.ycwl.basic.model.pc.order.req.OrderAddReq;
import com.ycwl.basic.model.pc.order.resp.OrderAppRespVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.repository.PriceRepository;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
@ -28,6 +22,8 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @Authorlongbinbin
* @Date2024/12/4 17:16
@ -62,7 +58,7 @@ public class AppOrderController {
@ApiOperation("用户端订单新增")
@PostMapping("/addOrder")
public ApiResponse<WxPayRespVO> addOrder(@RequestBody CreateOrderReqVO orderAddReq) throws Exception {
public ApiResponse<Map<String, Object>> addOrder(@RequestBody CreateOrderReqVO orderAddReq) throws Exception {
JwtInfo worker = JwtTokenUtil.getWorker();
return orderService.createOrder(worker.getUserId(), orderAddReq);
}
@ -70,7 +66,7 @@ public class AppOrderController {
@ApiOperation("用户端打包订单新增")
@PostMapping("/addBatchOrder")
public ApiResponse<WxPayRespVO> addOrder(@RequestBody CreateBatchOrderReqVO batchOrderReqVO) throws Exception {
public ApiResponse<Map<String, Object>> addOrder(@RequestBody CreateBatchOrderReqVO batchOrderReqVO) throws Exception {
JwtInfo worker = JwtTokenUtil.getWorker();
return orderService.createBatchOrder(worker.getUserId(), batchOrderReqVO);
}

View File

@ -8,6 +8,7 @@ import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicConfigResp;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.AppScenicService;
@ -51,9 +52,26 @@ public class AppScenicController {
@GetMapping("/{id}/config")
@IgnoreToken
public ApiResponse<ScenicConfigEntity> getConfig(@PathVariable Long id){
public ApiResponse<ScenicConfigResp> getConfig(@PathVariable Long id){
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(id);
return ApiResponse.success(scenicConfig);
ScenicConfigResp resp = new ScenicConfigResp();
resp.setBookRoutine(scenicConfig.getBookRoutine());
resp.setForceFinishTime(scenicConfig.getForceFinishTime());
resp.setTourTime(scenicConfig.getTourTime());
resp.setSampleStoreDay(scenicConfig.getSampleStoreDay());
resp.setFaceStoreDay(scenicConfig.getFaceStoreDay());
resp.setVideoStoreDay(scenicConfig.getVideoStoreDay());
resp.setAllFree(scenicConfig.getAllFree());
resp.setDisableSourceVideo(scenicConfig.getDisableSourceVideo());
resp.setDisableSourceImage(scenicConfig.getDisableSourceImage());
resp.setAntiScreenRecordType(scenicConfig.getAntiScreenRecordType());
resp.setVideoSourceStoreDay(scenicConfig.getVideoSourceStoreDay());
resp.setImageSourceStoreDay(scenicConfig.getImageSourceStoreDay());
resp.setUserSourceExpireDay(scenicConfig.getUserSourceExpireDay());
resp.setBrokerDirectRate(scenicConfig.getBrokerDirectRate());
resp.setVideoSourcePackHint(scenicConfig.getVideoSourcePackHint());
resp.setImageSourcePackHint(scenicConfig.getImageSourcePackHint());
return ApiResponse.success(resp);
}
@ApiOperation("查询景区设备总数和拍到用户的机位数量")

View File

@ -2,8 +2,6 @@ package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.annotation.RequestToFile;
import com.ycwl.basic.aspectj.HttpSaver;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
@ -12,6 +10,7 @@ import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
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;
@ -35,12 +34,6 @@ public class AppWxPayController {
@Autowired
private WxPayService wxPayService;
@ApiOperation(value = "微信预支付", notes = "微信预支付")
@PostMapping("/createOrder")
public ApiResponse<WxPayRespVO> createOrder(@RequestBody WXPayOrderReqVO req) throws Exception {
return ApiResponse.success(wxPayService.createOrder(req));
}
@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
@PostMapping("/payNotify")
@IgnoreToken
@ -48,29 +41,19 @@ public class AppWxPayController {
wxPayService.payNotify(request);
return ApiResponse.success(BizCodeEnum.REQUEST_OK);
}
@ApiOperation(value = "微信退款", notes = "微信退款")
@PostMapping("/refundOrder")
public ApiResponse<?> refundOrder(@RequestBody String orderId) throws Exception {
return ApiResponse.buildResult(wxPayService.refundOrder(orderId) ?
BizCodeEnum.SUCCESS :
BizCodeEnum.ADVANCE_PAYMENT_REFUND_FAILED);
@PostMapping("/{scenicId}/payNotify")
@IgnoreToken
public ApiResponse<?> payNotifyByScenicId(@PathVariable Long scenicId, HttpServletRequest request) {
wxPayService.payNotify(scenicId, request);
return ApiResponse.success(BizCodeEnum.REQUEST_OK);
}
@ApiOperation(value = "微信支付退款回调", notes = "微信支付退款回调")
@PostMapping("/refundNotify")
@PostMapping("/{scenicId}/refundNotify")
@IgnoreToken
public ApiResponse<?> refundNotify(@RequestBody String refundResult) throws GeneralSecurityException, IOException {
return ApiResponse.buildResult(wxPayService.refundNotify(refundResult) ?
public ApiResponse<?> refundNotify(@PathVariable Long scenicId, HttpServletRequest request) throws GeneralSecurityException, IOException {
return ApiResponse.buildResult(wxPayService.refundNotify(scenicId, request) ?
BizCodeEnum.SUCCESS :
BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_REFUND_FAILED);
}
@ApiOperation(value = "微信关闭订单", notes = "微信关闭订单")
@PostMapping("/closeOrder")
@IgnoreToken
public ApiResponse<?> closeOrder(@RequestBody String orderId) {
wxPayService.closeOrder(orderId);
return ApiResponse.buildResult(BizCodeEnum.REQUEST_OK);
}
}

View File

@ -3,7 +3,6 @@ package com.ycwl.basic.controller.pc;
import com.ycwl.basic.model.pc.broker.entity.BrokerEntity;
import com.ycwl.basic.model.pc.broker.req.BrokerRecordReqQuery;
import com.ycwl.basic.model.pc.broker.req.BrokerReqQuery;
import com.ycwl.basic.model.pc.broker.resp.BrokerRecordRespVO;
import com.ycwl.basic.model.pc.broker.resp.BrokerRespVO;
import com.ycwl.basic.model.pc.broker.resp.DailySummaryRespVO;
import com.ycwl.basic.service.pc.BrokerRecordService;
@ -131,7 +130,7 @@ public class BrokerController {
try {
WxMpUtil.generateWXAQRCode(appId, appSecret, appState, path, filePath);
File file = new File(filePath);
String s = adapter.uploadFile(file, filePath);
String s = adapter.uploadFile(null, file, filePath);
file.delete();
adapter.setAcl(StorageAcl.PUBLIC_READ, filePath);
return ApiResponse.success(s);

View File

@ -47,6 +47,15 @@ public class PriceConfigController {
public ApiResponse<Boolean> deletePriceConfig(@PathVariable Integer id) {
priceRepository.clearPriceCache(id);
priceConfigService.removeById(id);
priceRepository.clearPriceCache(id);
return ApiResponse.success(true);
}
@PostMapping("/{id}/status")
public ApiResponse<Boolean> updateStatus(@PathVariable Integer id) {
priceRepository.clearPriceCache(id);
priceConfigService.updateStatus(id);
priceRepository.clearPriceCache(id);
return ApiResponse.success(true);
}

View File

@ -1,10 +1,12 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
@ -35,6 +37,8 @@ public class ScenicController {
@Autowired
private ScenicRepository scenicRepository;
@Autowired
private AppStatisticsService appStatisticsService;
@ApiOperation("分页查询景区")
@PostMapping("/page")
@ -117,7 +121,7 @@ public class ScenicController {
try {
WxMpUtil.generateWXAQRCode(appId, appSecret, appState, path, filePath);
File file = new File(filePath);
String s = adapter.uploadFile(file, filePath);
String s = adapter.uploadFile(null, file, filePath);
file.delete();
adapter.setAcl(StorageAcl.PUBLIC_READ, filePath);
return ApiResponse.success(s);
@ -125,4 +129,25 @@ public class ScenicController {
return ApiResponse.fail("生成二维码失败");
}
}
@PostMapping("/{scenicId}/one")
public ApiResponse getStatisticsOne(@PathVariable("scenicId") Long scenicId, @RequestBody CommonQueryReq query) {
query.setScenicId(scenicId);
return appStatisticsService.oneStatistics(query);
}
@PostMapping("/{scenicId}/two")
public ApiResponse getStatisticsTwo(@PathVariable("scenicId") Long scenicId, @RequestBody CommonQueryReq query) {
query.setScenicId(scenicId);
return appStatisticsService.twoStatistics(query);
}
@PostMapping("/{scenicId}/three")
public ApiResponse getStatisticsThree(@PathVariable("scenicId") Long scenicId, @RequestBody CommonQueryReq query) {
query.setScenicId(scenicId);
return appStatisticsService.freeStatistics(query);
}
@PostMapping("/{scenicId}/fun")
public ApiResponse getStatisticsFun(@PathVariable("scenicId") Long scenicId, @RequestBody CommonQueryReq query) {
query.setScenicId(scenicId);
return appStatisticsService.userConversionFunnel(query);
}
}

View File

@ -0,0 +1,75 @@
package com.ycwl.basic.controller.proxy;
import com.ycwl.basic.annotation.IgnoreToken;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@RestController
public class ProxyController {
@IgnoreToken
@RequestMapping(value = "/proxy", method = RequestMethod.GET)
public void proxy(@RequestParam(value = "url") String url,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url; // 或根据业务逻辑选择默认协议
}
// 新增User-Agent检测逻辑
String userAgent = request.getHeader("User-Agent");
if (userAgent != null && userAgent.contains("Lavf/")) {
response.sendRedirect(url);
return;
}
// 创建HTTP连接
URL urlObj = new URL(url);
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
// 设置请求方法和请求头
connection.setRequestMethod("GET");
for (Enumeration<String> headers = request.getHeaderNames(); headers.hasMoreElements();) {
String headerName = headers.nextElement();
connection.addRequestProperty(headerName, request.getHeader(headerName));
}
// 处理响应
int responseCode = connection.getResponseCode();
response.setStatus(responseCode);
// 转发响应头
Map<String, List<String>> headerFields = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()) {
if (entry.getKey() != null) {
for (String value : entry.getValue()) {
response.addHeader(entry.getKey(), value);
}
}
}
// 流式传输响应体
try (InputStream inputStream = connection.getInputStream();
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
return;
}
}
}

View File

@ -17,8 +17,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@IgnoreToken
@RestController
@Api(tags = "渲染端对接接口")

View File

@ -1,14 +1,11 @@
package com.ycwl.basic.controller.viid;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ycwl.basic.annotation.IgnoreLogReq;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.annotation.RequestToFile;
import com.ycwl.basic.aspectj.HttpSaver;
import com.ycwl.basic.facebody.FaceBodyFactory;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.entity.AddFaceResp;
import com.ycwl.basic.mapper.DeviceMapper;
@ -17,14 +14,11 @@ import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.viid.entity.DeviceIdObject;
import com.ycwl.basic.model.viid.entity.FaceListObject;
import com.ycwl.basic.model.viid.entity.FaceObject;
import com.ycwl.basic.model.viid.entity.FacePositionObject;
import com.ycwl.basic.model.viid.entity.ImageListObject;
import com.ycwl.basic.model.viid.entity.ImageObject;
import com.ycwl.basic.model.viid.entity.ResponseStatusObject;
import com.ycwl.basic.model.viid.entity.SubImageInfoObject;
import com.ycwl.basic.model.viid.entity.SubImageList;
@ -69,12 +63,12 @@ import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.ycwl.basic.constant.StorageConstant.PHOTO_PATH;
import static com.ycwl.basic.service.task.impl.TaskFaceServiceImpl.generateEntityId;
@IgnoreToken
@RestController
@ -93,17 +87,20 @@ public class ViidController {
private ScenicRepository scenicRepository;
@Autowired
private TaskFaceService taskFaceService;
private final Map<String, ThreadPoolExecutor> executors = new ConcurrentHashMap<>();
private final Map<Long, ThreadPoolExecutor> executors = new ConcurrentHashMap<>();
@Autowired
private ScenicService scenicService;
private ThreadPoolExecutor getExecutor(String deviceId) {
ThreadPoolExecutor executor = executors.get(deviceId);
if (executor == null) {
executor = new ThreadPoolExecutor(4, 4096, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(4096));
executors.put(deviceId, executor);
}
return executor;
private ThreadPoolExecutor getExecutor(Long scenicId) {
return executors.computeIfAbsent(scenicId, k -> {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNamePrefix("VIID-" + scenicId + "-t")
.build();
return new ThreadPoolExecutor(
4, 4096, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(4096),
threadFactory);
});
}
// region 注册注销基础接口
@ -310,11 +307,11 @@ public class ViidController {
String url = adapter.uploadFile(file, "user-face", UUID.randomUUID() + "." + ext);
faceSample.setFaceUrl(url);
faceSampleMapper.add(faceSample);
ThreadPoolExecutor executor = getExecutor(device.getId().toString());
ThreadPoolExecutor executor = getExecutor(scenicId);
executor.execute(() -> {
if (faceBodyAdapter != null) {
taskFaceService.assureFaceDb(faceBodyAdapter, scenicId.toString());
AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), generateEntityId(faceSample), url, newFaceSampleId.toString());
AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), faceSample.getId().toString(), url, newFaceSampleId.toString());
if (addFaceResp != null) {
faceSample.setScore(addFaceResp.getScore());
faceSampleMapper.update(faceSample);
@ -372,11 +369,11 @@ public class ViidController {
faceSample.setFaceUrl(url);
faceSampleMapper.add(faceSample);
DynamicTaskGenerator.addTask(faceSample.getId());
ThreadPoolExecutor executor = getExecutor(device.getId().toString());
ThreadPoolExecutor executor = getExecutor(scenicId);
executor.execute(() -> {
if (faceBodyAdapter != null) {
taskFaceService.assureFaceDb(faceBodyAdapter, scenicId.toString());
AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), generateEntityId(faceSample), url, newFaceSampleId.toString());
AddFaceResp addFaceResp = faceBodyAdapter.addFace(scenicId.toString(), faceSample.getId().toString(), url, newFaceSampleId.toString());
if (addFaceResp != null) {
faceSample.setScore(addFaceResp.getScore());
faceSampleMapper.update(faceSample);
@ -402,8 +399,6 @@ public class ViidController {
@RequestMapping(value = "/Images", method = RequestMethod.POST)
@IgnoreLogReq
public VIIDBaseResp images(HttpServletRequest request, @RequestBody ImageUploadReq req) throws IOException {
// log.info("Images:{}", req);
HttpSaver.saveRequestToFile(request);
return new VIIDBaseResp(
new ResponseStatusObject("1", "/VIID/Images", "0", "OK", sdfTime.format(new Date()))
);

View File

@ -1,25 +1,18 @@
package com.ycwl.basic.controller.vpt;
import com.alibaba.fastjson.JSONObject;
import com.ycwl.basic.annotation.IgnoreLogReq;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.device.entity.common.FileObject;
import com.ycwl.basic.device.operator.VptPassiveStorageOperator;
import com.ycwl.basic.device.operator.VptPassiveStorageOperator;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.wvp.WvpSyncReqVo;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -29,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@IgnoreToken
@ -48,7 +40,7 @@ public class VptController {
}
@PostMapping("/scenic/{scenicId}/{taskId}/uploadUrl")
public String uploadUrl(@PathVariable("scenicId") Long scenicId, @PathVariable("taskId") Long taskId) {
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId);
IStorageAdapter adapter = scenicService.getScenicLocalStorageAdapter(scenicId);
String filename = StorageUtil.joinPath(StorageConstant.VIDEO_PIECE_PATH, taskId.toString() + ".mp4");
String urlForUpload = adapter.getUrlForUpload(new Date(System.currentTimeMillis() + 1000 * 60 * 60), "video/mp4", filename);
urlForUpload = urlForUpload.replace("-internal.aliyuncs.com", ".aliyuncs.com");
@ -56,7 +48,7 @@ public class VptController {
}
@PostMapping("/scenic/{scenicId}/{taskId}/success")
public ApiResponse<String> success(@PathVariable("scenicId") Long scenicId, @PathVariable("taskId") Long taskId, @RequestBody FileObject fileObject) {
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId);
IStorageAdapter adapter = scenicService.getScenicLocalStorageAdapter(scenicId);
String filename = StorageUtil.joinPath(StorageConstant.VIDEO_PIECE_PATH, taskId.toString() + ".mp4");
fileObject.setUrl(adapter.getUrl(filename));
adapter.setAcl(StorageAcl.PUBLIC_READ, filename);

View File

@ -1,24 +1,19 @@
package com.ycwl.basic.controller.wvp;
import com.alibaba.fastjson.JSONObject;
import com.ycwl.basic.annotation.IgnoreLogReq;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.device.entity.common.FileObject;
import com.ycwl.basic.device.operator.WvpPassiveStorageOperator;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.wvp.WvpSyncReqVo;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.DeviceService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -28,7 +23,6 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@IgnoreToken
@ -51,7 +45,7 @@ public class WvpController {
@PostMapping("/scenic/{scenicId}/{taskId}/uploadUrl")
public String uploadUrl(@PathVariable("scenicId") Long scenicId, @PathVariable("taskId") Long taskId) {
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId);
IStorageAdapter adapter = scenicService.getScenicLocalStorageAdapter(scenicId);
String filename = StorageUtil.joinPath(StorageConstant.VIDEO_PIECE_PATH, taskId.toString() + ".mp4");
String urlForUpload = adapter.getUrlForUpload(new Date(System.currentTimeMillis() + 1000 * 60 * 60), "video/mp4", filename);
urlForUpload = urlForUpload.replace("-internal.aliyuncs.com", ".aliyuncs.com");
@ -59,7 +53,7 @@ public class WvpController {
}
@PostMapping("/scenic/{scenicId}/{taskId}/success")
public ApiResponse<String> success(@PathVariable("scenicId") Long scenicId, @PathVariable("taskId") Long taskId, @RequestBody FileObject fileObject) {
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId);
IStorageAdapter adapter = scenicService.getScenicLocalStorageAdapter(scenicId);
String filename = StorageUtil.joinPath(StorageConstant.VIDEO_PIECE_PATH, taskId.toString() + ".mp4");
fileObject.setUrl(adapter.getUrl(filename));
adapter.setAcl(StorageAcl.PUBLIC_READ, filename);

View File

@ -1,20 +0,0 @@
package com.ycwl.basic.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 是否同意用户协议枚举
*
* @author songmingsong
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum AgreementEnum {
AGREE(1, "同意"),
NOT_AGREE(0, "未同意");
private int type;
private String remark;
}

View File

@ -1,69 +0,0 @@
package com.ycwl.basic.enums;
import java.util.HashMap;
import java.util.Map;
/**
* @Authorlongbinbin
* @Date2024/12/6 16:46
*/
public enum GoodsTypeEnum {
VIDEO(1,"成片"),
SOURCE(2,"源素材")
;
public Integer code;
private String value;
public static final Map<Integer, GoodsTypeEnum> cacheMap;
static {
cacheMap = new HashMap<>(GoodsTypeEnum.values().length);
for (GoodsTypeEnum value : GoodsTypeEnum.values()) {
cacheMap.put(value.code, value);
}
}
public static final java.util.Map<String, GoodsTypeEnum> valueMap;
static {
valueMap = new HashMap<>(GoodsTypeEnum.values().length);
for (GoodsTypeEnum value : GoodsTypeEnum.values()) {
valueMap.put(value.value, value);
}
}
GoodsTypeEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
/**
* 获取value值
*/
public static String getValue(Integer noticeMethod) {
if (noticeMethod == null) {
return null;
}
GoodsTypeEnum GoodsTypeEnum = cacheMap.get(noticeMethod);
if (GoodsTypeEnum == null) {
return null;
}
return GoodsTypeEnum.value;
}
/**
* 获取code值
*/
public static Integer getCode(String noticeMethod) {
if (noticeMethod == null) {
return -1;
}
GoodsTypeEnum GoodsTypeEnum = valueMap.get(noticeMethod);
if (GoodsTypeEnum == null) {
return -1;
}
return GoodsTypeEnum.code;
}
}

View File

@ -1,9 +1,6 @@
package com.ycwl.basic.exception;
import com.ycwl.basic.aspectj.HttpSaver;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.notify.NotifyFactory;
import com.ycwl.basic.notify.entity.NotifyContent;
import com.ycwl.basic.utils.ApiResponse;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.slf4j.Logger;
@ -12,14 +9,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.stream.Collectors;
/**
* @date 2022年09月23日 10:19

View File

@ -249,7 +249,7 @@ public class AliFaceBodyAdapter implements IFaceBodyAdapter {
return response.getData().getEntities().stream().map(ListFaceEntitiesResponse.Data.Entity::getEntityId).collect(Collectors.toList());
} catch (ClientException e) {
log.error("获取人脸数据失败!", e);
return null;
return Collections.emptyList();
}
}
}

View File

@ -172,6 +172,9 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
List<String> tokenList = listUserFace(dbName, entityId);
if (tokenList == null) {
return false;
}
AtomicInteger count = new AtomicInteger(0);
tokenList.forEach(faceToken -> {
try {
@ -232,9 +235,14 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
}
public List<String> listUserFace(String dbName, String entityId) {
IRateLimiter listFaceLimiter = getLimiter(LOCK_TYPE.LIST_FACE);
try {
AipFace client = getClient();
HashMap<String, String> options = new HashMap<>();
try {
listFaceLimiter.acquire();
} catch (InterruptedException ignored) {
}
JSONObject response = client.faceGetlist(entityId, dbName, options);
if (response.getInt("error_code") == 0) {
JSONObject resultObj = response.getJSONObject("result");
@ -249,13 +257,16 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
} else {
return Collections.emptyList();
}
} else if (response.getInt("error_code") == 223103) {
// 用户不存在
return Collections.emptyList();
} else {
log.warn("获取人脸列表失败!{}", response);
return Collections.emptyList();
return null;
}
} catch (Exception e) {
log.error("获取人脸列表失败!", e);
return Collections.emptyList();
return null;
}
}
@ -325,21 +336,21 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
private IRateLimiter getLimiter(LOCK_TYPE type) {
switch (type) {
case ADD_DB:
return addDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return addDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
case ADD_FACE:
return addFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(config.getAddQps()));
case LIST_DB:
return listDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return listDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
case LIST_FACE:
return listFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return listFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
case SEARCH_FACE:
return searchFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(config.getSearchQps()));
case DELETE_DB:
return deleteDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return deleteDbLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
case DELETE_ENTITY:
return deleteEntityLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return deleteEntityLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
case DELETE_FACE:
return deleteFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(100, TimeUnit.MILLISECONDS));
return deleteFaceLimiters.computeIfAbsent(config.getAppId(), k -> new FixedRateLimiter(105, TimeUnit.MILLISECONDS));
default:
return new FixedRateLimiter(500, TimeUnit.MILLISECONDS);
}

View File

@ -1,21 +0,0 @@
package com.ycwl.basic.filter;
import com.ycwl.basic.config.CachedBodyHttpServletRequest;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
@Component
public class RequestCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 包装原始请求
HttpServletRequestWrapper wrappedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request);
// 继续处理请求链
chain.doFilter(wrappedRequest, response);
}
}

View File

@ -7,8 +7,6 @@ import com.ycwl.basic.constant.PermissionConstant;
import com.ycwl.basic.constant.RequestConstant;
import com.ycwl.basic.exception.CheckTokenException;
import com.ycwl.basic.exception.MissTokenException;
import com.ycwl.basic.exception.PermissionException;
import com.ycwl.basic.exception.TokenExpireException;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
@ -24,17 +22,12 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
@Slf4j
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Autowired
JwtTokenUtil jwtTokenUtil;
@Autowired
RedisTemplate redisTemplate;

View File

@ -17,4 +17,6 @@ public interface PriceConfigMapper extends BaseMapper<PriceConfigEntity> {
List<PriceConfigRespVO> listByCondition(@Param("req") PriceConfigListReq req);
PriceConfigEntity getPriceByScenicTypeGoods(Long scenicId, Integer type, String goodsId);
int updateStatus(Integer id);
}

View File

@ -54,5 +54,5 @@ public interface TaskMapper {
List<TaskEntity> listEntity(TaskReqQuery taskReqQuery);
List<TaskRespVO> selectNotRunningByScenicId(Long scenicOnly);
List<TaskRespVO> selectNotRunningByScenicList(String scenicOnly);
}

View File

@ -19,6 +19,12 @@ public class IsBuyBatchRespVO {
private BigDecimal slashPrice;
public BigDecimal getPrice() {
if (origPrice == null) {
return BigDecimal.ZERO;
}
if (couponPrice == null) {
return origPrice;
}
return origPrice.subtract(couponPrice);
}
public BigDecimal getDiscountPrice() {

View File

@ -18,6 +18,12 @@ public class IsBuyRespVO {
private BigDecimal slashPrice;
public BigDecimal getPrice() {
if (origPrice == null) {
return BigDecimal.ZERO;
}
if (couponPrice == null) {
return origPrice;
}
return origPrice.subtract(couponPrice);
}
public BigDecimal getDiscountPrice() {

View File

@ -33,6 +33,7 @@ public class PriceConfigEntity {
* 划线价格
*/
private BigDecimal slashPrice;
private Integer status;
private Date createTime;
private Date updateTime;
}

View File

@ -30,5 +30,6 @@ public class PriceConfigRespVO {
* 划线价格
*/
private BigDecimal slashPrice;
private Integer status;
private Date createTime;
}

View File

@ -60,7 +60,7 @@ public class RenderWorkerEntity {
/**
* 是否仅用于指定景区空或0不适用否则为景区ID
*/
private Long scenicOnly;
private String scenicOnly;
/**
* 是否仅用于测试0不是1是
*/

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ycwl.basic.facebody.enums.FaceBodyAdapterType;
import com.ycwl.basic.pay.enums.PayAdapterType;
import com.ycwl.basic.storage.enums.StorageType;
import lombok.Data;
@ -71,6 +72,10 @@ public class ScenicConfigEntity {
private Float faceScoreThreshold;
private StorageType storeType;
private String storeConfigJson;
private StorageType tmpStoreType;
private String tmpStoreConfigJson;
private StorageType localStoreType;
private String localStoreConfigJson;
private BigDecimal brokerDirectRate;
private Integer faceDetectHelperThreshold;
@ -80,4 +85,10 @@ public class ScenicConfigEntity {
private FaceBodyAdapterType faceType;
private String faceConfigJson;
private PayAdapterType payType;
private String payConfigJson;
private String imageSourcePackHint;
private String videoSourcePackHint;
}

View File

@ -0,0 +1,48 @@
package com.ycwl.basic.model.pc.scenic.resp;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ycwl.basic.facebody.enums.FaceBodyAdapterType;
import com.ycwl.basic.pay.enums.PayAdapterType;
import com.ycwl.basic.storage.enums.StorageType;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Authorlongbinbin
* @Date2024/12/2 10:53
* 景区配置
*/
@Data
public class ScenicConfigResp {
/**
* 预约流程1-预约2-在线3-全部
*/
private Integer bookRoutine;
private Integer forceFinishTime;
private Integer tourTime;
/**
* 样本保存时间
*/
private Integer sampleStoreDay;
private Integer faceStoreDay;
/**
* 视频保存时间
*/
private Integer videoStoreDay;
private Integer allFree;
private Integer disableSourceVideo;
private Integer disableSourceImage;
private Integer antiScreenRecordType;
private Integer videoSourceStoreDay;
private Integer imageSourceStoreDay;
private Integer userSourceExpireDay;
private BigDecimal brokerDirectRate;
private String imageSourcePackHint;
private String videoSourcePackHint;
}

View File

@ -0,0 +1,58 @@
package com.ycwl.basic.pay;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.pay.adapter.WxMpPayAdapter;
import com.ycwl.basic.pay.enums.PayAdapterType;
import com.ycwl.basic.pay.exceptions.PayUndefinedException;
import com.ycwl.basic.pay.exceptions.PayUnsupportedException;
import java.util.HashMap;
import java.util.Map;
public class PayFactory {
public static IPayAdapter getAdapter(String typeName) {
PayAdapterType adapterEnum;
try {
adapterEnum = PayAdapterType.valueOf(typeName);
} catch (IllegalArgumentException e) {
throw new PayUnsupportedException("不支持的Adapter类型");
}
return getAdapter(adapterEnum);
}
public static IPayAdapter getAdapter(PayAdapterType type) {
switch (type) {
case WX_MP_PAY:
return new WxMpPayAdapter();
default:
throw new PayUnsupportedException("不支持的Adapter类型");
}
}
protected static Map<String, IPayAdapter> namedAdapter = new HashMap<>();
protected static IPayAdapter defaultAdapter = null;
public static void register(String name, IPayAdapter adapter) {
namedAdapter.put(name, adapter);
}
public static IPayAdapter use(String name) {
IPayAdapter adapter = namedAdapter.get(name);
if (adapter == null) {
throw new PayUndefinedException("未定义的支付方式:"+name);
}
return adapter;
}
public static IPayAdapter use() {
if (defaultAdapter == null) {
throw new PayUndefinedException("未定义默认的支付方式");
}
return defaultAdapter;
}
public static void setDefault(String defaultName) {
PayFactory.defaultAdapter = use(defaultName);
}
}

View File

@ -0,0 +1,42 @@
package com.ycwl.basic.pay.adapter;
// 假设request对象所在的包可根据实际情况修改
import com.ycwl.basic.pay.entity.CancelOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderResponse;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.pay.entity.RefundResponse;
import com.ycwl.basic.pay.entity.RefundOrderRequest;
import com.ycwl.basic.pay.entity.RefundOrderResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
// 将接口改为抽象类
public interface IPayAdapter {
// 下单方法
CreateOrderResponse createOrder(CreateOrderRequest request);
// 获取支付参数方法
Map<String, Object> getPaymentParams(CreateOrderResponse response);
// 处理回调信息方法
PayResponse handleCallback(HttpServletRequest request) throws IOException;
// 查询订单状态方法
PayResponse queryOrder(String orderNo);
// 退款方法
RefundOrderResponse refund(RefundOrderRequest request);
// 处理退款回调方法
RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException;
// 查询退款订单状态方法
RefundResponse checkRefundStatus(String refundNo);
void loadConfig(Map<String, String> config);
void cancelOrder(CancelOrderRequest request);
}

View File

@ -0,0 +1,353 @@
package com.ycwl.basic.pay.adapter;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.pay.entity.CancelOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderResponse;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.pay.entity.RefundResponse;
import com.ycwl.basic.pay.entity.RefundOrderRequest;
import com.ycwl.basic.pay.entity.RefundOrderResponse;
import com.ycwl.basic.pay.entity.WxMpPayConfig;
import com.ycwl.basic.pay.exceptions.PayWrongConfigException;
import org.springframework.util.Base64Utils;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_NONCE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SERIAL;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SIGNATURE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_TIMESTAMP;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
public class WxMpPayAdapter implements IPayAdapter {
private WxMpPayConfig config;
public static final String WECHAT_PAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";
public WxMpPayAdapter() {
}
public WxMpPayAdapter(WxMpPayConfig config) {
this.config = config;
}
@Override
public void loadConfig(Map<String, String> _config) {
this.config = new WxMpPayConfig();
if (_config != null) {
this.config.setMerchantId(_config.get("merchantId"));
this.config.setAppId(_config.get("appId"));
this.config.setPrivateKey(_config.get("privateKey"));
this.config.setSerialNumber(_config.get("serialNumber"));
this.config.setApiV3Key(_config.get("apiV3Key"));
}
}
private Config clientConfig;
private Config getConfig() {
if (clientConfig == null) {
clientConfig = new RSAAutoCertificateConfig.Builder()
.merchantId(config.getMerchantId())
.privateKey(config.getPrivateKey())
.merchantSerialNumber(config.getSerialNumber())
.apiV3Key(config.getApiV3Key())
.build();
}
return clientConfig;
}
@Override
public CreateOrderResponse createOrder(CreateOrderRequest request) {
CreateOrderResponse resp = new CreateOrderResponse();
if (request.getPrice() <= 0) {
resp.setSkipPay(true);
return resp;
}
Config wxConfig = getConfig();
JsapiService service = new JsapiService.Builder().config(wxConfig).build();
PrepayRequest prepayRequest = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(request.getPrice());
prepayRequest.setAmount(amount);
prepayRequest.setAppid(config.getAppId());
prepayRequest.setMchid(config.getMerchantId());
prepayRequest.setDescription(request.getDescription());
prepayRequest.setNotifyUrl(request.getNotifyUrl());
prepayRequest.setOutTradeNo(request.getOrderNo());
Payer payer = new Payer();
payer.setOpenid(request.getUserIdentify());
prepayRequest.setPayer(payer);
PrepayResponse response = service.prepay(prepayRequest);
resp.setSuccess(true);
resp.setSkipPay(false);
resp.setOrderNo(response.getPrepayId());
return resp;
}
@Override
public Map<String, Object> getPaymentParams(CreateOrderResponse response) {
Map<String, Object> params = new HashMap<>();
if (response.isSkipPay()) {
return params;
}
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
params.put("appId", config.getAppId());
params.put("timeStamp", timeStamp);
String nonce = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
params.put("nonceStr", nonce);
String signStr = Stream.of(config.getAppId(), String.valueOf(timeStamp), nonce, "prepay_id=" + response.getOrderNo())
.collect(Collectors.joining("\n", "", "\n"));
String sign;
try {
sign = getSign(signStr, config.getPrivateKey());
} catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
throw new PayWrongConfigException("配置错误");
}
params.put("paySign", sign);
params.put("signType", "RSA-SHA256");
params.put("prepayId", "prepay_id=" + response.getOrderNo());
return params;
}
@Override
public PayResponse handleCallback(HttpServletRequest request) throws IOException {
ServletInputStream inputStream = request.getInputStream();
StringBuffer body = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
body.append(s);
}
PayResponse resp = new PayResponse();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = (NotificationConfig) getConfig();
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(body.toString())
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
resp.setValid(true);
resp.setOrderNo(parse.getOutTradeNo());
if (parse.getAmount() != null) {
resp.setOrderPrice(parse.getAmount().getTotal());
resp.setPayPrice(parse.getAmount().getPayerTotal());
}
switch (parse.getTradeState()) {
case SUCCESS:
resp.setState(PayResponse.PAY_STATE.SUCCESS);
break;
case NOTPAY:
case CLOSED:
case REVOKED:
resp.setState(PayResponse.PAY_STATE.FAIL);
break;
case REFUND:
resp.setState(PayResponse.PAY_STATE.REFUND);
break;
default:
resp.setState(PayResponse.PAY_STATE.CANCEL);
break;
}
resp.setPayTime(parse.getSuccessTime());
resp.setOriginalResponse(parse);
return resp;
}
@Override
public PayResponse queryOrder(String orderNo) {
Config wxConfig = getConfig();
JsapiService service = new JsapiService.Builder().config(wxConfig).build();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(config.getMerchantId());
queryRequest.setOutTradeNo(orderNo);
PayResponse resp = new PayResponse();
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
resp.setValid(true);
resp.setOrderNo(result.getOutTradeNo());
if (result.getAmount() != null) {
resp.setOrderPrice(result.getAmount().getTotal());
resp.setPayPrice(result.getAmount().getPayerTotal());
}
switch (result.getTradeState()) {
case SUCCESS:
resp.setState(PayResponse.PAY_STATE.SUCCESS);
break;
case NOTPAY:
case CLOSED:
case REVOKED:
resp.setState(PayResponse.PAY_STATE.FAIL);
break;
case REFUND:
resp.setState(PayResponse.PAY_STATE.REFUND);
break;
default:
resp.setState(PayResponse.PAY_STATE.CANCEL);
break;
}
resp.setPayTime(result.getSuccessTime());
resp.setOriginalResponse(result);
return resp;
}
@Override
public RefundOrderResponse refund(RefundOrderRequest request) {
RefundOrderResponse resp = new RefundOrderResponse();
Config wxConfig = getConfig();
RefundService service = new RefundService.Builder().config(wxConfig).build();
CreateRequest createRequest = new CreateRequest();
createRequest.setOutTradeNo(request.getOrderNo());
createRequest.setOutRefundNo(request.getRefundNo());
AmountReq amountReq = new AmountReq();
amountReq.setTotal(Long.valueOf(request.getPrice()));
amountReq.setRefund(Long.valueOf(request.getRefundPrice()));
amountReq.setCurrency("CNY");
createRequest.setAmount(amountReq);
createRequest.setNotifyUrl(request.getNotifyUrl());
Refund refund = service.create(createRequest);
if (refund.getStatus() == SUCCESS) {
resp.setSuccess(true);
resp.setRefundNo(refund.getOutRefundNo());
} else {
resp.setSuccess(false);
}
return resp;
}
@Override
public RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException {
ServletInputStream inputStream = request.getInputStream();
StringBuffer body = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
body.append(s);
}
RefundResponse resp = new RefundResponse();
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = (NotificationConfig) getConfig();
NotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(body.toString())
.build();
RefundNotification parse = parser.parse(requestParam, RefundNotification.class);
resp.setValid(true);
resp.setOriginalResponse(parse);
if (parse.getRefundStatus() == SUCCESS) {
//退款成功
resp.setSuccess();
resp.setRefundTime(parse.getSuccessTime());
resp.setOrderNo(parse.getOutTradeNo());
resp.setRefundNo(parse.getRefundId());
if (parse.getAmount() != null) {
resp.setRefundPrice(Math.toIntExact(parse.getAmount().getPayerRefund()));
resp.setOrderPrice(Math.toIntExact(parse.getAmount().getTotal()));
}
} else {
//退款失败
resp.setFail();
}
return resp;
}
@Override
public RefundResponse checkRefundStatus(String refundNo) {
Config wxConfig = getConfig();
RefundService service = new RefundService.Builder().config(wxConfig).build();
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(refundNo);
RefundResponse resp = new RefundResponse();
Refund result = service.queryByOutRefundNo(request);
resp.setValid(true);
resp.setOriginalResponse(result);
if (result.getStatus() == SUCCESS) {
//退款成功
resp.setSuccess();
resp.setRefundTime(result.getSuccessTime());
resp.setOrderNo(result.getOutTradeNo());
resp.setRefundNo(result.getRefundId());
if (result.getAmount() != null) {
resp.setRefundPrice(Math.toIntExact(result.getAmount().getPayerRefund()));
resp.setOrderPrice(Math.toIntExact(result.getAmount().getTotal()));
}
} else {
//退款失败
resp.setFail();
}
return resp;
}
@Override
public void cancelOrder(CancelOrderRequest request) {
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setOutTradeNo(request.getOrderNo());
closeOrderRequest.setMchid(config.getMerchantId());
Config config = getConfig();
JsapiService service = new JsapiService.Builder().config(config).build();
service.closeOrder(closeOrderRequest);
}
public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
String replace = privateKey.replace("\\n", "\n");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
}

View File

@ -0,0 +1,10 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class CancelOrderRequest {
private String orderNo;
}

View File

@ -0,0 +1,35 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.math.BigInteger;
@Data
@Accessors(chain = true)
public class CreateOrderRequest {
/**
* 价格单位为分
*/
private Integer price;
private String goodsName;
private String orderNo;
private String description;
private String userIdentify;
private String notifyUrl;
public BigDecimal getPriceInYuan() {
return new BigDecimal(BigInteger.valueOf(price), 2);
}
public CreateOrderRequest setPriceInCents(Integer priceInCents) {
this.price = priceInCents;
return this;
}
public CreateOrderRequest setPriceInYuan(BigDecimal priceInYuan) {
this.price = priceInYuan.multiply(new BigDecimal(100)).intValue();
return this;
}
}

View File

@ -0,0 +1,10 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
@Data
public class CreateOrderResponse {
private boolean success;
private boolean skipPay;
private String orderNo;
}

View File

@ -0,0 +1,34 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
@Data
public class PayResponse {
private boolean valid;
private String orderNo;
private Object originalResponse;
private Integer orderPrice;
private Integer payPrice;
private PAY_STATE state;
private String payTime;
public boolean isPay() {
return state == PAY_STATE.SUCCESS;
}
public boolean isCancel() {
return state == PAY_STATE.CANCEL;
}
public boolean isRefund() {
return state == PAY_STATE.REFUND;
}
public enum PAY_STATE {
SUCCESS,
CANCEL,
REFUND,
FAIL,
UNKNOWN;
}
}

View File

@ -0,0 +1,14 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class RefundOrderRequest {
private Integer price;
private Integer refundPrice;
private String orderNo;
private String refundNo;
private String notifyUrl;
}

View File

@ -0,0 +1,9 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
@Data
public class RefundOrderResponse {
private boolean success;
private String refundNo;
}

View File

@ -0,0 +1,28 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
@Data
public class RefundResponse {
private boolean valid;
private String orderNo;
private String refundNo;
private Object originalResponse;
private Integer orderPrice;
private Integer refundPrice;
private PAY_STATE state;
private String refundTime;
public void setSuccess() {
state = PAY_STATE.SUCCESS;
}
public void setFail() {
state = PAY_STATE.FAIL;
}
public enum PAY_STATE {
SUCCESS,
FAIL
}
}

View File

@ -0,0 +1,12 @@
package com.ycwl.basic.pay.entity;
import lombok.Data;
@Data
public class WxMpPayConfig {
private String merchantId;
private String appId;
private String privateKey;
private String serialNumber;
private String apiV3Key;
}

View File

@ -0,0 +1,20 @@
package com.ycwl.basic.pay.enums;
public enum PayAdapterType {
WX_MP_PAY("WX_MP_PAY"),
;
private String type;
PayAdapterType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,7 @@
package com.ycwl.basic.pay.exceptions;
public class PayException extends RuntimeException {
public PayException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package com.ycwl.basic.pay.exceptions;
public class PayUndefinedException extends RuntimeException {
public PayUndefinedException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package com.ycwl.basic.pay.exceptions;
public class PayUnsupportedException extends PayException {
public PayUnsupportedException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package com.ycwl.basic.pay.exceptions;
public class PayWrongConfigException extends PayException {
public PayWrongConfigException(String message) {
super(message);
}
}

View File

@ -0,0 +1,32 @@
package com.ycwl.basic.pay.starter;
import com.ycwl.basic.pay.PayFactory;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.pay.starter.config.OverallPayConfig;
import com.ycwl.basic.pay.starter.config.PayConfigItem;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PayAutoConfiguration {
private final OverallPayConfig config;
public PayAutoConfiguration(OverallPayConfig config) {
this.config = config;
if (config != null) {
if (config.getConfigs() != null) {
loadConfig();
}
if (StringUtils.isNotBlank(config.getDefaultUse())) {
PayFactory.setDefault(config.getDefaultUse());
}
}
}
private void loadConfig() {
for (PayConfigItem item : config.getConfigs()) {
IPayAdapter adapter = PayFactory.getAdapter(item.getType());
adapter.loadConfig(item.getConfig());
PayFactory.register(item.getName(), adapter);
}
}
}

View File

@ -0,0 +1,15 @@
package com.ycwl.basic.pay.starter.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "pay")
@Data
public class OverallPayConfig {
private String defaultUse;
private List<PayConfigItem> configs;
}

View File

@ -0,0 +1,13 @@
package com.ycwl.basic.pay.starter.config;
import com.ycwl.basic.pay.enums.PayAdapterType;
import lombok.Data;
import java.util.Map;
@Data
public class PayConfigItem {
private String name;
private PayAdapterType type;
private Map<String, String> config;
}

View File

@ -47,12 +47,37 @@ public class FeiETicketPrinter {
//根据打印纸张的宽度自行调整内容的格式可参考下面的样例格式
String content;
content = "<CB>帧途AI旅拍</CB><BR>";
content += "┏━━━━━━━━━━━━━━┓<BR>";
content += "┃┉3制67表01符45制89表23符6┉┃<BR>";
content += "┣━━★━━♢━━◈━━◉━━┫<BR>";
content += "┃123制67表01符45制89表23符678┃<BR>";
content += "┗━━━━━━━━━━━━━━┛<BR>";
content = "<BR><B>世界再大</B><BR>";
content += "<B>你永远是这段旅途</B><BR>";
content += "<B>的焦点</B><BR>";
content += "━━━━━━━━━━━━━━━━<BR>";
content += "<B>正片主演:</B><BR>";
content += "旅途中最靓的你(测试名字很长很长故意不换行)<BR><BR>";
content += "<B>拍摄地点:</B><BR>";
content += "泸定桥<BR><BR>";
content += "<B>拍摄日期:</B><BR>";
content += "2025年4月4日<BR><BR>";
content += "<B>大片内容:</B><BR>";
content += "1.打卡泸定桥专属微电影(2部+)<BR>";
content += "2.打卡录像集(5条)<BR>";
content += "3.打卡照片集(5-10张)<BR>";
content += "━━━━━━━━━━━━━━━━<BR>";
content += "<C>帧帧皆故事 途途有回忆</C>";
content += "<C>扫码即可观赏您的照片及视频</C>";
content += "<BR><QR>https://zhentuai.com</QR>";
content += "<CB>游后微信扫码查看</CB>";
content += "<C>精彩指数:★★★★★</C><BR>";
// content += "┏━━━━━━━━━━━━━━┓<BR>";
// content += "┏━━━━━━━━━━━━━━┓<BR>";
// content += "┏━━━━━━━━━━━━━━┓<BR>";
// content += "┃┉3制67表01符45制89表23符6┉┃<BR>";
// content += "┣━━★━━♢━━◈━━◉━━┫<BR>";
// content += "┣━━★━━♢━━◈━━◉━━┫<BR>";
// content += "┣━━★━━♢━━◈━━◉━━┫<BR>";
// content += "┣━━★━━♢━━◈━━◉━━┫<BR>";
// content += "┃123制67表01符45制89表23符678┃<BR>";
// content += "┗━━━━━━━━━━━━━━┛<BR>";
return doPrint(sn,content,1);
}

View File

@ -1,24 +1,26 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;
public interface WxPayService {
/**
* 微信预支付
*/
WxPayRespVO createOrder(WXPayOrderReqVO req) throws Exception;
Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) throws Exception;
/**
* 微信支付回调
*/
void payNotify(HttpServletRequest xml);
void payNotify(HttpServletRequest request);
void payNotify(Long scenicId, HttpServletRequest request);
/**
@ -38,7 +40,7 @@ public interface WxPayService {
/**
* 微信退款回调
*/
boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException;
boolean refundNotify(Long scenicId, HttpServletRequest request) throws IOException;
/**
* 关闭订单
@ -46,4 +48,5 @@ public interface WxPayService {
* @param orderId 订单id订单编号
*/
void closeOrder(String orderId) ;
}

View File

@ -3,11 +3,9 @@ package com.ycwl.basic.service.mobile.impl;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.AgreementEnum;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.WechatErrorCodeEnum;
import com.ycwl.basic.exception.AppException;
@ -137,7 +135,7 @@ public class AppMemberServiceImpl implements AppMemberService {
public ApiResponse<?> agreement() {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
memberEntity.setAgreement(AgreementEnum.AGREE.getType());
memberEntity.setAgreement(1);
return ApiResponse.success(memberMapper.update(memberEntity));
}

View File

@ -46,8 +46,12 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -199,7 +203,20 @@ public class GoodsServiceImpl implements GoodsService {
goodsDetailVO.setGoodsType(sourceType);
goodsDetailVO.setSourceType(sourceType);
goodsDetailVO.setGoodsId(sourceRespVO.getId());
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
if (sourceRespVO.getVideoUrl() != null) {
try {
URL url = new URL(sourceRespVO.getVideoUrl());
if (StringUtils.startsWith(url.getHost(), "100.64.")) {
// 内网地址需要代理
goodsDetailVO.setVideoUrl("https://zhentuai.com/proxy?url=" + sourceRespVO.getVideoUrl());
} else {
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
} catch (MalformedURLException e) {
log.warn("url地址解析异常{}", sourceRespVO.getVideoUrl(), e);
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
}
goodsDetailVO.setUrl(sourceRespVO.getUrl());
goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
goodsDetailVO.setIsFree(sourceRespVO.getIsFree());
@ -378,7 +395,9 @@ public class GoodsServiceImpl implements GoodsService {
response.setCount(finishedTask);
int faceCutStatus = taskStatusBiz.getFaceCutStatus(faceId);
if (Integer.valueOf(0).equals(faceCutStatus)) {
response.setTemplateId(notFinishedTasks.get(0).getTemplateId());
if (!notFinishedTasks.isEmpty()) {
response.setTemplateId(notFinishedTasks.get(0).getTemplateId());
}
response.setStatus(2);
return response;
}
@ -430,10 +449,7 @@ public class GoodsServiceImpl implements GoodsService {
public ApiResponse<GoodsDetailVO> sourceGoodsInfo(Long sourceId) {
SourceRespVO sourceRespVO = sourceMapper.getById(sourceId);
if (sourceRespVO == null) {
sourceRespVO = sourceMapper.getById(sourceId);
if (sourceRespVO == null) {
return ApiResponse.fail("该视频不存在");
}
return ApiResponse.fail("该视频不存在");
}
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
goodsDetailVO.setGoodsName("原片");
@ -441,7 +457,20 @@ public class GoodsServiceImpl implements GoodsService {
goodsDetailVO.setScenicName(sourceRespVO.getScenicName());
goodsDetailVO.setGoodsType(sourceRespVO.getType());
goodsDetailVO.setGoodsId(sourceRespVO.getId());
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
if (sourceRespVO.getVideoUrl() != null) {
try {
URL url = new URL(sourceRespVO.getVideoUrl());
if (StringUtils.startsWith(url.getHost(), "100.64.")) {
// 内网地址需要代理
goodsDetailVO.setVideoUrl("https://zhentuai.com/proxy?url=" + sourceRespVO.getVideoUrl());
} else {
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
} catch (MalformedURLException e) {
log.warn("url地址解析异常{}", sourceRespVO.getVideoUrl(), e);
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
}
goodsDetailVO.setTemplateCoverUrl(sourceRespVO.getUrl());
goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
return ApiResponse.success(goodsDetailVO);
@ -522,7 +551,7 @@ public class GoodsServiceImpl implements GoodsService {
log.error("process error", e);
return;
}
String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
String url = adapter.uploadFile(null, watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
sourceMapper.addSourceWatermark(item.getGoodsId(), null, ImageWatermarkOperatorEnum.WATERMARK.getType(), url);
tmpFile.add(watermarkedFile);
@ -634,7 +663,7 @@ public class GoodsServiceImpl implements GoodsService {
log.error("process error", e);
return;
}
String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
String url = adapter.uploadFile(null, watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
sourceMapper.addSourceWatermark(item.getGoodsId(), face.getId(), type.getType(), url);
tmpFile.add(watermarkedFile);

View File

@ -1,28 +1,8 @@
package com.ycwl.basic.service.mobile.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.utils.StringUtils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.HttpConstant;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.enums.StatisticEnum;
@ -35,37 +15,28 @@ import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.payment.entity.PaymentEntity;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.pay.entity.CancelOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderResponse;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.pay.entity.RefundResponse;
import com.ycwl.basic.pay.entity.RefundOrderRequest;
import com.ycwl.basic.pay.entity.RefundOrderResponse;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
import static com.ycwl.basic.constant.WeiXinConstant.*;
/**
* @Author: songmingsong
@ -77,8 +48,6 @@ import static com.ycwl.basic.constant.WeiXinConstant.*;
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private PaymentMapper paymentMapper;
@Autowired
@ -89,116 +58,50 @@ public class WxPayServiceImpl implements WxPayService {
private OrderBiz orderBiz;
@Autowired
private OrderMapper orderMapper;
@Autowired
private ScenicService scenicService;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) {
public Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) {
PaymentEntity entity = new PaymentEntity();
entity.setOrderId(req.getOrderSn());
entity.setMemberId(req.getMemberId());
entity.setPayPrice(new BigDecimal(BigInteger.valueOf(req.getTotalPrice()), 2));
Long entityId = paymentMapper.addGetId(entity);
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
// 使用自动更新平台证书的RSA配置
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getTotalPrice());
request.setAmount(amount);
request.setAppid(wechatConfig.getMiniProgramAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getPayNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
request.setDescription(req.getDescription());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// SettleInfo settleInfo = new SettleInfo();
// settleInfo.setProfitSharing(true);
// request.setSettleInfo(settleInfo);
// 调用下单方法得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wechatConfig.getMiniProgramAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
vo.setPaySign(sign);
vo.setSignType("RSA-SHA256");
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString(HttpConstant.Message));
CreateOrderRequest request = new CreateOrderRequest()
.setOrderNo(String.valueOf(req.getOrderSn()))
.setPriceInCents(req.getTotalPrice())
.setDescription(req.getDescription())
.setGoodsName(req.getGoodsName())
.setUserIdentify(req.getOpenId())
.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/"+scenicId+"/payNotify");
CreateOrderResponse order = scenicPayAdapter.createOrder(request);
Map<String, Object> paymentParams = scenicPayAdapter.getPaymentParams(order);
paymentParams.put("needPay", !order.isSkipPay());
return paymentParams;
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
}
@Override
public void payNotify(HttpServletRequest request) {
public void payNotify(Long scenicId, HttpServletRequest request) {
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
// 读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
log.warn("微信支付回调:{}", s1);
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(s1)
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
log.info("[微信支付]parse = {}", parse);
PayResponse callbackResponse = scenicPayAdapter.handleCallback(request);
log.info("[微信支付]parse = {}", callbackResponse);
// 更新订单信息
new Thread(() -> {
long orderId = Long.parseLong(parse.getOutTradeNo());
switch (parse.getTradeState()) {
case SUCCESS:
orderBiz.paidOrder(orderId);
break;
case NOTPAY:
case CLOSED:
case REVOKED:
orderBiz.cancelOrder(orderId);
break;
case REFUND:
orderBiz.refundOrder(orderId);
break;
long orderId = Long.parseLong(callbackResponse.getOrderNo());
if (callbackResponse.isPay()) {
orderBiz.paidOrder(orderId);
} else if (callbackResponse.isCancel()) {
orderBiz.cancelOrder(orderId);
} else if (callbackResponse.isRefund()) {
orderBiz.refundOrder(orderId);
}
}).start();
} catch (Exception e) {
@ -207,59 +110,29 @@ public class WxPayServiceImpl implements WxPayService {
}
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wechatConfig.getMchId());
queryRequest.setOutTradeNo(orderId.toString());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
try {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
public void payNotify(HttpServletRequest request) {
payNotify(0L, request);
}
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
TransactionAmount amount = transaction.getAmount();
Integer total = amount.getTotal();
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("[微信退款] code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("[微信退款] Response_body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
return null;
}
@Override
public Boolean refundOrder(String orderId) {
OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(order.getScenicId());
BigDecimal payPrice = order.getPayPrice();
long priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).longValue(); // 转换为分int
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
// 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥商户私钥会用来生成请求的签名
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
RefundService service = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
request.setNotifyUrl(wechatConfig.getRefundNotifyUrl());
AmountReq amountReq = new AmountReq();
amountReq.setTotal(priceInCents);
amountReq.setRefund(priceInCents);
amountReq.setCurrency(WECHATPAY_CURRENCY_CNY);
request.setAmount(amountReq);
request.setOutTradeNo(orderId);
request.setOutRefundNo(SnowFlakeUtil.getId());
Refund refundResult = service.create(request);
if (refundResult.getStatus() == SUCCESS) {
int priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).intValue(); // 转换为分int
RefundOrderRequest request = new RefundOrderRequest()
.setOrderNo(orderId)
.setPrice(priceInCents)
.setRefundNo(SnowFlakeUtil.getId())
.setRefundPrice(priceInCents)
.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/"+order.getScenicId()+"/refundNotify");
RefundOrderResponse response = scenicPayAdapter.refund(request);
if (response.isSuccess()) {
OrderUpdateReq orderUpdateReq = new OrderUpdateReq();
orderUpdateReq.setId(Long.parseLong(orderId));
orderUpdateReq.setRefundStatus(OrderStateEnum.REFUNDED.getType());
@ -272,31 +145,14 @@ public class WxPayServiceImpl implements WxPayService {
}
@Override
public boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException {
// 转为map格式
Map<String, String> jsonMap = JSONObject.parseObject(refundResult, Map.class);
public boolean refundNotify(Long scenicId, HttpServletRequest request) throws IOException {
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(scenicId);
try {
RefundResponse callbackResponse = scenicPayAdapter.handleRefundCallback(request);
log.info("[微信支付]parse = {}", callbackResponse);
/*
* 退款成功后返回一个加密字段resource,以下为解密
* 解密需要从resource参数中获取到ciphertextnonceassociated_data这三个参数进行解密
*/
String resource = JSONObject.toJSONString(jsonMap.get(REFUNDS_RESOURCE));
JSONObject object = JSONObject.parseObject(resource);
String ciphertext = String.valueOf(object.get(REFUNDS_CIPHERTEXT));
String nonce = String.valueOf(object.get(REFUNDS_NONCE));
String associated_data = String.valueOf(object.get(REFUNDS_ASSOCIATED_DATA));
String resultStr = decryptToString(associated_data.getBytes(String.valueOf(StandardCharsets.UTF_8)),
nonce.getBytes(String.valueOf(StandardCharsets.UTF_8)),
ciphertext);
Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);
String refund_status = reqInfo.get(REFUNDS_REFUND_STATUS);// 退款状态
String out_trade_no = reqInfo.get(WECHATPAY_OUT_TRADE_NO); // 订单号
if (!StringUtils.isEmpty(refund_status) && WECHATPAY_SUCCESS.equals(refund_status)) {
long orderId = Long.parseLong(out_trade_no);
// 更新订单信息
long orderId = Long.parseLong(callbackResponse.getOrderNo());
orderBiz.refundOrder(orderId);
OrderEntity order = orderRepository.getOrder(orderId);
@ -306,117 +162,19 @@ public class WxPayServiceImpl implements WxPayService {
statisticsRecordAddReq.setScenicId(order.getScenicId());
statisticsRecordAddReq.setMorphId(orderId);
statisticsMapper.addStatisticsRecord(statisticsRecordAddReq);
log.info("[微信退款回调]退款成功");
return true;
} else {
log.error("[微信退款回调]退款失败");
} catch (Exception e) {
log.error("微信退款回调失败", e);
return false;
}
}
@Override
public void closeOrder(String orderId) {
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setOutTradeNo(orderId);
closeOrderRequest.setMchid(wechatConfig.getMchId());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
service.closeOrder(closeOrderRequest);
}
/**
* 退款回调 解密数据
*
* @param associatedData
* @param nonce
* @param ciphertext
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wechatConfig.getApiV3().getBytes(), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 生成退款请求token
*
* @param method
* @param orderId
* @param body
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
* @throws SignatureException
*/
public String getToken(String method, String orderId, String body) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, SignatureException {
String nonceStr = WXPayUtil.generateNonceStr();
long timestamp = System.currentTimeMillis() / NumberConstant.THOUSAND; // 生成时间戳
String parameter = method + "\n"
+ REFUNDS_URi + orderId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
// 对参数进行加密
byte[] bytes = parameter.getBytes(String.valueOf(StandardCharsets.UTF_8));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(wechatConfig.getKeyPath()); // privateKeyPath是商户证书密钥的位置apiclient_key.pem
sign.initSign(privateKey); // 商户密钥文件路径
sign.update(bytes);
String signature = Base64.getEncoder().encodeToString(sign.sign());
// 获取token
String token = "mchid=\"" + wechatConfig.getMchId() + "\"," // 商户号
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + wechatConfig.getMchSerialNo() + "\"," // merchantSerialNumber是微信支付中申请的证书序列号
+ "signature=\"" + signature + "\"";
return REFUNDS_SCHEMA + token;
}
/**
* 一个商户号只能初始化一个配置否则会因为重复的下载任务报错
*/
private static class Holder {
private static volatile Config instance;
static void init(WechatConfig wechatConfig) {
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
}
}
public static Config getInstance(WechatConfig wechatConfig) {
if (Holder.instance == null) {
synchronized (Holder.class) {
if (Holder.instance == null) {
Holder.init(wechatConfig);
}
}
}
return Holder.instance;
OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
IPayAdapter scenicPayAdapter = scenicService.getScenicPayAdapter(order.getScenicId());
CancelOrderRequest request = new CancelOrderRequest()
.setOrderNo(orderId);
scenicPayAdapter.cancelOrder(request);
}
}

View File

@ -8,14 +8,13 @@ import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.req.CreateBatchOrderReqVO;
import com.ycwl.basic.model.pc.order.req.CreateOrderReqVO;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.order.req.OrderAddReq;
import com.ycwl.basic.model.pc.order.req.OrderReqQuery;
import com.ycwl.basic.model.pc.order.resp.OrderAppRespVO;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.utils.ApiResponse;
import java.util.List;
import java.util.Map;
/**
* @Authorlongbinbin
@ -63,7 +62,7 @@ public interface OrderService {
ApiResponse<PageInfo<OrderRespVO>> refundPageQuery(OrderReqQuery query);
ApiResponse<WxPayRespVO> createOrder(Long userId, CreateOrderReqVO orderAddReq) throws Exception;
ApiResponse<Map<String, Object>> createOrder(Long userId, CreateOrderReqVO orderAddReq) throws Exception;
ApiResponse<WxPayRespVO> createBatchOrder(Long userId, CreateBatchOrderReqVO batchOrderReqVO) throws Exception;
ApiResponse<Map<String, Object>> createBatchOrder(Long userId, CreateBatchOrderReqVO batchOrderReqVO) throws Exception;
}

View File

@ -19,4 +19,6 @@ public interface PriceConfigService extends IService<PriceConfigEntity> {
void fillGoodsName(List<PriceConfigRespVO> result);
void fillGoodsName(PriceConfigRespVO config);
void updateStatus(Integer id);
}

View File

@ -6,6 +6,7 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.utils.ApiResponse;
@ -36,5 +37,11 @@ public interface ScenicService {
IStorageAdapter getScenicStorageAdapter(Long scenicId);
IStorageAdapter getScenicTmpStorageAdapter(Long scenicId);
IStorageAdapter getScenicLocalStorageAdapter(Long scenicId);
IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId);
IPayAdapter getScenicPayAdapter(Long scenicId);
}

View File

@ -136,14 +136,7 @@ public class FaceServiceImpl implements FaceService {
String faceUrl = adapter.uploadFile(file, filePath, fileName);
Long newFaceId = SnowFlakeUtil.getLongId();
Long oldFaceId = null;
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
IFaceBodyAdapter faceBodyAdapter;
if (scenicConfig != null && scenicConfig.getFaceType() != null) {
faceBodyAdapter = FaceBodyFactory.getAdapter(scenicConfig.getFaceType());
faceBodyAdapter.loadConfig(JSONObject.parseObject(scenicConfig.getFaceConfigJson(), Map.class));
} else {
faceBodyAdapter = FaceBodyFactory.use();
}
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId);
faceService.assureFaceDb(faceBodyAdapter, USER_FACE_DB_NAME+scenicId);
SearchFaceRespVo userDbSearchResult = faceService.searchFace(faceBodyAdapter, USER_FACE_DB_NAME+scenicId, faceUrl, "判断是否为用户上传过的人脸");
float strictScore = 0.6F;
@ -225,13 +218,7 @@ public class FaceServiceImpl implements FaceService {
return null;
}
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
IFaceBodyAdapter faceBodyAdapter;
if (scenicConfig != null && scenicConfig.getFaceType() != null) {
faceBodyAdapter = FaceBodyFactory.getAdapter(scenicConfig.getFaceType());
faceBodyAdapter.loadConfig(JSONObject.parseObject(scenicConfig.getFaceConfigJson(), Map.class));
} else {
faceBodyAdapter = FaceBodyFactory.use();
}
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
SearchFaceRespVo scenicDbSearchResult = faceService.searchFace(faceBodyAdapter, String.valueOf(face.getScenicId()), face.getFaceUrl(), "人脸识别");
if (scenicDbSearchResult == null) {
throw new BaseException("人脸识别失败,请换一张试试把~");
@ -244,7 +231,10 @@ public class FaceServiceImpl implements FaceService {
FaceSampleEntity faceSample = faceRepository.getFaceSample(resultItem);
if (faceSample != null) {
// 以这个结果为人脸库的匹配结果
scenicDbSearchResult = faceService.searchFace(faceBodyAdapter, String.valueOf(face.getScenicId()), faceSample.getFaceUrl(), "人脸补救措施1");
SearchFaceRespVo tmpResult = faceService.searchFace(faceBodyAdapter, String.valueOf(face.getScenicId()), faceSample.getFaceUrl(), "人脸补救措施1");
if (tmpResult != null && tmpResult.getSampleListIds() != null && !tmpResult.getSampleListIds().isEmpty()) {
scenicDbSearchResult = tmpResult;
}
}
}
}

View File

@ -33,7 +33,6 @@ import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.PriceRepository;
import com.ycwl.basic.repository.VideoRepository;
@ -52,7 +51,9 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@ -167,11 +168,12 @@ public class OrderServiceImpl implements OrderService {
/**
* 发起支付
* @param order 订单
*
* @param order 订单
* @param orderItems 商品详情
* @return 支付请求结果
*/
private WxPayRespVO initiatePayment(OrderEntity order, List<OrderItemEntity> orderItems) throws Exception {
private Map<String, Object> initiatePayment(OrderEntity order, List<OrderItemEntity> orderItems) throws Exception {
WXPayOrderReqVO wxPayOrderReqVO = new WXPayOrderReqVO();
String goodsName = null;
if (orderItems.size() > 1) {
@ -186,12 +188,6 @@ public class OrderServiceImpl implements OrderService {
goodsName = "景区照片包";
}
}
if (order.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) {
// 0元支付
WxPayRespVO wxPayRespVO = new WxPayRespVO();
wxPayRespVO.setNeedPay(false);
return wxPayRespVO;
}
wxPayOrderReqVO.setOpenId(order.getOpenId())
.setMemberId(order.getMemberId())
.setOrderSn(order.getId())
@ -199,7 +195,7 @@ public class OrderServiceImpl implements OrderService {
.setGoodsName(goodsName)
.setDescription(goodsName);
return wxPayService.createOrder(wxPayOrderReqVO);
return wxPayService.createOrder(order.getScenicId(), wxPayOrderReqVO);
}
@Override
@ -360,7 +356,7 @@ public class OrderServiceImpl implements OrderService {
@Override
@Transactional(rollbackFor = Exception.class)
public ApiResponse<WxPayRespVO> createOrder(Long userId, CreateOrderReqVO createOrderReqVO) throws Exception {
public ApiResponse<Map<String, Object>> createOrder(Long userId, CreateOrderReqVO createOrderReqVO) throws Exception {
IsBuyRespVO isBuy = orderBiz.isBuy(userId, createOrderReqVO.getScenicId(), createOrderReqVO.getGoodsType(), createOrderReqVO.getGoodsId());
if (isBuy.isBuy()) {
return ApiResponse.fail("您已购买此内容,无需重复购买!");
@ -442,16 +438,18 @@ public class OrderServiceImpl implements OrderService {
}
if (order.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) {
orderBiz.paidOrder(order.getId());
return ApiResponse.success(new WxPayRespVO());
HashMap<String, Object> data = new HashMap<>();
data.put("needPay", false);
return ApiResponse.success(data);
} else {
WxPayRespVO wxPayRespVO = initiatePayment(order, orderItems);
Map<String, Object> wxPayRespVO = initiatePayment(order, orderItems);
return ApiResponse.success(wxPayRespVO);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ApiResponse<WxPayRespVO> createBatchOrder(Long userId, CreateBatchOrderReqVO batchOrderReqVO) throws Exception {
public ApiResponse<Map<String, Object>> createBatchOrder(Long userId, CreateBatchOrderReqVO batchOrderReqVO) throws Exception {
PriceConfigEntity priceConfig = priceRepository.getPriceConfigByScenicTypeGoods(batchOrderReqVO.getScenicId(), batchOrderReqVO.getType(), batchOrderReqVO.getGoodsId());
if (priceConfig == null) {
return ApiResponse.fail("该套餐暂未开放购买");
@ -504,9 +502,11 @@ public class OrderServiceImpl implements OrderService {
}
if (order.getPayPrice().equals(BigDecimal.ZERO)) {
orderBiz.paidOrder(order.getId());
return ApiResponse.success(new WxPayRespVO());
HashMap<String, Object> data = new HashMap<>();
data.put("needPay", false);
return ApiResponse.success(data);
} else {
WxPayRespVO wxPayRespVO = initiatePayment(order, orderItems);
Map<String, Object> wxPayRespVO = initiatePayment(order, orderItems);
return ApiResponse.success(wxPayRespVO);
}
}

View File

@ -74,4 +74,9 @@ public class PriceConfigServiceImpl extends ServiceImpl<PriceConfigMapper, Price
item.setGoodsNames(StringUtils.join(goodsNames, ","));
}
}
@Override
public void updateStatus(Integer id) {
baseMapper.updateStatus(id);
}
}

View File

@ -12,11 +12,14 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.pay.PayFactory;
import com.ycwl.basic.pay.adapter.IPayAdapter;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.exceptions.StorageUnsupportedException;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
@ -105,6 +108,7 @@ public class ScenicServiceImpl implements ScenicService {
scenicRepository.clearCache(id);
scenicFaceBodyAdapterMap.remove(id);
scenicStorageAdapterMap.remove(id);
scenicPayAdapterMap.remove(id);
return ApiResponse.success(true);
}else {
return ApiResponse.fail("景区删除失败");
@ -197,6 +201,9 @@ public class ScenicServiceImpl implements ScenicService {
scenicRepository.clearCache(config.getScenicId());
scenicFaceBodyAdapterMap.remove(config.getScenicId());
scenicStorageAdapterMap.remove(config.getScenicId());
scenicTmpStorageAdapterMap.remove(config.getScenicId());
scenicLocalStorageAdapterMap.remove(config.getScenicId());
scenicPayAdapterMap.remove(config.getScenicId());
}
@ -207,14 +214,57 @@ public class ScenicServiceImpl implements ScenicService {
IStorageAdapter adapter;
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null && scenicConfig.getStoreType() != null) {
adapter = StorageFactory.get(scenicConfig.getStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class));
try {
adapter = StorageFactory.get(scenicConfig.getStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class));
} catch (StorageUnsupportedException ignored) {
return StorageFactory.use("video");
}
} else {
adapter = StorageFactory.use("video");
}
return adapter;
});
}
private static final Map<Long, IStorageAdapter> scenicTmpStorageAdapterMap = new ConcurrentHashMap<>();
@Override
public IStorageAdapter getScenicTmpStorageAdapter(Long scenicId) {
return scenicTmpStorageAdapterMap.computeIfAbsent(scenicId, (key) -> {
IStorageAdapter adapter;
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null && scenicConfig.getTmpStoreType() != null) {
try {
adapter = StorageFactory.get(scenicConfig.getTmpStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getTmpStoreConfigJson(), Map.class));
} catch (StorageUnsupportedException ignored) {
return getScenicStorageAdapter(scenicId);
}
} else {
return getScenicStorageAdapter(scenicId);
}
return adapter;
});
}
private static final Map<Long, IStorageAdapter> scenicLocalStorageAdapterMap = new ConcurrentHashMap<>();
@Override
public IStorageAdapter getScenicLocalStorageAdapter(Long scenicId) {
return scenicLocalStorageAdapterMap.computeIfAbsent(scenicId, (key) -> {
IStorageAdapter adapter;
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null && scenicConfig.getLocalStoreType() != null) {
try {
adapter = StorageFactory.get(scenicConfig.getLocalStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getLocalStoreConfigJson(), Map.class));
} catch (StorageUnsupportedException ignored) {
return getScenicStorageAdapter(scenicId);
}
} else {
return getScenicStorageAdapter(scenicId);
}
return adapter;
});
}
private static final Map<Long, IFaceBodyAdapter> scenicFaceBodyAdapterMap = new ConcurrentHashMap<>();
@Override
public IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId) {
@ -230,4 +280,20 @@ public class ScenicServiceImpl implements ScenicService {
return adapter;
});
}
private static final Map<Long, IPayAdapter> scenicPayAdapterMap = new ConcurrentHashMap<>();
@Override
public IPayAdapter getScenicPayAdapter(Long scenicId) {
return scenicPayAdapterMap.computeIfAbsent(scenicId, (key) -> {
IPayAdapter adapter;
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null && scenicConfig.getPayType() != null) {
adapter = PayFactory.getAdapter(scenicConfig.getPayType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getPayConfigJson(), Map.class));
} else {
adapter = PayFactory.use();
}
return adapter;
});
}
}

View File

@ -11,9 +11,12 @@ import com.ycwl.basic.service.pc.SourceService;
import com.ycwl.basic.task.VideoPieceGetter;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.SnowFlakeUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
@ -32,18 +35,63 @@ public class SourceServiceImpl implements SourceService {
public ApiResponse<PageInfo<SourceRespVO>> pageQuery(SourceReqQuery sourceReqQuery) {
PageHelper.startPage(sourceReqQuery.getPageNum(), sourceReqQuery.getPageSize());
List<SourceRespVO> list = sourceMapper.list(sourceReqQuery);
list.forEach(sourceRespVO -> {
if (sourceRespVO.getVideoUrl() != null) {
try {
URL url = new URL(sourceRespVO.getVideoUrl());
if (StringUtils.startsWith(url.getHost(), "100.64.")) {
// 内网地址需要代理
sourceRespVO.setVideoUrl("https://zhentuai.com/proxy?url=" + sourceRespVO.getVideoUrl());
} else {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
} catch (MalformedURLException e) {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
}
});
PageInfo<SourceRespVO> pageInfo = new PageInfo<>(list);
return ApiResponse.success(pageInfo);
}
@Override
public ApiResponse<List<SourceRespVO>> list(SourceReqQuery sourceReqQuery) {
return ApiResponse.success(sourceMapper.list(sourceReqQuery));
List<SourceRespVO> list = sourceMapper.list(sourceReqQuery);
list.forEach(sourceRespVO -> {
if (sourceRespVO.getVideoUrl() != null) {
try {
URL url = new URL(sourceRespVO.getVideoUrl());
if (StringUtils.startsWith(url.getHost(), "100.64.")) {
// 内网地址需要代理
sourceRespVO.setVideoUrl("https://zhentuai.com/proxy?url=" + sourceRespVO.getVideoUrl());
} else {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
} catch (MalformedURLException e) {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
}
});
return ApiResponse.success(list);
}
@Override
public ApiResponse<SourceRespVO> getById(Long id, Long userId) {
return ApiResponse.success(sourceMapper.userGetById(id, userId));
SourceRespVO sourceRespVO = sourceMapper.userGetById(id, userId);
if (sourceRespVO.getVideoUrl() != null) {
try {
URL url = new URL(sourceRespVO.getVideoUrl());
if (StringUtils.startsWith(url.getHost(), "100.64.")) {
// 内网地址需要代理
sourceRespVO.setVideoUrl("https://zhentuai.com/proxy?url=" + sourceRespVO.getVideoUrl());
} else {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
} catch (MalformedURLException e) {
sourceRespVO.setVideoUrl(sourceRespVO.getVideoUrl());
}
}
return ApiResponse.success(sourceRespVO);
}
@Override

View File

@ -10,8 +10,6 @@ public interface TaskFaceService {
SearchFaceRespVo searchFace(IFaceBodyAdapter adapter, String dbName, String faceUrl, String reason);
String uploadFile(MultipartFile file, Long userId);
boolean deleteFaceSample(Long scenicId, String dbName, String entityId);
boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName);

View File

@ -3,8 +3,6 @@ package com.ycwl.basic.service.task.impl;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesResponse;
import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.constant.FaceConstant;
@ -17,7 +15,6 @@ import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.mapper.FaceDetectLogMapper;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.ScenicMapper;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
@ -27,7 +24,6 @@ import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
import com.ycwl.basic.model.pc.faceDetectLog.resp.MatchLocalRecord;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
@ -42,8 +38,6 @@ import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.task.VideoPieceGetter;
import com.ycwl.basic.utils.DateUtils;
import com.ycwl.basic.utils.ratelimiter.FixedRateLimiter;
import com.ycwl.basic.utils.ratelimiter.IRateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -55,18 +49,13 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
@ -239,30 +228,27 @@ public class TaskFaceServiceImpl implements TaskFaceService {
List<FaceSampleEntity> allFaceSampleList = new ArrayList<>();
if (StringUtils.isNumeric(dbName)) { // 景区
allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds);
Long firstFaceSampleId = acceptFaceSampleIds.get(0);
Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream().filter(faceSample -> faceSample.getId().equals(firstFaceSampleId)).findAny();
if (firstFaceSample.isPresent()) {
if (tourMinutes > 0) {
List<FaceSampleEntity> acceptFaceSampleList = faceSampleMapper.listByIds(acceptFaceSampleIds);
Date startDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), -tourMinutes/2);
Date endDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), tourMinutes/2);
acceptFaceSampleIds = acceptFaceSampleList.stream()
.filter(faceSample -> faceSample.getCreateAt().after(startDate) && faceSample.getCreateAt().before(endDate))
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配:{},时间范围需要在:{}~{}间", firstFaceSample, startDate, endDate);
if (!acceptFaceSampleIds.isEmpty()) {
Long firstFaceSampleId = acceptFaceSampleIds.get(0);
Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream().filter(faceSample -> faceSample.getId().equals(firstFaceSampleId)).findAny();
if (firstFaceSample.isPresent()) {
if (tourMinutes > 0) {
List<FaceSampleEntity> acceptFaceSampleList = faceSampleMapper.listByIds(acceptFaceSampleIds);
Date startDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), -tourMinutes/2);
Date endDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), tourMinutes/2);
acceptFaceSampleIds = acceptFaceSampleList.stream()
.filter(faceSample -> faceSample.getCreateAt().after(startDate) && faceSample.getCreateAt().before(endDate))
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配:{},时间范围需要在:{}~{}间", firstFaceSample, startDate, endDate);
} else {
log.info("时间范围逻辑:景区未限制");
}
} else {
log.info("时间范围逻辑:景区未限制");
log.info("时间范围逻辑:最高匹配ID{},未找到", firstFaceSampleId);
}
} else {
log.info("时间范围逻辑最高匹配ID{},未找到", firstFaceSampleId);
}
}
if (acceptFaceSampleIds.isEmpty()) {
respVo.setFirstMatchRate(0f);
respVo.setSampleListIds(Collections.emptyList());
return respVo;
}
List<MatchLocalRecord> collect = new ArrayList<>();
for (SearchFaceResultItem item : records) {
MatchLocalRecord record = new MatchLocalRecord();
@ -286,6 +272,11 @@ public class TaskFaceServiceImpl implements TaskFaceService {
record.setMatched(item.getScore() > _threshold);
collect.add(record);
}
if (acceptFaceSampleIds.isEmpty()) {
respVo.setFirstMatchRate(0f);
respVo.setSampleListIds(Collections.emptyList());
return respVo;
}
logEntity.setMatchLocalRecord(JSONObject.toJSONString(collect));
respVo.setFirstMatchRate(response.getFirstMatchRate());
respVo.setSampleListIds(acceptFaceSampleIds);
@ -298,30 +289,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
}
@Override
public String uploadFile(MultipartFile file, Long userId) {
if (file.isEmpty()) {
throw new RuntimeException("文件不存在!");
}
String originalFilename = file.getOriginalFilename();
//获取文件名后缀
String suffix = originalFilename.split("\\.")[1];
if ("Jpeg".equals(suffix)) {
suffix = "jpg";
}
//文件储存路径
String filePath = StorageUtil.joinPath("user-faces", DateUtils.format(new Date(),"yyyy-MM-dd"));
// 生成文件名
String fileName= userId + "." + suffix;
IStorageAdapter adapter = StorageFactory.use("faces");
try {
return adapter.uploadFile(file.getInputStream(), filePath, fileName);
} catch (IOException e) {
log.error("文件上传失败!", e);
return null;
}
}
@Override
public boolean deleteFaceSample(Long scenicId, String dbName, String entityId) {
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
@ -368,22 +335,6 @@ public class TaskFaceServiceImpl implements TaskFaceService {
return redisTemplate.delete(FaceConstant.FACE_DB_NAME_PFX + "*");
}
private static final String DATE_FORMAT="yyyyMMddHHmmss";
public static String generateEntityId(FaceSampleEntity entity) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
Random random = new Random();
int randomNumber = random.nextInt(900) + 100;
return entity.getDeviceId().toString() + "_" + sdf.format(entity.getCreateAt()) + randomNumber;
}
public static String generateEntityId(FaceSampleRespVO entity) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
Random random = new Random();
int randomNumber = random.nextInt(900) + 100;
return entity.getDeviceId().toString() + "_" + sdf.format(entity.getCreateAt()) + randomNumber;
}
public String getFaceUrl(Long faceId) {
if (faceId == null) {
return null;

View File

@ -58,6 +58,7 @@ import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.task.VideoPieceGetter;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.VideoReUploader;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -117,8 +118,6 @@ public class TaskTaskServiceImpl implements TaskService {
@Autowired
private VideoRepository videoRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private ScenicService scenicService;
private final ReentrantLock lock = new ReentrantLock();
@ -126,7 +125,8 @@ public class TaskTaskServiceImpl implements TaskService {
private TaskStatusBiz taskStatusBiz;
@Autowired
private DeviceRepository deviceRepository;
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 1024, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1024));
@Autowired
private VideoReUploader videoReUploader;
private RenderWorkerEntity getWorker(@NonNull WorkerAuthReqVo req) {
@ -191,7 +191,7 @@ public class TaskTaskServiceImpl implements TaskService {
try {
List<TaskRespVO> taskList;
if (worker.getScenicOnly() != null) {
taskList = taskMapper.selectNotRunningByScenicId(worker.getScenicOnly());
taskList = taskMapper.selectNotRunningByScenicList(worker.getScenicOnly());
} else {
taskList = taskMapper.selectNotRunning();
}
@ -590,9 +590,10 @@ public class TaskTaskServiceImpl implements TaskService {
videoMapper.add(video);
}
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(task.getScenicId());
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(task.getScenicId());
IStorageAdapter adapter = scenicService.getScenicTmpStorageAdapter(task.getScenicId());
String filename = StorageUtil.joinPath(StorageConstant.VLOG_PATH, task.getId() + "_" + task.getScenicId() + ".mp4");
adapter.setAcl(StorageAcl.PUBLIC_READ, filename);
videoReUploader.addVideoTask(task.getVideoUrl(), video.getId());
int isBuy = 0;
FaceEntity face = faceRepository.getFace(task.getFaceId());
if (face != null) {
@ -619,9 +620,9 @@ public class TaskTaskServiceImpl implements TaskService {
}
}
videoMapper.updateRelationWhenTaskSuccess(taskId, video.getId(), isBuy);
executor.execute(() -> {
new Thread(() -> {
sendVideoGeneratedServiceNotification(taskId);
});
}).start();
}
@Override
@ -649,7 +650,7 @@ public class TaskTaskServiceImpl implements TaskService {
if (task == null) {
return null;
}
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(task.getScenicId());
IStorageAdapter adapter = scenicService.getScenicTmpStorageAdapter(task.getScenicId());
String filename = StorageUtil.joinPath(StorageConstant.VLOG_PATH, task.getId() + "_" + task.getScenicId() + ".mp4");
if (StringUtils.isBlank(task.getVideoUrl())) {
// 生成

View File

@ -0,0 +1,7 @@
package com.ycwl.basic.stats.biz;
import org.springframework.stereotype.Component;
@Component
public class StatsBiz {
}

View File

@ -0,0 +1,43 @@
package com.ycwl.basic.stats.controller;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.stats.dto.AddTraceReq;
import com.ycwl.basic.stats.service.StatsService;
import com.ycwl.basic.stats.util.StatsUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/trace/v1")
public class TraceController {
@Autowired
private StatsService statsService;
@IgnoreToken
@PostMapping("/start")
public void startTrace(HttpServletRequest request, HttpServletResponse response) {
String traceId = request.getHeader("traceId");
if (traceId == null || traceId.isEmpty()) {
traceId = StatsUtil.createUuid();
response.setHeader("Set-TraceId", traceId);
}
statsService.addStats(traceId, null);
}
@IgnoreToken
@PostMapping("/add")
public void addTrace(HttpServletRequest request, HttpServletResponse response, @RequestBody AddTraceReq req) {
String traceId = request.getHeader("traceId");
if (traceId == null || traceId.isEmpty()) {
traceId = StatsUtil.createUuid();
response.setHeader("Set-TraceId", traceId);
}
statsService.addRecord(traceId, req.getAction(), req.getIdentifier(), req.getParams());
}
}

View File

@ -0,0 +1,10 @@
package com.ycwl.basic.stats.dto;
import lombok.Data;
@Data
public class AddTraceReq {
private String action;
private String identifier;
private String params;
}

View File

@ -0,0 +1,18 @@
package com.ycwl.basic.stats.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("t_stats")
public class StatsEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String traceId;
private Long memberId;
private Date createTime;
}

View File

@ -0,0 +1,20 @@
package com.ycwl.basic.stats.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("t_stats_record")
public class StatsRecordEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String traceId;
private String action;
private String identifier;
private String params;
private Date createTime;
}

View File

@ -0,0 +1,63 @@
package com.ycwl.basic.stats.interceptor;
import com.ycwl.basic.annotation.IgnoreLogReq;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.stats.service.StatsService;
import com.ycwl.basic.stats.util.StatsUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashSet;
@Component
public class StatsInterceptor implements HandlerInterceptor {
@Autowired
private StatsService statsService;
// 在请求处理前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
request.setAttribute("startTime", System.currentTimeMillis());
String requestURI = request.getRequestURI();
String method = request.getMethod();
String traceId = request.getHeader("traceId");
if (StringUtils.isEmpty(traceId)) {
return true;
// traceId = StatsUtil.createUuid();
// response.setHeader("Set-TraceId", traceId);
// statsService.addStats(traceId, null);
}
if (handlerMethod.getMethodAnnotation(IgnoreLogReq.class) == null) {
statsService.addRecord(traceId, "REQUEST",method + " " + requestURI, null);
}
if (StringUtils.isNotEmpty(BaseContextHandler.getUserId())) {
statsService.updateStats(traceId, Long.valueOf(BaseContextHandler.getUserId()));
}
// 返回 true 继续后续流程false 终止请求
return true;
}
// 控制器方法执行后渲染视图前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
// 请求完全完成后执行无论是否异常
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// long startTime = (Long) request.getAttribute("startTime");
// long endTime = System.currentTimeMillis();
// System.out.println("【AfterCompletion】请求结束耗时" + (endTime - startTime) + "ms");
}
}

View File

@ -0,0 +1,9 @@
package com.ycwl.basic.stats.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ycwl.basic.stats.entity.StatsEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StatsMapper extends BaseMapper<StatsEntity> {
}

View File

@ -0,0 +1,9 @@
package com.ycwl.basic.stats.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ycwl.basic.stats.entity.StatsRecordEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StatsRecordMapper extends BaseMapper<StatsRecordEntity> {
}

View File

@ -0,0 +1,9 @@
package com.ycwl.basic.stats.service;
public interface StatsService {
void addStats(String traceId, Long memberId);
void updateStats(String traceId, Long memberId);
void addRecord(String traceId, String action, String identifier, String params);
}

View File

@ -0,0 +1,50 @@
package com.ycwl.basic.stats.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ycwl.basic.stats.entity.StatsEntity;
import com.ycwl.basic.stats.entity.StatsRecordEntity;
import com.ycwl.basic.stats.mapper.StatsMapper;
import com.ycwl.basic.stats.mapper.StatsRecordMapper;
import com.ycwl.basic.stats.service.StatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class StatsServiceImpl implements StatsService {
@Autowired
private StatsMapper statsMapper;
@Autowired
private StatsRecordMapper statsRecordMapper;
@Override
public void addStats(String traceId, Long memberId) {
StatsEntity entity = new StatsEntity();
entity.setTraceId(traceId);
entity.setMemberId(memberId);
entity.setCreateTime(new Date());
statsMapper.insert(entity);
}
@Override
public void updateStats(String traceId, Long memberId) {
StatsEntity entity = new StatsEntity();
entity.setMemberId(memberId);
LambdaQueryWrapper<StatsEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(StatsEntity::getTraceId, traceId);
statsMapper.update(entity, queryWrapper);
}
@Override
public void addRecord(String traceId, String action, String identifier, String params) {
StatsRecordEntity entity = new StatsRecordEntity();
entity.setTraceId(traceId);
entity.setAction(action);
entity.setIdentifier(identifier);
entity.setParams(params);
entity.setCreateTime(new Date());
statsRecordMapper.insert(entity);
}
}

View File

@ -0,0 +1,9 @@
package com.ycwl.basic.stats.util;
import java.util.UUID;
public class StatsUtil {
public static String createUuid() {
return UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -1,6 +1,7 @@
package com.ycwl.basic.storage.adapters;
import com.ycwl.basic.storage.exceptions.UploadFileFailedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@ -9,16 +10,17 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Date;
@Slf4j
public abstract class AStorageAdapter implements IStorageAdapter {
@Override
public String uploadFile(File file, String ...path) {
public String uploadFile(String contentType, File file, String ...path) {
if (file == null) {
return null;
}
try {
InputStream inputStream = new FileInputStream(file);
return uploadFile(inputStream, path);
return uploadFile(contentType, inputStream, path);
} catch (FileNotFoundException e) {
throw new UploadFileFailedException("文件不存在");
}
@ -31,8 +33,9 @@ public abstract class AStorageAdapter implements IStorageAdapter {
}
try {
InputStream inputStream = file.getInputStream();
return uploadFile(inputStream, path);
return uploadFile(file.getContentType(), inputStream, path);
} catch (Exception e) {
log.warn("文件上传失败", e);
throw new UploadFileFailedException("文件上传失败");
}
}

View File

@ -62,7 +62,7 @@ final public class AliOssAdapter extends AStorageAdapter {
}
@Override
public String uploadFile(InputStream inputStream, String ...path) {
public String uploadFile(String contentType, InputStream inputStream, String ...path) {
if (inputStream == null) {
return null;
}
@ -71,6 +71,9 @@ final public class AliOssAdapter extends AStorageAdapter {
OSS ossClient = wrapper.getOSSClient();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
if (StringUtils.isNotBlank(contentType)) {
metadata.setContentType(contentType);
}
PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream);
ossClient.putObject(putObjectRequest);
return getUrl(path);

View File

@ -55,7 +55,7 @@ public class AwsOssAdapter extends AStorageAdapter {
}
@Override
public String uploadFile(InputStream inputStream, String... path) {
public String uploadFile(String contentType, InputStream inputStream, String... path) {
if (inputStream == null) {
return null;
}
@ -64,6 +64,9 @@ public class AwsOssAdapter extends AStorageAdapter {
AmazonS3Client s3Client = wrapper.getS3Client();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
if (StringUtils.isNotBlank(contentType)) {
metadata.setContentType(contentType);
}
PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream, metadata);
putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead); // 设置访问权限让所有用户都允许访问
s3Client.putObject(putObjectRequest);

View File

@ -14,8 +14,8 @@ import java.util.Map;
public interface IStorageAdapter {
void loadConfig(Map<String, String> config);
void setConfig(StorageConfig config);
String uploadFile(InputStream inputStream, String ...path);
String uploadFile(File file, String ...path);
String uploadFile(String contentType, InputStream inputStream, String ...path);
String uploadFile(String contentType, File file, String ...path);
String uploadFile(MultipartFile file, String ...path);
boolean deleteFile(String ...path);
String getUrl(String ...path);

View File

@ -22,7 +22,7 @@ public class LocalStorageAdapter extends AStorageAdapter{
}
@Override
public String uploadFile(InputStream inputStream, String... path) {
public String uploadFile(String contentType, InputStream inputStream, String... path) {
return "";
}

View File

@ -2,12 +2,15 @@ package com.ycwl.basic.storage.entity;
import lombok.Data;
import java.util.Date;
@Data
public class StorageFileObject {
private String path;
private String name;
private Long size;
private Object rawObject;
private Date modifyTime;
public String getFullPath() {
return path + "/" + name;

View File

@ -1,5 +1,6 @@
package com.ycwl.basic.task;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
@ -37,7 +38,6 @@ import java.util.List;
import java.util.Objects;
import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
import static com.ycwl.basic.service.task.impl.TaskFaceServiceImpl.generateEntityId;
@Component
@EnableScheduling
@ -79,7 +79,7 @@ public class FaceCleaner {
return;
}
faceSampleList.forEach(faceSample -> {
boolean success = adapter.deleteFace(String.valueOf(scenic.getId()), generateEntityId(faceSample));
boolean success = adapter.deleteFace(String.valueOf(scenic.getId()), faceSample.getId().toString());
if (success) {
log.info("当前景区{}人脸样本ID{},删除成功", scenic.getId(), faceSample.getId());
faceSampleMapper.deleteById(faceSample.getId());
@ -140,7 +140,7 @@ public class FaceCleaner {
});
}
@Scheduled(cron = "0 0 3 * * ?")
@Scheduled(cron = "0 15 3 * * ?")
public void deleteNotBuyVideos(){
ScenicReqQuery scenicQuery = new ScenicReqQuery();
List<ScenicRespVO> scenicList = scenicMapper.list(scenicQuery);
@ -212,6 +212,12 @@ public class FaceCleaner {
IStorageAdapter adapter = StorageFactory.use("faces");
List<StorageFileObject> fileObjectList = adapter.listDir("user-face");
fileObjectList.parallelStream().forEach(fileObject -> {
if (fileObject.getModifyTime() != null) {
// 如果是一天以内修改的则跳过
if (DateUtil.between(fileObject.getModifyTime(), new Date(), DateUnit.DAY) < 1) {
return;
}
}
if(faceSampleRespVOS.parallelStream().noneMatch(faceSampleRespVO -> faceSampleRespVO.getFaceUrl().contains(fileObject.getFullPath()))){
log.info("删除人脸文件:{}", fileObject);
adapter.deleteFile(fileObject.getFullPath());
@ -226,6 +232,12 @@ public class FaceCleaner {
log.info("开始清理视频文件");
List<StorageFileObject> fileObjectList = adapter.listDir(StorageConstant.VIDEO_PIECE_PATH);
fileObjectList.parallelStream().forEach(fileObject -> {
if (fileObject.getModifyTime() != null) {
// 如果是一天以内修改的则跳过
if (DateUtil.between(fileObject.getModifyTime(), new Date(), DateUnit.DAY) <= 1) {
return;
}
}
if (list.parallelStream().filter(videoRespVO -> Objects.nonNull(videoRespVO.getVideoUrl())).noneMatch(videoRespVO -> videoRespVO.getVideoUrl().contains(fileObject.getFullPath()))){
log.info("删除文件:{}", fileObject);
adapter.deleteFile(fileObject.getFullPath());
@ -236,6 +248,12 @@ public class FaceCleaner {
log.info("开始清理图片文件");
fileObjectList = adapter.listDir(StorageConstant.PHOTO_PATH);
fileObjectList.parallelStream().forEach(fileObject -> {
if (fileObject.getModifyTime() != null) {
// 如果是一天以内修改的则跳过
if (DateUtil.between(fileObject.getModifyTime(), new Date(), DateUnit.DAY) <= 1) {
return;
}
}
if (list.parallelStream().filter(videoRespVO -> Objects.nonNull(videoRespVO.getUrl())).noneMatch(videoRespVO -> videoRespVO.getUrl().contains(fileObject.getFullPath()))){
log.info("删除文件:{}", fileObject);
adapter.deleteFile(fileObject.getFullPath());
@ -253,6 +271,12 @@ public class FaceCleaner {
log.info("开始清理视频文件");
List<StorageFileObject> fileObjectList = adapter.listDir(StorageConstant.VLOG_PATH);
fileObjectList.parallelStream().forEach(fileObject -> {
if (fileObject.getModifyTime() != null) {
// 如果是一天以内修改的则跳过
if (DateUtil.between(fileObject.getModifyTime(), new Date(), DateUnit.DAY) <= 1) {
return;
}
}
if (list.parallelStream().filter(videoRespVO -> Objects.nonNull(videoRespVO.getVideoUrl())).noneMatch(videoRespVO -> videoRespVO.getVideoUrl().contains(fileObject.getFullPath()))){
log.info("删除文件:{}", fileObject);
adapter.deleteFile(fileObject.getFullPath());

View File

@ -1,12 +1,13 @@
package com.ycwl.basic.task;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.TaskStatusBiz;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.device.DeviceFactory;
import com.ycwl.basic.device.entity.common.FileObject;
import com.ycwl.basic.device.operator.IDeviceStorageOperator;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.SourceMapper;
@ -19,9 +20,9 @@ import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.VideoReUploader;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@ -41,6 +42,7 @@ import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@ -63,6 +65,8 @@ public class VideoPieceGetter {
private TemplateRepository templateRepository;
@Autowired
private TaskStatusBiz taskStatusBiz;
@Autowired
private VideoReUploader videoReUploader;
@Data
public static class Task {
@ -118,7 +122,13 @@ public class VideoPieceGetter {
// taskStatusBiz.setFaceCutStatus(task.faceId, 0);
// }
AtomicBoolean invoke = new AtomicBoolean(false);
final ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 512, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(512));
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNamePrefix("VPG-" + task.faceId + "-t")
.build();
final ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 512, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(512),
threadFactory
);
List<String> currentPlaceholder = new ArrayList<>();
List<FaceSampleEntity> list = faceSampleMapper.listByIds(task.getFaceSampleIds());
Map<Long, Long> pairDeviceMap = new ConcurrentHashMap<>();
@ -277,7 +287,7 @@ public class VideoPieceGetter {
}
log.info("视频裁切成功");
IStorageAdapter adapter = StorageFactory.use("assets");
url = adapter.uploadFile(outFile, "video-source", outFile.getName());
url = adapter.uploadFile("video/mp4", outFile, StorageConstant.VIDEO_PIECE_PATH, outFile.getName());
// 上传成功后删除文件
outFile.delete();
}
@ -312,6 +322,7 @@ public class VideoPieceGetter {
sourceMapper.addRelation(videoSource);
}
sourceMapper.add(sourceEntity);
videoReUploader.addTask(sourceEntity.getVideoUrl(), sourceEntity.getId());
} else {
// 有原视频
if (task.memberId != null && task.faceId != null) {

View File

@ -15,6 +15,7 @@ import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.service.task.impl.TaskTaskServiceImpl;
import lombok.extern.slf4j.Slf4j;
@ -47,8 +48,8 @@ public class VideoTaskGenerator {
private ScenicMapper scenicMapper;
@Autowired
private ScenicRepository scenicRepository;
// TODO: 可配置现在赶时间暂时写死
@Autowired
private FaceService faceService;
@Scheduled(cron = "0 0 * * * *")
public void generateVideoTask() {
@ -84,7 +85,7 @@ public class VideoTaskGenerator {
query.setEndTime(DateUtil.endOfDay(new Date()));
List<FaceRespVO> list = faceMapper.list(query);
list.stream().parallel().forEach(face -> {
taskFaceService.searchFace(face.getId());
faceService.matchFaceId(face.getId(), false);
if (Integer.valueOf(3).equals(scenicConfig.getBookRoutine())) {
// 全部生成
contentList.forEach(content -> {

View File

@ -1,202 +0,0 @@
package com.ycwl.basic.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @date 2022年06月02日 13:40
* 自定义的BigDecimal计算工具类
*/
public class CustomBigDecimalUtils {
public static final BigDecimal HUNDRED_BIG_DECIMAL = new BigDecimal("100");
/**
* 除法
*
* @param divisor 除数
* @param dividend 被除数
* @param scale 保留的小数位数
* @param roundingMode 舍入模式比如四舍五入
* @return java.math.BigDecimal
* @date 2022/6/2 13:43
*/
public static BigDecimal divide(BigDecimal divisor,
BigDecimal dividend,
int scale,
RoundingMode roundingMode) {
if (divisor == null || dividend == null) {
throw new NullPointerException("传入的数据不能为空");
}
if (Double.compare(divisor.doubleValue(), BigDecimal.ZERO.doubleValue()) == 0 ||
Double.compare(dividend.doubleValue(), BigDecimal.ZERO.doubleValue()) == 0) {
return BigDecimal.ZERO;
}
return divisor.divide(dividend, scale, roundingMode);
}
/**
* 除法
*
* @param divisor 除数
* @param dividend 被除数
* @param scale 保留的小数位数
* @param roundingMode 舍入模式比如四舍五入
* @return java.math.BigDecimal
* @date 2022/6/2 13:43
*/
public static BigDecimal divide(Integer divisor,
Integer dividend,
int scale,
RoundingMode roundingMode) {
if (divisor == null || dividend == null) {
throw new NullPointerException("传入的数据不能为空");
}
if (divisor == 0 || dividend == 0) {
return BigDecimal.ZERO;
}
return new BigDecimal(divisor).divide(new BigDecimal(dividend), scale, roundingMode);
}
/**
* 除法
*
* @param divisor 除数
* @param dividend 被除数
* @param scale 保留的小数位数
* @param roundingMode 舍入模式比如四舍五入
* @return java.math.BigDecimal
* @date 2022/6/2 13:43
*/
public static BigDecimal divide(Double divisor,
Double dividend,
int scale,
RoundingMode roundingMode) {
if (divisor == null || dividend == null) {
throw new NullPointerException("传入的数据不能为空");
}
if (Double.compare(divisor, BigDecimal.ZERO.doubleValue()) == 0 ||
Double.compare(dividend, BigDecimal.ZERO.doubleValue()) == 0) {
return BigDecimal.ZERO;
}
return new BigDecimal(String.valueOf(divisor)).divide(new BigDecimal(String.valueOf(dividend)),
scale, roundingMode);
}
/**
* 除法
*
* @param divisor 除数
* @param dividend 被除数
* @param scale 保留的小数位数
* @param roundingMode 舍入模式比如四舍五入
* @return java.math.BigDecimal
* @date 2022/6/2 13:43
*/
public static BigDecimal divide(Double divisor,
Integer dividend,
int scale,
RoundingMode roundingMode) {
if (divisor == null || dividend == null) {
throw new NullPointerException("传入的数据不能为空");
}
if (Double.compare(divisor, BigDecimal.ZERO.doubleValue()) == 0 ||
Double.compare(dividend, BigDecimal.ZERO.doubleValue()) == 0) {
return BigDecimal.ZERO;
}
return new BigDecimal(String.valueOf(divisor)).divide(new BigDecimal(String.valueOf(dividend)),
scale, roundingMode);
}
/**
* 减法
*
* @param subtraction 减数
* @param minuend 被减数
* @return java.math.BigDecimal
* @date 2022/6/2 13:53
*/
public static BigDecimal subtract(BigDecimal subtraction,
BigDecimal minuend) {
if (subtraction == null || minuend == null) {
throw new NullPointerException("传入的数据不能为空");
}
return subtraction.subtract(minuend);
}
/**
* 减法
*
* @param subtraction 减数
* @param minuend 被减数
* @return java.math.BigDecimal
* @date 2022/6/2 13:53
*/
public static BigDecimal subtract(Double subtraction,
Double minuend) {
if (subtraction == null || minuend == null) {
throw new NullPointerException("传入的数据不能为空");
}
return new BigDecimal(String.valueOf(subtraction)).subtract(new BigDecimal(String.valueOf(minuend)));
}
/**
* 减法
*
* @param subtraction 减数
* @param minuend 被减数
* @return java.math.BigDecimal
* @date 2022/6/2 13:53
*/
public static BigDecimal subtract(BigDecimal subtraction,
Double minuend) {
if (subtraction == null || minuend == null) {
throw new NullPointerException("传入的数据不能为空");
}
return subtraction.subtract(new BigDecimal(minuend));
}
/**
* 加法
* @date 2022/6/7 17:31
* @param addend 加数
* @param summand 被加数
* @return java.lang.Double
*/
public static Double add(Double addend,
Double summand) {
if (addend == null || summand == null) {
throw new NullPointerException("传入的数据不能为空");
}
return new BigDecimal(addend.toString()).add(new BigDecimal(summand.toString())).doubleValue();
}
/**
* 加法
* @date 2022/6/7 17:31
* @param addend 加数
* @param summand 被加数
* @return java.lang.Double
*/
public static Double add(BigDecimal addend,
BigDecimal summand) {
if (addend == null || summand == null) {
throw new NullPointerException("传入的数据不能为空");
}
return addend.add(summand).doubleValue();
}
}

View File

@ -1,31 +0,0 @@
package com.ycwl.basic.utils;
import org.apache.http.StatusLine;
/**
* Http服务工具类
* @author songmingsong
*/
public class HttpServiceUtil {
/**
* 请求OK代码
*/
public static final int REQUEST_OK_CODE=200;
/**
* 无响应内容
*/
public static final String REQUEST_NO_RESULT="No_Result";
/**
* 是否响应成功
* @param status 响应状态
* @return boolean
*/
public static boolean success(StatusLine status) {
return status.getStatusCode() == REQUEST_OK_CODE;
}
}

View File

@ -45,8 +45,7 @@ public class ImageUtils {
@Override
public String getContentType() {
// TODO - implementation depends on your requirements
return header.split(":")[0];
return "";
}
@Override

View File

@ -1,180 +0,0 @@
package com.ycwl.basic.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @date 2022年07月14日 14:12
* 数据格式转换工具类
*/
public class ObjectConvertUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectConvertUtils.class);
/**
* 浅克隆
*
* @param source 原对象
* @param clazz 目标对象
* @return T
* @date 2022/4/6 13:45
*/
public static <T> T clone(Object source, Class<T> clazz) {
return clone(source, clazz, true);
}
/**
*
* @date 2022/7/18 16:36
* @param source 原对象
* @param clazz 目标对象
* @param whetherAssignNull 是否赋值空值:true是 false否
* @return T
*/
public static <T> T clone(Object source, Class<T> clazz, Boolean whetherAssignNull) {
T target;
if (source == null) {
return null;
}
try {
target = clazz.newInstance();
if (whetherAssignNull) {
BeanUtils.copyProperties(source, target);
} else {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
return target;
} catch (Exception e) {
LOGGER.error("数据转换异常", e);
return null;
}
}
/**
* 对象与对象之间的数据转换
*
* @param source 转换的数据对象
* @param target 需要转换数据的对象
* @date 2022/7/14 17:24
*/
public static void clone(Object source, Object target) {
clone(source, target, true);
}
/**
*
* @date 2022/7/18 16:39
* @param source 转换的数据对象
* @param target 需要转换数据的对象
* @param whetherAssignNull 是否赋值空值:true是 false否
*/
public static void clone(Object source, Object target, Boolean whetherAssignNull) {
if (source == null) {
return;
}
try {
if (whetherAssignNull) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
} else {
BeanUtils.copyProperties(source, target);
}
} catch (Exception e) {
LOGGER.error("数据转换异常", e);
}
}
/**
* 对象与对象之间的数据转换
*
* @param source 转换的数据对象
* @param target 需要转换数据的对象
* @date 2022/7/15 9:11
*/
public static void cglibBeanCopierCloneObject(Object source, Object target) {
BeanCopierUtils.copyProperties(source, target);
}
/**
* 对象与对象之间的数据转换
*
* @param source 转换的数据对象
* @param target 需要转换数据的对象
* @date 2022/7/15 9:11
*/
public static <T> T cglibBeanCopierCloneObject(Object source, Class<T> target) {
T t;
try {
t = target.newInstance();
BeanCopierUtils.copyProperties(source, t);
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return t;
}
/**
* 将list集合转换为传入的对象的数据集合
*
* @param sourceList 原数据集合
* @param clazz 需要转换的集合数据对象
* @return java.util.List<T>
* @date 2022/4/6 13:49
*/
public static <T> List<T> cloneList(List<?> sourceList, Class<T> clazz) {
return cloneList(sourceList, clazz, true);
}
/**
*
* @date 2022/7/18 16:41
* @param sourceList 原数据集合
* @param clazz 需要转换的集合数据对象
* @param whetherAssignNull 是否赋值空值:true是 false否
* @return java.util.List<T>
*/
public static <T> List<T> cloneList(List<?> sourceList, Class<T> clazz, Boolean whetherAssignNull) {
try {
List<T> targetList = new ArrayList<>(sourceList.size());
for (Object source : sourceList) {
if (whetherAssignNull) {
targetList.add(clone(source, clazz));
} else {
targetList.add(clone(source, clazz, false));
}
}
return targetList;
} catch (Exception e) {
LOGGER.error("数据转换异常", e);
return null;
}
}
/**
* 获取需要忽略的属性
*/
public static String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
// 此处判断可根据需求修改
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
}

View File

@ -1,77 +0,0 @@
package com.ycwl.basic.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @date 2022年08月08日 16:06
* redis缓存操作行为工具类
*/
@Component
public class RedisCacheOperationUtils {
/**
* string类型的redis操作类
*/
private static StringRedisTemplate stringRedisTemplate;
/**
* 通用的redis操作类
*/
private static RedisTemplate redisTemplate;
/**
* 创建string类型的操作对象
*/
public static BoundValueOperations<String, String> createValueOperations(String cacheKey) {
return stringRedisTemplate.boundValueOps(cacheKey);
}
/**
* 创建list类型的操作对象
*/
public static BoundListOperations<String, List<String>> createListOperations(String cacheKey) {
return redisTemplate.boundListOps(cacheKey);
}
/**
* 创建hash类型的操作对象
*/
public static BoundHashOperations createHashOperations(String cacheKey) {
return redisTemplate.boundHashOps(cacheKey);
}
/**
* 创建set类型的操作对象
*/
public static BoundSetOperations createSetOperations(String cacheKey) {
return redisTemplate.boundSetOps(cacheKey);
}
/**
* 创建zset类型的操作对象
*/
public static BoundZSetOperations createZSetOperations(String cacheKey) {
return redisTemplate.boundZSetOps(cacheKey);
}
/**
* 判断缓存key值是否存在
*/
public static Boolean hasKey(String cacheKey) {
return redisTemplate.hasKey(cacheKey);
}
/**
* 删除缓存的redis缓存key
*/
public static Boolean delete(String cacheKey) {
return redisTemplate.delete(cacheKey);
}
@Autowired
public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
RedisCacheOperationUtils.stringRedisTemplate = stringRedisTemplate;
}
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisCacheOperationUtils.redisTemplate = redisTemplate;
}
}

View File

@ -1,98 +0,0 @@
package com.ycwl.basic.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* SSL工具类
*
* @author songmingsong
*/
@Slf4j
public class SslUtil {
/**
* 获取HtttpClient对象
*
* @return CloseableHttpClient
*/
public static CloseableHttpClient sslHttpClientBuild() {
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", trustAllHttpsCertificates()).build();
// 创建ConnectionManager添加Connection配置信息
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(socketFactoryRegistry);
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
return httpClient;
}
/**
* 信任所有Http证书
*
* @return SSLConnectionSocketFactory
*/
private static SSLConnectionSocketFactory trustAllHttpsCertificates() {
SSLConnectionSocketFactory socketFactory = null;
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new X509TrustManager() {
@Override
// 返回受信任的X509证书数组
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
// 该方法检查服务器的证书若不信任该证书同样抛出异常通过自己实现该方法可以使之信任我们指定的任何证书
// 在实现该方法时也可以简单的不做任何处理即一个空的函数体由于不会抛出异常它就会信任任何证书
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
if (chain!=null&&chain.length>0) {
chain[0].checkValidity();
}
} catch (Exception e) {
log.error("checkServerTrusted",e);
}
}
@Override
// 该方法检查客户端的证书若不信任该证书则抛出异常由于我们不需要对客户端进行认证
// 因此我们只需要执行默认的信任管理器的这个方法JSSE中默认的信任管理器类为TrustManager
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
if (chain!=null&&chain.length>0) {
chain[0].checkValidity();
}
} catch (Exception e) {
log.error("checkClientTrusted",e);
}
}
};
trustAllCerts[0] = tm;
SSLContext sc = null;
try {
sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, null);
socketFactory = new SSLConnectionSocketFactory(sc, NoopHostnameVerifier.INSTANCE);
// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
log.error("trustAllHttpsCertificates", e);
}
return socketFactory;
}
}

View File

@ -0,0 +1,149 @@
package com.ycwl.basic.utils;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.http.HttpUtil;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.utils.StorageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
public class VideoReUploader {
private static final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNamePrefix("Vid-ReUp-")
.build();
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 1024, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1024),
threadFactory
);
@Autowired
private SourceMapper sourceMapper;
@Autowired
private ScenicService scenicService;
@Autowired
private VideoMapper videoMapper;
@Autowired
private VideoRepository videoRepository;
@Autowired
private ScenicRepository scenicRepository;
public void addTask(String url, Long sourceId) {
SourceEntity entity = sourceMapper.getEntity(sourceId);
if (entity == null) {
return;
}
if (entity.getScenicId() == null) {
return;
}
if (entity.getType() != 1) {
return;
}
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(entity.getScenicId());
if (scenicConfig == null || scenicConfig.getLocalStoreType() == null || scenicConfig.getLocalStoreConfigJson() == null) {
return;
}
final String dstFilePath = StorageUtil.joinPath(StorageConstant.VIDEO_PIECE_PATH, entity.getId().toString() + ".mp4");
final IStorageAdapter adapter = scenicService.getScenicStorageAdapter(entity.getScenicId());
if (StringUtils.equals(url, adapter.getUrl(dstFilePath))) {
return;
}
String tmpFilePath = UUID.randomUUID().toString();
executor.execute(() -> {
// 先下载后上传
File dstFile = new File(tmpFilePath);
log.info("下载视频:{}sourceId{}", url, sourceId);
long size = HttpUtil.downloadFile(url, dstFile);
log.info("下载视频完成:{};大小:{}sourceId{}", url, size, sourceId);
try {
log.info("开始上传:{}sourceId{}", dstFilePath, sourceId);
String newUrl = adapter.uploadFile("video/mp4", dstFile, dstFilePath);
log.info("上传成功:{}sourceId{}", newUrl, sourceId);
SourceEntity updateEntity = new SourceEntity();
updateEntity.setId(sourceId);
updateEntity.setVideoUrl(newUrl);
sourceMapper.update(updateEntity);
} catch (Exception e) {
log.info("上传失败:{}sourceId{}", dstFilePath, sourceId, e);
} finally {
try {
dstFile.delete();
} catch (Exception ignored) {
}
}
});
}
public void addVideoTask(String url, Long videoId) {
VideoEntity entity = videoMapper.getEntity(videoId);
if (entity == null) {
return;
}
if (entity.getScenicId() == null) {
return;
}
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(entity.getScenicId());
if (scenicConfig == null || scenicConfig.getTmpStoreType() == null || scenicConfig.getTmpStoreConfigJson() == null) {
return;
}
final String dstFilePath = StorageUtil.joinPath(StorageConstant.VLOG_PATH, entity.getTaskId() + "_" + entity.getScenicId() + ".mp4");
final IStorageAdapter adapter = scenicService.getScenicStorageAdapter(entity.getScenicId());
if (StringUtils.equals(url, adapter.getUrl(dstFilePath))) {
return;
}
String tmpFilePath = UUID.randomUUID().toString();
executor.execute(() -> {
// 先下载后上传
File dstFile = new File(tmpFilePath);
log.info("下载视频:{}videoId{}", url, videoId);
long size = HttpUtil.downloadFile(url, dstFile);
log.info("下载视频完成:{};大小:{}videoId{}", url, size, videoId);
try {
log.info("开始上传:{}videoId{}", dstFilePath, videoId);
String newUrl = adapter.uploadFile("video/mp4", dstFile, dstFilePath);
adapter.setAcl(StorageAcl.PUBLIC_READ, dstFilePath);
log.info("上传成功:{}videoId{}", newUrl, videoId);
VideoEntity updateEntity = new VideoEntity();
updateEntity.setId(videoId);
updateEntity.setVideoUrl(newUrl);
videoMapper.update(updateEntity);
} catch (Exception e) {
log.info("上传失败:{}videoId{}", dstFilePath, videoId, e);
} finally {
videoRepository.clearVideoCache(videoId);
try {
dstFile.delete();
} catch (Exception ignored) {
}
}
});
}
}

View File

@ -1,54 +0,0 @@
package com.ycwl.basic.utils;
import cn.hutool.core.util.XmlUtil;
import com.wechat.pay.java.core.util.PemUtil;
import org.springframework.util.Base64Utils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Map;
import java.util.Random;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付
* @Version: 1.0
*/
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException {
//replace 根据实际情况不一定都需要
String replace = privateKey.replace("\\n", "\n");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
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);
}
public static Map<String, Object> xmlToMap(String xmlData) {
return XmlUtil.xmlToMap(xmlData);
}
}

View File

@ -6,26 +6,34 @@ import com.alibaba.fastjson.JSONObject;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WxMpUtil {
private static final String GET_WXA_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s";
private static final String GET_URL_LICK_URL = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s";
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
private static String ACCESS_TOKEN = "";
private static Date expireTime = new Date();
private static final Map<String, String> tokens = new ConcurrentHashMap<>();
private static final Map<String, Date> expireTimes = new ConcurrentHashMap<>();
private static String getAccessToken(String appId, String appSecret) {
if (ACCESS_TOKEN != null && !ACCESS_TOKEN.isEmpty()) {
if (expireTime.getTime() > System.currentTimeMillis()) {
return ACCESS_TOKEN;
if (expireTimes.containsKey(appId)) {
Date expireTime = expireTimes.get(appId);
if (expireTime.getTime() < System.currentTimeMillis()) {
tokens.remove(appId);
}
} else {
tokens.remove(appId);
}
String url = String.format(ACCESS_TOKEN_URL, appId, appSecret);
String response = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(response);
ACCESS_TOKEN = jsonObject.getString("access_token");
expireTime = new Date(System.currentTimeMillis() + jsonObject.getInteger("expires_in") * 1000 / 2);
return ACCESS_TOKEN;
return tokens.computeIfAbsent(appId, (k) -> {
String url = String.format(ACCESS_TOKEN_URL, appId, appSecret);
String response = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(response);
String token = jsonObject.getString("access_token");
Date expireTime = new Date(System.currentTimeMillis() + jsonObject.getInteger("expires_in") * 1000 / 2);
expireTimes.put(appId, expireTime);
return token;
});
}
public static void generateWXAQRCode(String appId, String appSecret, String envVersion, String path, String filePath) throws Exception {

View File

@ -1,17 +0,0 @@
package com.ycwl.basic.xss;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.apache.commons.text.StringEscapeUtils;
import java.io.IOException;
public class XssJacksonDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return StringEscapeUtils.escapeHtml4(jp.getText());
}
}

View File

@ -1,18 +0,0 @@
package com.ycwl.basic.xss;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.apache.commons.text.StringEscapeUtils;
import java.io.IOException;
public class XssJacksonSerializer extends JsonSerializer<String>{
@Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(StringEscapeUtils.escapeHtml4(value));
}
}

View File

@ -49,60 +49,6 @@ mybatis-plus:
# 指定使用的日志配置文件
logging:
config: classpath:logback-spring.xml
# 微信小程序相关配置
wx:
# 公众号的appId
appId: fix
# 公众号的密钥
appSecret: fix
# 小程序的AppId
miniProgramAppId: wxe7ff26af70bfc37c
# 小程序的secret
miniProgramSecret: 5252fbbc68513bc77b7cc0052b9f9695
# 申请openid授权
grandType: authorization_code
# 推送模板
push:
templateId: 5b8vTm7kvwYubqDxb3dxBqFIhc3Swt5l7QHSK5r-ZRI
# 商户号
mchId: 1700540331
# 商户证书序列号
mchSerialNo: 2AD248A1D15F0056D6AEC20B4EEF53F3C32CBFF0
# 支付回调接口地址
payNotifyUrl: https://zhentuai.com/api/mobile/wx/pay/v1/payNotify
# 退款回调接口地址
refundNotifyUrl: https://zhentuai.com/api/mobile/wx/pay/v1/refundNotify
# 商户API私钥路径
keyPath: "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHwDoab8iRX4vn
Ta3a+gh5Z3wcyTM3VkWhkAPJGNJhaNgzOBI9b4v1x+uSZ4To2uXhQE5hvcxWSfNZ
F7H6yp+6estADeZLpdXLBDBvPDOjEGhG7YUHJjWnFh5eY2Rtnx4/7x+xCsRS+Mtq
Mx6KMrb4UZYNIq14peTQgfICkDBdqnUIkFjDmChUy0LlsSFW8AyJIqrBec5VZWf/
QqsIBf9vsTVxrqlX+5owYZuPYoFOIQJFUW8dfU20qk//BxiDjPFZBLNa02aIEbNF
SXZE2TlKD7zD0Qv/w95Hf1V+a/cxP2B6LNtqwioIdHbU6Axu7uHkr0RSPXPsvOWY
eUIpGP4bAgMBAAECggEBALm34w3TYtKu2D/tfDh9gkWGTuHgu6q2nrTxVmOxnWEN
/v3YIzVVsfaJs2ACuZNaeqNsi7PaqNKNnSD3o/X+UHYsVy8t/THWdSl1sqapfYUd
6yYPDkEwaG/6Y6/0j1pQt+pPpNKRpSlTwqTx9HIfZvkHuhBqbokfDNhECUQS1bUu
8pmyALIkuXu38B/xCs/EH+Lp1N69IA0mwalT/2zi9ZJhGNg5OWBzNbkMKf2Tck+9
SnS6s+pAT6YxB9qYhg645H3dRKXdeSYbjI+uiIe/7cJvTxfQDLD/oDap2TJQH3iM
SVbSfNFbR1tH6PiYx9eXeElEg2QXEPztbf8NcUlGOGkCgYEA8PewLVzUdHD20KkX
tZP+JCvSTy2f0kPHTc3+BU+MOlG8uZzsK06efo+X5BGkMRj8P+MPp9rJQr5IJqJl
GpeA1XpF54v2DIcNcjrQHIJ9XWoWirZjMWRVn4D5laQTI+FEV8pyFfJJOLIgBe9b
c6hBHNnAB4Y6JZ/s+US4ymK4wQ0CgYEA1DZMcuyiUgjnprUsOIokpg8RxGLIvB5+
2FyCffa/CikMRU8bRtHdpJLguArrR9rEILU6fICPARdlCg8r0XMrvniiAaS3YlDj
tSxbfrrS6xySvVFdNusv/j3i5/76IedsSArJLeQpIZMZ3n6q/dmq4kbWh+bT/5z+
MVpjWixpYccCgYBtaEh5kDh2VgP6YYv+SZ+OVMc8Y/64vUV0sh6v0ppcsFf7/p/M
WfnkhNX2G3xtPmbpqvKkx9WxlCu2Pu2g0UERrF6o7wdcUMVuI/3xs92v2Ec72+vV
tTSbIzgvFTwLgnBBXA3IoSVVtKqNh0wCi1Zk/wkNYYhtJNu3odg1K/Wu0QKBgBOv
IbI7TucrGkm1Xm+0KKgal7xOqW4BqiRpmFUU0S2hFxlKuC3+g3+jfCK2KJLWsQCT
ruQjjKA+Skn/lEHuW+1kBSr/217MQALrJWWA8NWMJfRXmrzgXehIV0bLuOnyLHIW
Rgjys/oAShMATt4TFa29gmLCv4FjT5TGXJbdrby7AoGBAOqER71Vi90UV84sYLWx
DSW/3q5/QrndmeeaQALslA6sidnTZWqlhMsNPl8dfBKl6xnrCdcaeY5xk/xmVOqP
0KedJgT+IjLwY1yJ9QOBd02ejAY3qNlKt2NiSWv2GBY1cZxqhkHNaI/UWI9CAyH5
YfkdFNxtYLdVAwuylMoV3fKI
-----END PRIVATE KEY-----"
# 商户APIV3密钥
apiV3: ZHENTUAIzhentuaiZHENTUAIzhentuai
# 存储
storage:
@ -168,7 +114,45 @@ facebody:
accessKeyId: "LTAI5tMwrmxVcUEKoH5QzLHx"
accessKeySecret: "ZCIP8aKx1jwX1wkeYIPQEDZ8fPtN1c"
region: "cn-shanghai"
#支付
pay:
default-use: zt
configs:
- name: zt
type: WX_MP_PAY
config:
merchantId: "1700540331"
appId: "wxe7ff26af70bfc37c"
privateKey: "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHwDoab8iRX4vn
Ta3a+gh5Z3wcyTM3VkWhkAPJGNJhaNgzOBI9b4v1x+uSZ4To2uXhQE5hvcxWSfNZ
F7H6yp+6estADeZLpdXLBDBvPDOjEGhG7YUHJjWnFh5eY2Rtnx4/7x+xCsRS+Mtq
Mx6KMrb4UZYNIq14peTQgfICkDBdqnUIkFjDmChUy0LlsSFW8AyJIqrBec5VZWf/
QqsIBf9vsTVxrqlX+5owYZuPYoFOIQJFUW8dfU20qk//BxiDjPFZBLNa02aIEbNF
SXZE2TlKD7zD0Qv/w95Hf1V+a/cxP2B6LNtqwioIdHbU6Axu7uHkr0RSPXPsvOWY
eUIpGP4bAgMBAAECggEBALm34w3TYtKu2D/tfDh9gkWGTuHgu6q2nrTxVmOxnWEN
/v3YIzVVsfaJs2ACuZNaeqNsi7PaqNKNnSD3o/X+UHYsVy8t/THWdSl1sqapfYUd
6yYPDkEwaG/6Y6/0j1pQt+pPpNKRpSlTwqTx9HIfZvkHuhBqbokfDNhECUQS1bUu
8pmyALIkuXu38B/xCs/EH+Lp1N69IA0mwalT/2zi9ZJhGNg5OWBzNbkMKf2Tck+9
SnS6s+pAT6YxB9qYhg645H3dRKXdeSYbjI+uiIe/7cJvTxfQDLD/oDap2TJQH3iM
SVbSfNFbR1tH6PiYx9eXeElEg2QXEPztbf8NcUlGOGkCgYEA8PewLVzUdHD20KkX
tZP+JCvSTy2f0kPHTc3+BU+MOlG8uZzsK06efo+X5BGkMRj8P+MPp9rJQr5IJqJl
GpeA1XpF54v2DIcNcjrQHIJ9XWoWirZjMWRVn4D5laQTI+FEV8pyFfJJOLIgBe9b
c6hBHNnAB4Y6JZ/s+US4ymK4wQ0CgYEA1DZMcuyiUgjnprUsOIokpg8RxGLIvB5+
2FyCffa/CikMRU8bRtHdpJLguArrR9rEILU6fICPARdlCg8r0XMrvniiAaS3YlDj
tSxbfrrS6xySvVFdNusv/j3i5/76IedsSArJLeQpIZMZ3n6q/dmq4kbWh+bT/5z+
MVpjWixpYccCgYBtaEh5kDh2VgP6YYv+SZ+OVMc8Y/64vUV0sh6v0ppcsFf7/p/M
WfnkhNX2G3xtPmbpqvKkx9WxlCu2Pu2g0UERrF6o7wdcUMVuI/3xs92v2Ec72+vV
tTSbIzgvFTwLgnBBXA3IoSVVtKqNh0wCi1Zk/wkNYYhtJNu3odg1K/Wu0QKBgBOv
IbI7TucrGkm1Xm+0KKgal7xOqW4BqiRpmFUU0S2hFxlKuC3+g3+jfCK2KJLWsQCT
ruQjjKA+Skn/lEHuW+1kBSr/217MQALrJWWA8NWMJfRXmrzgXehIV0bLuOnyLHIW
Rgjys/oAShMATt4TFa29gmLCv4FjT5TGXJbdrby7AoGBAOqER71Vi90UV84sYLWx
DSW/3q5/QrndmeeaQALslA6sidnTZWqlhMsNPl8dfBKl6xnrCdcaeY5xk/xmVOqP
0KedJgT+IjLwY1yJ9QOBd02ejAY3qNlKt2NiSWv2GBY1cZxqhkHNaI/UWI9CAyH5
YfkdFNxtYLdVAwuylMoV3fKI
-----END PRIVATE KEY-----"
serialNumber: "2AD248A1D15F0056D6AEC20B4EEF53F3C32CBFF0"
apiV3Key: ZHENTUAIzhentuaiZHENTUAIzhentuai
notify:
defaultUse: ""
configs:

View File

@ -54,57 +54,6 @@ mybatis-plus:
# 指定使用的日志配置文件
logging:
config: classpath:logback-spring-prod.xml
# 微信小程序相关配置
wx:
# 公众号的appId
appId: fix
# 公众号的密钥
appSecret: fix
# 小程序的AppId
miniProgramAppId: wxe7ff26af70bfc37c
# 小程序的secret
miniProgramSecret: 5252fbbc68513bc77b7cc0052b9f9695
# 申请openid授权
grandType: authorization_code
# 商户号
mchId: 1700540331
# 商户证书序列号
mchSerialNo: 2AD248A1D15F0056D6AEC20B4EEF53F3C32CBFF0
# 支付回调接口地址
payNotifyUrl: https://zhentuai.com/api/mobile/wx/pay/v1/payNotify
# 退款回调接口地址
refundNotifyUrl: https://zhentuai.com/api/mobile/wx/pay/v1/refundNotify
# 商户API私钥路径
keyPath: "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHwDoab8iRX4vn
Ta3a+gh5Z3wcyTM3VkWhkAPJGNJhaNgzOBI9b4v1x+uSZ4To2uXhQE5hvcxWSfNZ
F7H6yp+6estADeZLpdXLBDBvPDOjEGhG7YUHJjWnFh5eY2Rtnx4/7x+xCsRS+Mtq
Mx6KMrb4UZYNIq14peTQgfICkDBdqnUIkFjDmChUy0LlsSFW8AyJIqrBec5VZWf/
QqsIBf9vsTVxrqlX+5owYZuPYoFOIQJFUW8dfU20qk//BxiDjPFZBLNa02aIEbNF
SXZE2TlKD7zD0Qv/w95Hf1V+a/cxP2B6LNtqwioIdHbU6Axu7uHkr0RSPXPsvOWY
eUIpGP4bAgMBAAECggEBALm34w3TYtKu2D/tfDh9gkWGTuHgu6q2nrTxVmOxnWEN
/v3YIzVVsfaJs2ACuZNaeqNsi7PaqNKNnSD3o/X+UHYsVy8t/THWdSl1sqapfYUd
6yYPDkEwaG/6Y6/0j1pQt+pPpNKRpSlTwqTx9HIfZvkHuhBqbokfDNhECUQS1bUu
8pmyALIkuXu38B/xCs/EH+Lp1N69IA0mwalT/2zi9ZJhGNg5OWBzNbkMKf2Tck+9
SnS6s+pAT6YxB9qYhg645H3dRKXdeSYbjI+uiIe/7cJvTxfQDLD/oDap2TJQH3iM
SVbSfNFbR1tH6PiYx9eXeElEg2QXEPztbf8NcUlGOGkCgYEA8PewLVzUdHD20KkX
tZP+JCvSTy2f0kPHTc3+BU+MOlG8uZzsK06efo+X5BGkMRj8P+MPp9rJQr5IJqJl
GpeA1XpF54v2DIcNcjrQHIJ9XWoWirZjMWRVn4D5laQTI+FEV8pyFfJJOLIgBe9b
c6hBHNnAB4Y6JZ/s+US4ymK4wQ0CgYEA1DZMcuyiUgjnprUsOIokpg8RxGLIvB5+
2FyCffa/CikMRU8bRtHdpJLguArrR9rEILU6fICPARdlCg8r0XMrvniiAaS3YlDj
tSxbfrrS6xySvVFdNusv/j3i5/76IedsSArJLeQpIZMZ3n6q/dmq4kbWh+bT/5z+
MVpjWixpYccCgYBtaEh5kDh2VgP6YYv+SZ+OVMc8Y/64vUV0sh6v0ppcsFf7/p/M
WfnkhNX2G3xtPmbpqvKkx9WxlCu2Pu2g0UERrF6o7wdcUMVuI/3xs92v2Ec72+vV
tTSbIzgvFTwLgnBBXA3IoSVVtKqNh0wCi1Zk/wkNYYhtJNu3odg1K/Wu0QKBgBOv
IbI7TucrGkm1Xm+0KKgal7xOqW4BqiRpmFUU0S2hFxlKuC3+g3+jfCK2KJLWsQCT
ruQjjKA+Skn/lEHuW+1kBSr/217MQALrJWWA8NWMJfRXmrzgXehIV0bLuOnyLHIW
Rgjys/oAShMATt4TFa29gmLCv4FjT5TGXJbdrby7AoGBAOqER71Vi90UV84sYLWx
DSW/3q5/QrndmeeaQALslA6sidnTZWqlhMsNPl8dfBKl6xnrCdcaeY5xk/xmVOqP
0KedJgT+IjLwY1yJ9QOBd02ejAY3qNlKt2NiSWv2GBY1cZxqhkHNaI/UWI9CAyH5
YfkdFNxtYLdVAwuylMoV3fKI
-----END PRIVATE KEY-----"
# 商户APIV3密钥
apiV3: ZHENTUAIzhentuaiZHENTUAIzhentuai
# 存储
storage:
@ -171,7 +120,45 @@ facebody:
accessKeyId: "LTAI5tMwrmxVcUEKoH5QzLHx"
accessKeySecret: "ZCIP8aKx1jwX1wkeYIPQEDZ8fPtN1c"
region: "cn-shanghai"
#支付
pay:
default-use: zt
configs:
- name: zt
type: WX_MP_PAY
config:
merchantId: "1700540331"
appId: "wxe7ff26af70bfc37c"
privateKey: "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHwDoab8iRX4vn
Ta3a+gh5Z3wcyTM3VkWhkAPJGNJhaNgzOBI9b4v1x+uSZ4To2uXhQE5hvcxWSfNZ
F7H6yp+6estADeZLpdXLBDBvPDOjEGhG7YUHJjWnFh5eY2Rtnx4/7x+xCsRS+Mtq
Mx6KMrb4UZYNIq14peTQgfICkDBdqnUIkFjDmChUy0LlsSFW8AyJIqrBec5VZWf/
QqsIBf9vsTVxrqlX+5owYZuPYoFOIQJFUW8dfU20qk//BxiDjPFZBLNa02aIEbNF
SXZE2TlKD7zD0Qv/w95Hf1V+a/cxP2B6LNtqwioIdHbU6Axu7uHkr0RSPXPsvOWY
eUIpGP4bAgMBAAECggEBALm34w3TYtKu2D/tfDh9gkWGTuHgu6q2nrTxVmOxnWEN
/v3YIzVVsfaJs2ACuZNaeqNsi7PaqNKNnSD3o/X+UHYsVy8t/THWdSl1sqapfYUd
6yYPDkEwaG/6Y6/0j1pQt+pPpNKRpSlTwqTx9HIfZvkHuhBqbokfDNhECUQS1bUu
8pmyALIkuXu38B/xCs/EH+Lp1N69IA0mwalT/2zi9ZJhGNg5OWBzNbkMKf2Tck+9
SnS6s+pAT6YxB9qYhg645H3dRKXdeSYbjI+uiIe/7cJvTxfQDLD/oDap2TJQH3iM
SVbSfNFbR1tH6PiYx9eXeElEg2QXEPztbf8NcUlGOGkCgYEA8PewLVzUdHD20KkX
tZP+JCvSTy2f0kPHTc3+BU+MOlG8uZzsK06efo+X5BGkMRj8P+MPp9rJQr5IJqJl
GpeA1XpF54v2DIcNcjrQHIJ9XWoWirZjMWRVn4D5laQTI+FEV8pyFfJJOLIgBe9b
c6hBHNnAB4Y6JZ/s+US4ymK4wQ0CgYEA1DZMcuyiUgjnprUsOIokpg8RxGLIvB5+
2FyCffa/CikMRU8bRtHdpJLguArrR9rEILU6fICPARdlCg8r0XMrvniiAaS3YlDj
tSxbfrrS6xySvVFdNusv/j3i5/76IedsSArJLeQpIZMZ3n6q/dmq4kbWh+bT/5z+
MVpjWixpYccCgYBtaEh5kDh2VgP6YYv+SZ+OVMc8Y/64vUV0sh6v0ppcsFf7/p/M
WfnkhNX2G3xtPmbpqvKkx9WxlCu2Pu2g0UERrF6o7wdcUMVuI/3xs92v2Ec72+vV
tTSbIzgvFTwLgnBBXA3IoSVVtKqNh0wCi1Zk/wkNYYhtJNu3odg1K/Wu0QKBgBOv
IbI7TucrGkm1Xm+0KKgal7xOqW4BqiRpmFUU0S2hFxlKuC3+g3+jfCK2KJLWsQCT
ruQjjKA+Skn/lEHuW+1kBSr/217MQALrJWWA8NWMJfRXmrzgXehIV0bLuOnyLHIW
Rgjys/oAShMATt4TFa29gmLCv4FjT5TGXJbdrby7AoGBAOqER71Vi90UV84sYLWx
DSW/3q5/QrndmeeaQALslA6sidnTZWqlhMsNPl8dfBKl6xnrCdcaeY5xk/xmVOqP
0KedJgT+IjLwY1yJ9QOBd02ejAY3qNlKt2NiSWv2GBY1cZxqhkHNaI/UWI9CAyH5
YfkdFNxtYLdVAwuylMoV3fKI
-----END PRIVATE KEY-----"
serialNumber: "2AD248A1D15F0056D6AEC20B4EEF53F3C32CBFF0"
apiV3Key: ZHENTUAIzhentuaiZHENTUAIzhentuai
# 通知到人
notify:
defaultUse: "developer"

Some files were not shown because too many files have changed in this diff Show More