Compare commits

...

13 Commits

Author SHA1 Message Date
c2ce6f91ed 日志调整 2025-06-13 16:55:11 +08:00
da3de2cc89 缓存时间调整 2025-06-13 15:36:29 +08:00
d245d09837 支持跳过景区文件删除,跳过重复adapter 2025-06-13 15:36:29 +08:00
4d8236afa1 通过identity定义是否相同 2025-06-13 15:35:11 +08:00
04e2ade669 添加锁避免高并发冲突 2025-06-13 15:35:11 +08:00
cd4678120d 修复删除逻辑 2025-06-13 15:35:11 +08:00
cf9802f9ec 添加后台订单筛选 2025-06-10 16:46:18 +08:00
a84b38dab5 推客统计 2025-06-10 16:46:18 +08:00
e32f231a8f 设备支持自定义标定区域 2025-06-10 16:46:18 +08:00
f9fcb06355 根据经纬度查询景区,并添加查询所有景区的功能 2025-06-10 16:46:18 +08:00
2ae0b3c4b3 参数错误打印错误请求地址方便排查 2025-06-05 14:24:47 +08:00
3bd8face68 缓存统计信息 2025-06-04 13:46:10 +08:00
1841e43b85 1 2025-06-03 15:02:45 +08:00
37 changed files with 553 additions and 362 deletions

View File

@ -2,9 +2,11 @@ package com.ycwl.basic.controller.mobile;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO;
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;
@ -22,6 +24,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
@ -41,11 +44,21 @@ public class AppScenicController {
private AppScenicService appScenicService;
@Autowired
private ScenicRepository scenicRepository;
private static final List<String> ENABLED_USER_IDs = new ArrayList<>(){{
add("3932535453961555968");
add("3936121342868459520");
add("3936940597855784960");
}};
@ApiOperation("分页查询景区列表")
@PostMapping("/page")
public ApiResponse<PageInfo<ScenicAppVO>> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery){
return appScenicService.pageQuery(scenicReqQuery);
String userId = BaseContextHandler.getUserId();
if (ENABLED_USER_IDs.contains(userId)) {
return appScenicService.pageQuery(scenicReqQuery);
} else {
return ApiResponse.success(new PageInfo<>(new ArrayList<>()));
}
}
@ApiOperation("根据id查询景区详情")
@IgnoreToken
@ -96,4 +109,10 @@ public class AppScenicController {
List<ContentPageVO> contentPageVOS = faceService.faceContentList(faceId);
return ApiResponse.success(contentPageVOS);
}
@PostMapping("/nearby")
public ApiResponse<List<ScenicAppVO>> nearby(@RequestBody ScenicIndexVO scenicIndexVO) {
List<ScenicAppVO> list = appScenicService.scenicListByLnLa(scenicIndexVO);
return ApiResponse.success(list);
}
}

View File

@ -2,6 +2,7 @@ package com.ycwl.basic.exception;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -11,6 +12,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.io.IOException;
/**
@ -54,6 +57,12 @@ public class CustomExceptionHandle {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return ApiResponse.buildResponse(bizException.getCode(), bizException.getMsg());
}
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
public ApiResponse<String> handle(HttpServletRequest request, HttpServletResponse response, MethodArgumentTypeMismatchException exception) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
LOGGER.error("参数错误 -> 请求地址:{},请求方式:{}", request.getRequestURI(), request.getMethod());
return ApiResponse.buildResponse(5000, "参数错误!");
}
@ExceptionHandler(value =IOException.class)
public ApiResponse<String> handle(HttpServletResponse response, IOException e) {

View File

@ -2,6 +2,7 @@ package com.ycwl.basic.mapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
@ -93,4 +94,7 @@ public interface StatisticsMapper {
List<Long> getBrokerIdListForUser(Long memberId, Date startTime, Date endTime);
Long getUserRecentEnterType(Long memberId, Date endTime);
List<AppStatisticsFunnelVO> listStatByScenic(Long scenicId, Date startTime, Date endTime);
int insertStat(Long scenicId, Date date, AppStatisticsFunnelVO data);
}

View File

@ -70,7 +70,7 @@ public class ScenicAppVO {
/**
* 距离
*/
@ApiModelProperty("距离")
@ApiModelProperty("距离")
private BigDecimal distance;
/**

View File

@ -18,6 +18,7 @@ public class CommonQueryReq {
private Integer standard;
@ApiModelProperty(value = "景区id")
private Long scenicId;
private boolean realtime;
private Date startTime;
private Date endTime;
}

View File

@ -3,6 +3,8 @@ package com.ycwl.basic.model.mobile.statistic.resp;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @Author:longbinbin
@ -13,49 +15,118 @@ import lombok.Data;
public class AppStatisticsFunnelVO {
@ApiModelProperty("镜头检测游客数")
// private Integer cameraShotOfMemberNum;
private String cameraShotOfMemberNum;
@ApiModelProperty("镜头检测游客数_扫码访问人数_转化率")
private String csom_scaom;
private int cameraShotOfMemberNum; // cs1
@ApiModelProperty("扫码访问人数")
private Integer scanCodeVisitorOfMemberNum;
@ApiModelProperty("扫码访问人数_上传头像人数_转化率")
private String scaom_ufom;
private int scanCodeVisitorOfMemberNum; // sv1
@ApiModelProperty("上传头像(人脸)人数")
private Integer uploadFaceOfMemberNum;
@ApiModelProperty("上传头像人数_推送订阅人数_转化率")
private String ufom_pom;
private int uploadFaceOfMemberNum; // u1
@ApiModelProperty("推送订阅人数")
private Integer pushOfMemberNum;
@ApiModelProperty("推送订阅人数_生成视频人数_转化率")
private String pom_cvom;
private int pushOfMemberNum; // m1
@ApiModelProperty("生成视频人数")
private Integer completeVideoOfMemberNum;
@ApiModelProperty("生成视频人数_预览视频人数_转化率")
private String cvom_pvom;
private int completeVideoOfMemberNum; // gv1
@ApiModelProperty("预览视频人数")
private Integer previewVideoOfMemberNum;
@ApiModelProperty("预览视频人数_点击购买人数_转化率")
private String pvom_cpom;
private int previewVideoOfMemberNum; // pv1
@ApiModelProperty("点击购买人数")
private Integer clickOnPayOfMemberNum;
@ApiModelProperty("点击购买人数_支付订单人数_转化率")
private String cpom_pom;
private int clickOnPayOfMemberNum; // cp1
@ApiModelProperty("支付订单人数")
private Integer payOfMemberNum;
private int payOfMemberNum; // p1
@ApiModelProperty("总访问人数")
private Integer totalVisitorOfMemberNum;
private int totalVisitorOfMemberNum; // v1
@ApiModelProperty("生成视频条数")
private Integer completeOfVideoNum;
private int completeOfVideoNum; // gv2
@ApiModelProperty("预览视频条数")
private Integer previewOfVideoNum;
private int previewOfVideoNum; // pv2
@ApiModelProperty("支付订单数")
private Integer payOfOrderNum;
private int payOfOrderNum; // p2
@ApiModelProperty("支付订单金额")
private String payOfOrderAmount;
private BigDecimal payOfOrderAmount; // o3
@ApiModelProperty("退款订单数")
private Integer refundOfOrderNum;
private int refundOfOrderNum; // ro2
@ApiModelProperty("退款订单金额")
private String refundOfOrderAmount;
private BigDecimal refundOfOrderAmount; // ro3
@ApiModelProperty("镜头检测游客数_扫码访问人数_转化率")
public String getCsom_scaom() {
return "-"; // TODO: REAL
}
@ApiModelProperty("扫码访问人数_上传头像人数_转化率")
public String getScaom_ufom() {
if (uploadFaceOfMemberNum == 0 || scanCodeVisitorOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(uploadFaceOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(scanCodeVisitorOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
@ApiModelProperty("上传头像人数_推送订阅人数_转化率")
public String getUfom_pom() {
if (pushOfMemberNum == 0 || uploadFaceOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(uploadFaceOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(pushOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
@ApiModelProperty("推送订阅人数_生成视频人数_转化率")
public String getPom_cvom() {
if (completeVideoOfMemberNum == 0 || pushOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(completeVideoOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(pushOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
@ApiModelProperty("生成视频人数_预览视频人数_转化率")
public String getCvom_pvom() {
if (previewVideoOfMemberNum == 0 || completeVideoOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(previewVideoOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(completeVideoOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
@ApiModelProperty("预览视频人数_点击购买人数_转化率")
public String getPvom_cpom() {
if (clickOnPayOfMemberNum == 0 || previewVideoOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(clickOnPayOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(previewVideoOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
@ApiModelProperty("点击购买人数_支付订单人数_转化率")
public String getCpom_pom() {
if (payOfMemberNum == 0 || clickOnPayOfMemberNum == 0) {
return "0.00";
}
return new BigDecimal(payOfMemberNum)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(clickOnPayOfMemberNum), 2, RoundingMode.HALF_UP)
.toString();
}
public BigDecimal payOfOrderAmount() {
return payOfOrderAmount;
}
public String getPayOfOrderAmount() {
if (payOfOrderAmount == null) {
return "0.00";
}
return payOfOrderAmount.toString();
}
public BigDecimal refundOfOrderAmount() {
return refundOfOrderAmount;
}
public String getRefundOfOrderAmount() {
if (refundOfOrderAmount == null) {
return "0.00";
}
return refundOfOrderAmount.toString();
}
}

View File

@ -61,4 +61,5 @@ public class DeviceConfigEntity {
private Integer imageFree;
private Integer videoFree;
private Long pairDevice;
private String videoCrop;
}

View File

@ -38,4 +38,6 @@ public class FaceSampleReqQuery extends BaseQueryParameterReq {
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
private String matchSampleIds;
}

View File

@ -31,7 +31,7 @@ public class DeviceRepository {
}
DeviceEntity device = deviceMapper.getByDeviceId(deviceId);
if (null != device) {
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JSONObject.toJSONString(device));
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JSONObject.toJSONString(device), 3, TimeUnit.DAYS);
}
return device;
}
@ -45,8 +45,8 @@ public class DeviceRepository {
device = deviceMapper.getByDeviceNo2(deviceNo);
}
if (null != device) {
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), JSONObject.toJSONString(device));
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getId()), JSONObject.toJSONString(device));
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), JSONObject.toJSONString(device), 3, TimeUnit.DAYS);
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getId()), JSONObject.toJSONString(device), 3, TimeUnit.DAYS);
} else {
redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), "null", 60L, TimeUnit.SECONDS);
}
@ -64,7 +64,7 @@ public class DeviceRepository {
deviceConfig.setDeviceId(deviceId);
deviceMapper.addConfig(deviceConfig);
}
redisTemplate.opsForValue().set(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId), JSONObject.toJSONString(deviceConfig));
redisTemplate.opsForValue().set(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId), JSONObject.toJSONString(deviceConfig), 3, TimeUnit.DAYS);
return deviceConfig;
}
@ -111,7 +111,7 @@ public class DeviceRepository {
device.setOnline(online);
device.setKeepaliveAt(keepaliveAt);
device.setIpAddr(ipAddr);
redisTemplate.opsForValue().set(String.format(DEVICE_ONLINE_CACHE_KEY, deviceId), JSONObject.toJSONString(device), 2, TimeUnit.DAYS);
redisTemplate.opsForValue().set(String.format(DEVICE_ONLINE_CACHE_KEY, deviceId), JSONObject.toJSONString(device), 3, TimeUnit.DAYS);
// deviceMapper.updateOnlineStatus(deviceId, ipAddr, online, keepaliveAt);
updateDeviceCache(device);
}

View File

@ -34,7 +34,7 @@ public class FaceRepository {
}
FaceEntity face = faceMapper.get(id);
if (face != null) {
redisTemplate.opsForValue().set(String.format(FACE_CACHE_KEY, id), JSONObject.toJSONString(face), 60 * 60 * 24L, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(String.format(FACE_CACHE_KEY, id), JSONObject.toJSONString(face), 12, TimeUnit.HOURS);
}
return face;
}

View File

@ -8,6 +8,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@Component
public class PriceRepository {
@ -27,8 +28,8 @@ public class PriceRepository {
if (priceConfigEntity == null) {
priceConfigEntity = mapper.getPriceByScenicTypeGoods(scenicId, type, goodsId);
if (priceConfigEntity != null) {
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(priceConfigEntity));
redisTemplate.opsForValue().set(String.format(PRICE_ID_CACHE, priceConfigEntity.getId()), JSON.toJSONString(priceConfigEntity));
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(priceConfigEntity), 12, TimeUnit.HOURS);
redisTemplate.opsForValue().set(String.format(PRICE_ID_CACHE, priceConfigEntity.getId()), JSON.toJSONString(priceConfigEntity), 12, TimeUnit.HOURS);
}
}
return priceConfigEntity;

View File

@ -11,6 +11,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
@Component
public class VideoRepository {
@ -28,8 +29,8 @@ public class VideoRepository {
}
VideoEntity video = videoMapper.getEntity(videoId);
if (video != null) {
redisTemplate.opsForValue().set(String.format(VIDEO_CACHE_KEY, videoId), JSON.toJSONString(video));
redisTemplate.opsForValue().set(String.format(VIDEO_BY_TASK_ID_CACHE_KEY, video.getTaskId()), JSON.toJSONString(video));
redisTemplate.opsForValue().set(String.format(VIDEO_CACHE_KEY, videoId), JSON.toJSONString(video), 12, TimeUnit.HOURS);
redisTemplate.opsForValue().set(String.format(VIDEO_BY_TASK_ID_CACHE_KEY, video.getTaskId()), JSON.toJSONString(video), 12, TimeUnit.HOURS);
}
return video;
}
@ -40,8 +41,8 @@ public class VideoRepository {
}
VideoEntity video = videoMapper.findByTaskId(taskId);
if (video != null) {
redisTemplate.opsForValue().set(String.format(VIDEO_BY_TASK_ID_CACHE_KEY, taskId), JSON.toJSONString(video));
redisTemplate.opsForValue().set(String.format(VIDEO_CACHE_KEY, video.getId()), JSON.toJSONString(video));
redisTemplate.opsForValue().set(String.format(VIDEO_BY_TASK_ID_CACHE_KEY, taskId), JSON.toJSONString(video), 12, TimeUnit.HOURS);
redisTemplate.opsForValue().set(String.format(VIDEO_CACHE_KEY, video.getId()), JSON.toJSONString(video), 12, TimeUnit.HOURS);
}
return video;
}

View File

@ -31,7 +31,7 @@ public class VideoTaskRepository {
} else {
TaskEntity task = taskMapper.get(taskId);
if (task != null && 1 == task.getStatus()) {
redisTemplate.opsForValue().set(String.format(TASK_CACHE_KEY, taskId), JSONObject.toJSONString(task), 60 * 60 * 24L, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(String.format(TASK_CACHE_KEY, taskId), JSONObject.toJSONString(task), 12, TimeUnit.HOURS);
}
return task;
}

View File

@ -3,6 +3,7 @@ package com.ycwl.basic.service.mobile;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO;
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
@ -28,4 +29,6 @@ public interface AppScenicService {
ApiResponse<ScenicRespVO> getMyScenic();
ApiResponse<List<DeviceRespVO>> getMyDevices();
List<ScenicAppVO> scenicListByLnLa(ScenicIndexVO scenicIndexVO);
}

View File

@ -8,6 +8,7 @@ import com.ycwl.basic.mapper.*;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO;
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
@ -21,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
/**
@ -110,4 +112,10 @@ public class AppScenicServiceImpl implements AppScenicService {
List<DeviceRespVO> deviceRespVOList = deviceMapper.listByScenicIdWithWVP(account.getScenicId());
return ApiResponse.success(deviceRespVOList);
}
@Override
public List<ScenicAppVO> scenicListByLnLa(ScenicIndexVO scenicIndexVO) {
List<ScenicAppVO> scenicAppVOS = scenicMapper.scenicListByLnLa(scenicIndexVO);
return scenicAppVOS.stream().filter(scenic -> scenic.getDistance().compareTo(scenic.getRadius().multiply(BigDecimal.valueOf(1_000L))) < 0).toList();
}
}

View File

@ -2,6 +2,8 @@ package com.ycwl.basic.service.mobile.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
@ -11,17 +13,22 @@ import com.ycwl.basic.model.mobile.statistic.resp.AppSta1VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppSta2VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppSta3VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
import com.ycwl.basic.model.pc.scenicDeviceStats.resp.ScenicDeviceStatsResp;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author:longbinbin
@ -56,11 +63,13 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
standardToNewSpecificTime(query);
//查询处理数据逻辑
oneStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取上一个周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
oneStatisticsHandler(2,query,vo);
if (DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday())) {
//----------------------------------------------------
//获取上一个周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
oneStatisticsHandler(2,query,vo);
}
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
@ -82,11 +91,13 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
standardToNewSpecificTime(query);
//查询处理数据逻辑
twoStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
twoStatisticsHandler(2,query,vo);
if (DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday())) {
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
twoStatisticsHandler(2, query, vo);
}
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
@ -108,11 +119,13 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
standardToNewSpecificTime(query);
//查询处理数据逻辑
freeStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
freeStatisticsHandler(2,query,vo);
if (DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday())) {
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
freeStatisticsHandler(2, query, vo);
}
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
@ -121,8 +134,13 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
return ApiResponse.success(vo);
}
@Autowired
private RedisTemplate<String, String> redisTemplate;
private ReentrantLock lock = new ReentrantLock();
@Override
public ApiResponse<AppStatisticsFunnelVO> userConversionFunnel(CommonQueryReq query) {
String redisKey = "statistics:tmp_cache:"+query.getScenicId();
AppStatisticsFunnelVO vo = new AppStatisticsFunnelVO();
if(query.getEndTime()==null && query.getStartTime()==null){
// 没有传时间,则代表用户没有自定义查询时间,使用standard来判断查询时间范围
@ -133,89 +151,138 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
//获取当前周期的具体时间范围
standardToNewSpecificTime(query);
}
if (!query.isRealtime()) {
if (!(DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday()))) {
// 查询缓存数据
List<AppStatisticsFunnelVO> list = statisticsMapper.listStatByScenic(query.getScenicId(), query.getStartTime(), query.getEndTime());
AppStatisticsFunnelVO resp = new AppStatisticsFunnelVO();
if (list != null && !list.isEmpty()) {
for (AppStatisticsFunnelVO item : list) {
// Integer fields
resp.setCameraShotOfMemberNum(addIntSafely(resp.getCameraShotOfMemberNum(), item.getCameraShotOfMemberNum()));
resp.setScanCodeVisitorOfMemberNum(addIntSafely(resp.getScanCodeVisitorOfMemberNum(), item.getScanCodeVisitorOfMemberNum()));
resp.setUploadFaceOfMemberNum(addIntSafely(resp.getUploadFaceOfMemberNum(), item.getUploadFaceOfMemberNum()));
resp.setPushOfMemberNum(addIntSafely(resp.getPushOfMemberNum(), item.getPushOfMemberNum()));
resp.setCompleteVideoOfMemberNum(addIntSafely(resp.getCompleteVideoOfMemberNum(), item.getCompleteVideoOfMemberNum()));
resp.setPreviewVideoOfMemberNum(addIntSafely(resp.getPreviewVideoOfMemberNum(), item.getPreviewVideoOfMemberNum()));
resp.setClickOnPayOfMemberNum(addIntSafely(resp.getClickOnPayOfMemberNum(), item.getClickOnPayOfMemberNum()));
resp.setPayOfMemberNum(addIntSafely(resp.getPayOfMemberNum(), item.getPayOfMemberNum()));
resp.setTotalVisitorOfMemberNum(addIntSafely(resp.getTotalVisitorOfMemberNum(), item.getTotalVisitorOfMemberNum()));
resp.setCompleteOfVideoNum(addIntSafely(resp.getCompleteOfVideoNum(), item.getCompleteOfVideoNum()));
resp.setPreviewOfVideoNum(addIntSafely(resp.getPreviewOfVideoNum(), item.getPreviewOfVideoNum()));
resp.setPayOfOrderNum(addIntSafely(resp.getPayOfOrderNum(), item.getPayOfOrderNum()));
resp.setRefundOfOrderNum(addIntSafely(resp.getRefundOfOrderNum(), item.getRefundOfOrderNum()));
//镜头检测游客数
Integer cameraShotOfMemberNum=statisticsMapper.countCameraShotOfMember(query);
//扫码访问人数
// 扫小程序码或景区码进入访问的用户数,包括授权用户(使用OpenID进行精准统计)和未授权用户(使用 UUID统计访问)。但当用户授权时,获取OpenID并与UUID关联,删除本地UUID,避免重复记录。
Integer scanCodeVisitorOfMemberNum=statisticsMapper.countScanCodeOfMember(query);
//镜头检测游客数_扫码访问人数_转化率
// vo.setCsom_scaom(calculateConversionRate(scanCodeVisitorOfMemberNum,cameraShotOfMemberNum));
vo.setCsom_scaom("-");
//上传头像(人脸)人数
// 上传了人脸的用户数(包括本地临时ID和获取到OpenID的,同一设备微信获取到OpenID要覆盖掉之前生成的临时ID),上传多张人脸都只算一个人。
Integer uploadFaceOfMemberNum=statisticsMapper.countUploadFaceOfMember(query);
//扫码访问人数_上传头像人数_转化率
vo.setScaom_ufom(calculateConversionRate(uploadFaceOfMemberNum,scanCodeVisitorOfMemberNum));
//推送订阅人数
// 只要点了允许通知,哪怕只勾选1条订阅都算
Integer pushOfMemberNum =statisticsMapper.countPushOfMember(query);
//上传头像人数_推送订阅人数_转化率
vo.setUfom_pom((calculateConversionRate(pushOfMemberNum,uploadFaceOfMemberNum)));
//生成视频人数
// 生成过Vlog视频的用户ID数,要注意屏蔽掉以前没有片段也能生成的情况
Integer completeVideoOfMemberNum =statisticsMapper.countCompleteVideoOfMember(query);
//推送订阅人数_生成视频人数_转化率
vo.setPom_cvom((calculateConversionRate(completeVideoOfMemberNum,pushOfMemberNum)));
//预览视频人数
// 购买前播放了5秒的视频条数。
Integer previewVideoOfMemberNum =statisticsMapper.countPreviewVideoOfMember(query);
if (previewVideoOfMemberNum==null){
previewVideoOfMemberNum=0;
// BigDecimal fields
resp.setPayOfOrderAmount(addBigDecimalSafely(resp.payOfOrderAmount(), item.payOfOrderAmount()));
resp.setRefundOfOrderAmount(addBigDecimalSafely(resp.refundOfOrderAmount(), item.refundOfOrderAmount()));
}
}
return ApiResponse.success(resp);
}
}
//生成视频人数_预览视频人数_转化率
vo.setCvom_pvom((calculateConversionRate(previewVideoOfMemberNum,completeVideoOfMemberNum)));
//点击购买人数
// 点了立即购买按钮的用户ID就算,包括支付的和未支付的都算,只要点击了。
Integer clickOnPayOfMemberNum =statisticsMapper.countClickPayOfMember(query);
//预览视频人数_点击购买人数_转化率
vo.setPvom_cpom((calculateConversionRate(clickOnPayOfMemberNum,previewVideoOfMemberNum)));
//支付订单人数
Integer payOfMemberNum =statisticsMapper.countPayOfMember(query);
//点击购买人数_支付订单人数_转化率
vo.setCpom_pom((calculateConversionRate(payOfMemberNum,clickOnPayOfMemberNum)));
//总访问人数
// 通过任何途径访问到小程序的总人数,包括授权用户和未授权用户。
Integer totalVisitorOfMemberNum =statisticsMapper.countTotalVisitorOfMember(query);
if (!query.isRealtime()) {
if (DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday())) {
// 缓存
if (redisTemplate.hasKey(redisKey)) {
String s = redisTemplate.opsForValue().get(redisKey);
JSONObject cacheObj = JSON.parseObject(s);
return ApiResponse.success(cacheObj.toJavaObject(AppStatisticsFunnelVO.class));
}
}
}
lock.lock();
try {
// 缓存
if (!query.isRealtime()) {
if (DateUtil.isIn(query.getStartTime(), DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(query.getEndTime(), DateUtil.tomorrow(), DateUtil.yesterday())) {
// 缓存
if (redisTemplate.hasKey(redisKey)) {
String s = redisTemplate.opsForValue().get(redisKey);
JSONObject cacheObj = JSON.parseObject(s);
return ApiResponse.success(cacheObj.toJavaObject(AppStatisticsFunnelVO.class));
}
}
}
//镜头检测游客数
// Integer cameraShotOfMemberNum=statisticsMapper.countCameraShotOfMember(query);
//扫码访问人数
// 扫小程序码或景区码进入访问的用户数,包括授权用户(使用OpenID进行精准统计)和未授权用户(使用 UUID统计访问)。但当用户授权时,获取OpenID并与UUID关联,删除本地UUID,避免重复记录。
Integer scanCodeVisitorOfMemberNum=statisticsMapper.countScanCodeOfMember(query);
//上传头像(人脸)人数
// 上传了人脸的用户数(包括本地临时ID和获取到OpenID的,同一设备微信获取到OpenID要覆盖掉之前生成的临时ID),上传多张人脸都只算一个人。
Integer uploadFaceOfMemberNum=statisticsMapper.countUploadFaceOfMember(query);
//推送订阅人数
// 只要点了允许通知,哪怕只勾选1条订阅都算
Integer pushOfMemberNum =statisticsMapper.countPushOfMember(query);
//生成视频人数
// 生成过Vlog视频的用户ID数,要注意屏蔽掉以前没有片段也能生成的情况
Integer completeVideoOfMemberNum =statisticsMapper.countCompleteVideoOfMember(query);
//预览视频人数
// 购买前播放了5秒的视频条数。
Integer previewVideoOfMemberNum =statisticsMapper.countPreviewVideoOfMember(query);
if (previewVideoOfMemberNum==null){
previewVideoOfMemberNum=0;
}
//点击购买人数
// 点了立即购买按钮的用户ID就算,包括支付的和未支付的都算,只要点击了。
Integer clickOnPayOfMemberNum =statisticsMapper.countClickPayOfMember(query);
//支付订单人数
Integer payOfMemberNum =statisticsMapper.countPayOfMember(query);
//总访问人数
// 通过任何途径访问到小程序的总人数,包括授权用户和未授权用户。
Integer totalVisitorOfMemberNum =statisticsMapper.countTotalVisitorOfMember(query);
// Integer totalVisitorOfMemberNum =scanCodeVisitorOfMemberNum;
//生成视频条数
// 仅指代生成的Vlog条数,不包含录像原片。
Integer completeOfVideoNum =statisticsMapper.countCompleteOfVideo(query);
//预览视频条数
Integer previewOfVideoNum =statisticsMapper.countPreviewOfVideo(query);
//支付订单数
Integer payOfOrderNum =statisticsMapper.countPayOfOrder(query);
//支付订单金额
BigDecimal payOfOrderAmount =statisticsMapper.countOrderAmount(query);
//退款订单数
Integer refundOfOrderNum =statisticsMapper.countRefundOfOrder(query);
//退款订单金额
BigDecimal refundOfOrderAmount =statisticsMapper.countRefundAmount(query);
//生成视频条数
// 仅指代生成的Vlog条数,不包含录像原片。
Integer completeOfVideoNum =statisticsMapper.countCompleteOfVideo(query);
//预览视频条数
Integer previewOfVideoNum =statisticsMapper.countPreviewOfVideo(query);
//支付订单数
Integer payOfOrderNum =statisticsMapper.countPayOfOrder(query);
//支付订单金额
BigDecimal payOfOrderAmount =statisticsMapper.countOrderAmount(query);
//退款订单数
Integer refundOfOrderNum =statisticsMapper.countRefundOfOrder(query);
//退款订单金额
BigDecimal refundOfOrderAmount =statisticsMapper.countRefundAmount(query);
// vo.setCameraShotOfMemberNum(cameraShotOfMemberNum);
vo.setCameraShotOfMemberNum("-");
vo.setScanCodeVisitorOfMemberNum(scanCodeVisitorOfMemberNum);
vo.setUploadFaceOfMemberNum(uploadFaceOfMemberNum);
vo.setPushOfMemberNum(pushOfMemberNum);
vo.setCompleteVideoOfMemberNum(completeVideoOfMemberNum);
vo.setPreviewVideoOfMemberNum(previewVideoOfMemberNum);
vo.setClickOnPayOfMemberNum(clickOnPayOfMemberNum);
vo.setPayOfMemberNum(payOfMemberNum);
vo.setScanCodeVisitorOfMemberNum(scanCodeVisitorOfMemberNum);
vo.setUploadFaceOfMemberNum(uploadFaceOfMemberNum);
vo.setPushOfMemberNum(pushOfMemberNum);
vo.setCompleteVideoOfMemberNum(completeVideoOfMemberNum);
vo.setPreviewVideoOfMemberNum(previewVideoOfMemberNum);
vo.setClickOnPayOfMemberNum(clickOnPayOfMemberNum);
vo.setPayOfMemberNum(payOfMemberNum);
//转化率格式
DecimalFormat df = new DecimalFormat("0.0");
vo.setTotalVisitorOfMemberNum(totalVisitorOfMemberNum);
vo.setCompleteOfVideoNum(completeOfVideoNum);
vo.setPreviewOfVideoNum(previewOfVideoNum);
vo.setPayOfOrderNum(payOfOrderNum);
vo.setPayOfOrderAmount(payOfOrderAmount.setScale(2, RoundingMode.HALF_UP));
vo.setRefundOfOrderNum(refundOfOrderNum);
vo.setRefundOfOrderAmount(refundOfOrderAmount.setScale(2, RoundingMode.HALF_UP));
statisticsMapper.insertStat(query.getScenicId(), new Date(), vo);
redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(vo), 60, TimeUnit.SECONDS);
return ApiResponse.success(vo);
} finally {
lock.unlock();
}
}
vo.setTotalVisitorOfMemberNum(totalVisitorOfMemberNum);
vo.setCompleteOfVideoNum(completeOfVideoNum);
vo.setPreviewOfVideoNum(previewOfVideoNum);
vo.setPayOfOrderNum(payOfOrderNum);
vo.setPayOfOrderAmount(df.format(payOfOrderAmount.setScale(2, RoundingMode.HALF_UP)));
vo.setRefundOfOrderNum(refundOfOrderNum);
vo.setRefundOfOrderAmount(df.format(refundOfOrderAmount.setScale(2, RoundingMode.HALF_UP)));
private BigDecimal addBigDecimalSafely(BigDecimal bd1, BigDecimal bd2) {
if (bd1 == null) {
bd1 = BigDecimal.ZERO;
}
if (bd2 == null) {
bd2 = BigDecimal.ZERO;
}
return bd1.add(bd2).setScale(2, RoundingMode.HALF_UP);
}
return ApiResponse.success(vo);
private int addIntSafely(Integer int1, Integer int2) {
return int1 == null ? 0 : int1 + (int2 == null ? 0 : int2);
}
@Override
@ -272,27 +339,22 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
* @param vo
*/
private void freeStatisticsHandler(Integer cycle,CommonQueryReq query,AppSta3VO vo){
//查询扫码访问人数
int sceneNum=statisticsMapper.countScanCodeOfMember(query);
//查询推送订阅人数
int pushNum=statisticsMapper.countPushOfMember(query);
// 查询预览视频人数
Integer previewNum=statisticsMapper.countPreviewVideoOfMember(query);
ApiResponse<AppStatisticsFunnelVO> resp = userConversionFunnel(query);
AppStatisticsFunnelVO data = resp.getData();
if(cycle==1){
//当前周期的扫码访问人数
vo.setNowScanCodeOfPeopleNum(sceneNum);
vo.setNowScanCodeOfPeopleNum(data.getScanCodeVisitorOfMemberNum());
//当前周期的推送订阅人数
vo.setNowPushOfPeopleNum(pushNum);
vo.setNowPushOfPeopleNum(data.getPushOfMemberNum());
//当前周期的预览视频人数
vo.setNowPreviewVideoOfPeopleNum(previewNum);
vo.setNowPreviewVideoOfPeopleNum(data.getPreviewOfVideoNum());
}else if(cycle==2){
//上一个周期的扫码访问人数
vo.setPreviousScanCodeOfPeopleNum(sceneNum);
vo.setPreviousScanCodeOfPeopleNum(data.getScanCodeVisitorOfMemberNum());
//上一个周期的推送订阅人数
vo.setPreviousPushOfPeopleNum(pushNum);
vo.setPreviousPushOfPeopleNum(data.getPushOfMemberNum());
//上一个周期的预览视频人数
vo.setPreviousPreviewVideoOfPeopleNum(previewNum);
vo.setPreviousPreviewVideoOfPeopleNum(data.getPreviewOfVideoNum());
}
}
@ -334,23 +396,25 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
* @param vo
*/
private void oneStatisticsHandler(Integer cycle,CommonQueryReq query,AppSta1VO vo){
ApiResponse<AppStatisticsFunnelVO> resp = userConversionFunnel(query);
AppStatisticsFunnelVO data = resp.getData();
//订单金额格式
DecimalFormat orderAmountDf = new DecimalFormat("0.0");
//转化率格式
DecimalFormat df = new DecimalFormat("0.00");
// 计算当前周期的支付订单金额
BigDecimal orderAmount=statisticsMapper.countOrderAmount(query).setScale(2, RoundingMode.HALF_UP);
String orderAmount=data.getPayOfOrderAmount();
//查询预览视频人数
int preview=statisticsMapper.countPreviewVideoOfMember(query);
int preview=data.getPreviewOfVideoNum();
//查询扫码人数
int scanCode=statisticsMapper.countScanCodeOfMember(query);
int scanCode=data.getScanCodeVisitorOfMemberNum();
//查询付费人数
int pay=statisticsMapper.countPayOfMember(query);
int payCount=statisticsMapper.countPayOfOrder(query);
int pay=data.getPayOfMemberNum();
int payCount=data.getPayOfOrderNum();
if(cycle==1){
// 支付过订单的金额,包含已退款的金额。
vo.setNowOrderAmount(orderAmountDf.format(orderAmount));
vo.setNowOrderAmount(orderAmount);
// 订单数÷预览人数。假设一共5个人预览,产生了3个订单,其实是2个人支付的(其中1人购买2单),预览-支付转化率是3÷5,而不是2÷5。
if(preview==0){
vo.setNowPreviewPay("0.00");
@ -368,7 +432,7 @@ public class AppStatisticsServiceImpl implements AppStatisticsService {
}else if(cycle==2){
//上一个周期的支付订单金额
// 支付过订单的金额,包含已退款的金额。
vo.setPreviousOrderAmount(orderAmountDf.format(orderAmount));
vo.setPreviousOrderAmount(orderAmount);
// 订单数÷预览人数。假设一共5个人预览,产生了3个订单,其实是2个人支付的(其中1人购买2单),预览-支付转化率是3÷5,而不是2÷5。
if(preview==0){
vo.setPreviousPreviewPay("0.00");

View File

@ -27,7 +27,7 @@ public class DeviceStatsServiceImpl implements DeviceStatsService {
end = DateUtil.endOfDay(new Date());
}
ScenicDeviceStatsListResp resp = new ScenicDeviceStatsListResp();
if (DateUtil.isIn(start, DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(start, DateUtil.tomorrow(), DateUtil.yesterday())) {
if (DateUtil.isIn(start, DateUtil.tomorrow(), DateUtil.yesterday()) && DateUtil.isIn(end, DateUtil.tomorrow(), DateUtil.yesterday())) {
resp.setRealtime(true);
List<ScenicDeviceStatsResp> data = mapper.countRealtimeStatsByScenicId(scenicId, start, end);
resp.setData(data);

View File

@ -35,6 +35,11 @@ import java.util.stream.Collectors;
final public class AliOssAdapter extends AStorageAdapter {
private AliOssStorageConfig config;
@Override
public String identity() {
return config.identity();
}
@Override
public void loadConfig(Map<String, String> _config) {
AliOssStorageConfig config = new AliOssStorageConfig();

View File

@ -28,6 +28,11 @@ import java.util.stream.Collectors;
public class AwsOssAdapter extends AStorageAdapter {
private AwsOssStorageConfig config;
@Override
public String identity() {
return config.identity();
}
@Override
public void loadConfig(Map<String, String> _config) {
AwsOssStorageConfig config = new AwsOssStorageConfig();

View File

@ -12,6 +12,7 @@ import java.util.List;
import java.util.Map;
public interface IStorageAdapter {
String identity();
void loadConfig(Map<String, String> config);
void setConfig(StorageConfig config);
String uploadFile(String contentType, InputStream inputStream, String ...path);

View File

@ -11,6 +11,11 @@ import java.util.List;
import java.util.Map;
public class LocalStorageAdapter extends AStorageAdapter{
@Override
public String identity() {
return "";
}
@Override
public void loadConfig(Map<String, String> config) {

View File

@ -1,5 +1,6 @@
package com.ycwl.basic.storage.entity;
import cn.hutool.crypto.digest.MD5;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -19,6 +20,11 @@ public class AliOssStorageConfig extends StorageConfig {
// TODO: 检查配置是否正确
}
@Override
public String identity() {
return MD5.create().digestHex16(accessKeyId + bucketName + prefix);
}
public String getUrl() {
String url = this.url;
if (url == null) {

View File

@ -1,5 +1,6 @@
package com.ycwl.basic.storage.entity;
import cn.hutool.crypto.digest.MD5;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -19,6 +20,11 @@ public class AwsOssStorageConfig extends StorageConfig {
// TODO: 检查配置是否正确
}
@Override
public String identity() {
return MD5.create().digestHex16(accessKeyId + bucketName + prefix);
}
public String getUrl() {
String url = this.url;
if (url == null) {

View File

@ -2,4 +2,5 @@ package com.ycwl.basic.storage.entity;
public abstract class StorageConfig {
public abstract void checkEverythingOK();
public abstract String identity();
}

View File

@ -33,11 +33,13 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
import static com.ycwl.basic.constant.StorageConstant.VIID_FACE;
@Component
@EnableScheduling
@ -58,6 +60,7 @@ public class FaceCleaner {
private FaceMapper faceMapper;
@Autowired
private ScenicService scenicService;
public static final List<String> disableDeleteScenicIds = List.of("3955650120997015552");
@Scheduled(cron = "0 0 1 * * ?")
public void deleteExpireSample(){
@ -217,9 +220,9 @@ public class FaceCleaner {
}
private void cleanFaceSampleOss() {
log.info("开始清理人脸文件");
List<FaceSampleRespVO> faceSampleRespVOS = faceSampleMapper.list(new FaceSampleReqQuery());
List<FaceSampleRespVO> list = faceSampleMapper.list(new FaceSampleReqQuery());
IStorageAdapter adapter = StorageFactory.use("faces");
List<StorageFileObject> fileObjectList = adapter.listDir("user-face");
List<StorageFileObject> fileObjectList = adapter.listDir(VIID_FACE);
fileObjectList.parallelStream().forEach(fileObject -> {
if (fileObject.getModifyTime() != null) {
// 如果是一天以内修改的,则跳过
@ -227,7 +230,7 @@ public class FaceCleaner {
return;
}
}
if(faceSampleRespVOS.parallelStream().noneMatch(faceSampleRespVO -> faceSampleRespVO.getFaceUrl().contains(fileObject.getName()))){
if(list.parallelStream().noneMatch(faceSampleRespVO -> faceSampleRespVO.getFaceUrl().contains(fileObject.getName()))){
log.info("删除人脸文件:{}", fileObject);
adapter.deleteFile(fileObject.getFullPath());
}
@ -236,8 +239,20 @@ public class FaceCleaner {
public void cleanSourceOss() {
log.info("开始清理源视频素材文件");
List<SourceRespVO> list = sourceMapper.list(new SourceReqQuery());
ArrayList<String> adapterIdentity = new ArrayList<>();
scenicMapper.list(new ScenicReqQuery()).forEach(scenic -> {
if (disableDeleteScenicIds.contains(scenic.getId().toString())) {
log.info("景区【{}】禁止删除文件,跳过!", scenic.getName());
return;
}
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenic.getId());
String identity = adapter.identity();
if (!adapterIdentity.contains(identity)) {
log.info("因为Identity相同,跳过");
adapterIdentity.add(identity);
} else {
return;
}
log.info("开始清理视频文件");
List<StorageFileObject> fileObjectList = adapter.listDir(StorageConstant.VIDEO_PIECE_PATH);
fileObjectList.parallelStream().forEach(fileObject -> {
@ -275,8 +290,20 @@ public class FaceCleaner {
public void cleanVideoOss() {
log.info("开始清理视频文件");
List<VideoRespVO> list = videoMapper.list(new VideoReqQuery());
ArrayList<String> adapterIdentity = new ArrayList<>();
scenicMapper.list(new ScenicReqQuery()).forEach(scenic -> {
if (disableDeleteScenicIds.contains(scenic.getId().toString())) {
log.info("景区【{}】禁止删除文件,跳过!", scenic.getName());
return;
}
IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenic.getId());
String identity = adapter.identity();
if (!adapterIdentity.contains(identity)) {
adapterIdentity.add(identity);
} else {
log.info("因为Identity相同,跳过");
return;
}
log.info("开始清理视频文件");
List<StorageFileObject> fileObjectList = adapter.listDir(StorageConstant.VLOG_PATH);
fileObjectList.parallelStream().forEach(fileObject -> {

View File

@ -2,7 +2,15 @@ package com.ycwl.basic.task;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.mapper.ScenicDeviceStatsMapper;
import com.ycwl.basic.mapper.ScenicMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.model.pc.scenicDeviceStats.entity.ScenicDeviceStatsEntity;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.utils.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@ -13,9 +21,15 @@ import java.util.List;
@Component
@EnableScheduling
public class ScenicDeviceStatsTask {
public class ScenicStatsTask {
@Autowired
private ScenicDeviceStatsMapper mapper;
@Autowired
private StatisticsMapper statisticsMapper;
@Autowired
private AppStatisticsService statisticsService;
@Autowired
private ScenicMapper scenicMapper;
@Scheduled(cron = "0 10 0 * * *")
public void countDeviceStats() {
Date yesterdayStart = DateUtil.beginOfDay(DateUtil.yesterday());
@ -38,4 +52,20 @@ public class ScenicDeviceStatsTask {
});
}
}
@Scheduled(cron = "0 20 0 * * *")
public void countScenicStats() {
Date yesterdayStart = DateUtil.beginOfDay(DateUtil.yesterday());
Date yesterdayEnd = DateUtil.endOfDay(yesterdayStart);
List<ScenicRespVO> list = scenicMapper.list(new ScenicReqQuery());
list.forEach((scenic) -> {
CommonQueryReq query = new CommonQueryReq();
query.setScenicId(scenic.getId());
query.setStartTime(yesterdayStart);
query.setEndTime(yesterdayEnd);
query.setRealtime(true);
ApiResponse<AppStatisticsFunnelVO> resp = statisticsService.userConversionFunnel(query);
AppStatisticsFunnelVO data = resp.getData();
statisticsMapper.insertStat(scenic.getId(), yesterdayStart, data);
});
}
}

View File

@ -23,6 +23,7 @@ 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;
@ -288,6 +289,9 @@ public class VideoPieceGetter {
sourceEntity.setUrl(imgSource.getUrl());
sourceEntity.setPosJson(imgSource.getPosJson());
}
if (StringUtils.isNotBlank(config.getVideoCrop())) {
sourceEntity.setPosJson(config.getVideoCrop());
}
sourceEntity.setVideoUrl(url);
sourceEntity.setFaceSampleId(faceSampleId);
sourceEntity.setScenicId(device.getScenicId());
@ -314,6 +318,9 @@ public class VideoPieceGetter {
videoReUploader.addTask(sourceEntity.getId());
} else {
source.setVideoUrl(url);
if (StringUtils.isNotBlank(config.getVideoCrop())) {
source.setPosJson(config.getVideoCrop());
}
sourceMapper.update(source);
videoReUploader.addTask(source.getId());
}

View File

@ -8,6 +8,7 @@ import java.io.InputStream;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class WxMpUtil {
private static final String GET_WXA_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s";
@ -16,25 +17,31 @@ public class WxMpUtil {
public static final String GET_USER_PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
private static final Map<String, String> tokens = new ConcurrentHashMap<>();
private static final Map<String, Date> expireTimes = new ConcurrentHashMap<>();
private static final ReentrantLock lock = new ReentrantLock();
private static String getAccessToken(String appId, String appSecret) {
if (expireTimes.containsKey(appId)) {
Date expireTime = expireTimes.get(appId);
if (expireTime.getTime() < System.currentTimeMillis()) {
lock.lock();
try {
if (expireTimes.containsKey(appId)) {
Date expireTime = expireTimes.get(appId);
if (expireTime.getTime() < System.currentTimeMillis()) {
tokens.remove(appId);
}
} else {
tokens.remove(appId);
}
} else {
tokens.remove(appId);
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;
});
} finally {
lock.unlock();
}
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

@ -8,45 +8,6 @@
</encoder>
</appender>
<!-- info 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>logs/project_info.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch><!-- 如果命中ERROR就禁止这条日志 -->
<onMismatch>ACCEPT</onMismatch><!-- 如果没有命中就使用这条规则 -->
</filter>
<!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_info.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>logs/project_info.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<maxHistory>7</maxHistory>
<!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>1GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<!--编码器-->
<encoder>
<!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- error 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_error.log -->
@ -85,39 +46,15 @@
</encoder>
</appender>
<appender name="sql_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>logs/project_sql.log</File>
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/project_sql.%d.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="error_log" />
<appender-ref ref="STDOUT" />
</root>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.ycwl.basic" level="INFO">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
</logger>
<!-- 利用logback输入mybatis的sql日志,
注意:如果不加 additivity="false" 则此logger会将输出转发到自身以及祖先的logger中,就会出现日志文件中sql重复打印-->
<logger name="com.ycwl.basic.mapper" level="DEBUG" additivity="false">
<appender-ref ref="sql_log" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -21,83 +21,6 @@
</encoder>
</appender>
<!-- info 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>logs/project_info.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch><!-- 如果命中ERROR就禁止这条日志 -->
<onMismatch>ACCEPT</onMismatch><!-- 如果没有命中就使用这条规则 -->
</filter>
<!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_info.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>logs/project_info.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<maxHistory>30</maxHistory>
<!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>20GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<!--编码器-->
<encoder>
<!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- error 日志-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_error.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
<appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径和名称-->
<File>logs/project_error.log</File>
<!--是否追加到文件末尾,默认为true-->
<append>true</append>
<!-- ThresholdFilter过滤低于指定阈值的事件。 对于等于或高于阈值的事件,ThresholdFilter将在调用其decision()方法时响应NEUTRAL。 但是,将拒绝级别低于阈值的事件 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level><!-- 低于ERROR级别的日志(debug,info)将被拒绝,等于或者高于ERROR的级别将相应NEUTRAL -->
</filter>
<!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:logs/project_error.2017-12-05.0.log -->
<!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
<fileNamePattern>logs/project_error.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
<maxHistory>30</maxHistory>
<!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
<totalSizeCap>20GB</totalSizeCap>
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<!--编码器-->
<encoder>
<!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender(不用在意level值)。
换句话说,appender是从记录器层次结构中附加地继承的。
例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。
@ -107,26 +30,14 @@
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.ycwl.basic" level="INFO">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
</logger>
<!-- 利用logback输入mybatis的sql日志,
注意:如果不加 additivity="false" 则此logger会将输出转发到自身以及祖先的logger中,就会出现日志文件中sql重复打印-->
<logger name="com.ycwl.basic.mapper" level="DEBUG" additivity="false">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
<appender-ref ref="STDOUT" />
</logger>
<!-- additivity=false代表禁止默认累计的行为,即com.atomikos中的日志只会记录到日志文件中,不会输出层次级别更高的任何appender-->
<logger name="com.ycwl.basic" level="INFO" additivity="false">
<appender-ref ref="info_log" />
<appender-ref ref="error_log" />
<appender-ref ref="STDOUT" />
</logger>

View File

@ -43,7 +43,7 @@
WHERE DATE_ADD(#{startTime}, INTERVAL (units.i + tens.i * 10 + hundreds.i * 100) DAY) &lt;= #{endTime}
) date_series
LEFT JOIN broker_record br ON DATE(br.create_time) = date_series.date AND br.broker_id = #{brokerId}
LEFT JOIN t_stats_record s ON s.action = 'CODE_SCAN' and s.identifier = br.id
LEFT JOIN t_stats_record s ON s.action = 'CODE_SCAN' and DATE(s.create_time) = date_series.date and s.identifier = #{brokerId}
GROUP BY date_series.date
ORDER BY date_series.date
</select>

View File

@ -40,7 +40,8 @@
enable_pre_book = #{enablePreBook},
image_free = #{imageFree},
video_free = #{videoFree},
pair_device = #{pairDevice}
pair_device = #{pairDevice},
video_crop = #{videoCrop}
where id = #{id}
</update>
<update id="updateEntity">

View File

@ -306,6 +306,14 @@
<if test="endCancelTime!= null ">
and cancel_at &lt;= #{endCancelTime}
</if>
<if test="type != null">
<if test="type &lt; 0">
and o.type = #{type}
</if>
<if test="type &gt;= 0">
and o.id in (select order_id from order_item where goods_type = #{type})
</if>
</if>
</where>
order by o.create_at desc
</select>

View File

@ -173,7 +173,8 @@
where s.id = #{id}
</select>
<select id="appList" resultType="com.ycwl.basic.model.mobile.scenic.ScenicAppVO">
select s.id, `name`, `phone`, introduction,cover_url, longitude, latitude, radius, province, city, area, address
select s.id, `name`, `phone`, introduction, cover_url, longitude, latitude, radius, province, city, area, address,
(SELECT COUNT(1) FROM device WHERE scenic_id = s.id AND status = 1) as deviceNum
from scenic s
where
`status` = 1
@ -218,13 +219,15 @@
ifnull(
cast(
ST_Distance_Sphere(
Point(longitude, latitude), Point(#{params.longitude}, #{params.latitude})
Point(longitude, latitude), Point(#{longitude}, #{latitude})
) AS
DECIMAL(10, 2)
), 0
) AS distance
from scenic s
where `status` = 1
and `latitude` is not null
and `longitude` is not null
ORDER BY distance ASC
</select>
<select id="getConfig" resultType="com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity">

View File

@ -312,4 +312,15 @@
order by r.create_time desc
limit 1
</select>
<select id="listStatByScenic" resultType="com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO">
SELECT cs1 AS cameraShotOfMemberNum, v1 AS totalVisitorOfMemberNum, sv1 AS scanCodeVisitorOfMemberNum, u1 AS uploadFaceOfMemberNum, m1 AS pushOfMemberNum, gv1 AS completeVideoOfMemberNum, gv2 AS completeOfVideoNum, pv1 AS previewVideoOfMemberNum, pv2 AS previewOfVideoNum, cp1 AS clickOnPayOfMemberNum, o1 AS payOfMemberNum, o2 AS payOfOrderNum, o3 AS payOfOrderAmount, ro2 AS refundOfOrderNum, ro3 AS refundOfOrderAmount
FROM scenic_stats
WHERE scenic_id = #{scenicId}
AND date BETWEEN #{startTime} AND #{endTime}
</select>
<insert id="insertStat">
REPLACE INTO scenic_stats (scenic_id, date, cs1, v1, sv1, u1, m1, gv1, gv2, pv1, pv2, cp1, o1, o2, o3, ro2, ro3)
VALUES (#{scenicId}, #{date}, #{data.cameraShotOfMemberNum}, #{data.totalVisitorOfMemberNum}, #{data.scanCodeVisitorOfMemberNum}, #{data.uploadFaceOfMemberNum}, #{data.pushOfMemberNum}, #{data.completeVideoOfMemberNum}, #{data.completeOfVideoNum}, #{data.previewVideoOfMemberNum}, #{data.previewOfVideoNum}, #{data.clickOnPayOfMemberNum}, #{data.payOfMemberNum}, #{data.payOfOrderNum}, #{data.payOfOrderAmount}, #{data.refundOfOrderNum}, #{data.refundOfOrderAmount})
</insert>
</mapper>

View File

@ -1,24 +0,0 @@
package com.ycwl.basic.task;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ScenicDeviceStatsTaskTest {
@Autowired
private ScenicDeviceStatsTask task;
@Test
public void testA() {
task.countDeviceStats();
}
}

View File

@ -0,0 +1,60 @@
package com.ycwl.basic.task;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.mapper.ScenicMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
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.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Calendar;
import java.util.List;
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ScenicStatsTaskTest {
@Autowired
private ScenicStatsTask task;
@Autowired
private ScenicMapper scenicMapper;
@Autowired
private AppStatisticsService statisticsService;
@Autowired
private StatisticsMapper statisticsMapper;
@Test
public void testA() {
task.countDeviceStats();
}
@Test
public void testB() {
Calendar calendar = Calendar.getInstance();
calendar.set(2025, Calendar.MAY, 1);
List<ScenicRespVO> list = scenicMapper.list(new ScenicReqQuery());
while (calendar.getTime().getTime() < System.currentTimeMillis()) {
System.out.println(calendar.getTime());
list.forEach((scenic) -> {
CommonQueryReq query = new CommonQueryReq();
query.setScenicId(scenic.getId());
query.setStartTime(DateUtil.beginOfDay(calendar.getTime()));
query.setEndTime(DateUtil.endOfDay(calendar.getTime()));
ApiResponse<AppStatisticsFunnelVO> resp = statisticsService.userConversionFunnel(query);
AppStatisticsFunnelVO data = resp.getData();
statisticsMapper.insertStat(scenic.getId(), DateUtil.beginOfDay(calendar.getTime()), data);
});
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
}
}