Compare commits

..

1 Commits

Author SHA1 Message Date
8a17392ae5 Jenkinsfile
Some checks are pending
Gitea/FrameTour-BE/pipeline/head Build queued...
2025-06-30 17:51:34 +08:00
1148 changed files with 9500 additions and 98386 deletions

5
.gitignore vendored
View File

@@ -1,8 +1,3 @@
.idea/
logs/
target/
.*
.claude
.vscode
*.jpg
!.gitignore

View File

@@ -1,27 +0,0 @@
# Repository Guidelines
## Build, Test, and Development Commands
- Build artifact: `mvn clean package` (tests are skipped by default via `pom.xml`).
- Run locally (dev): `mvn spring-boot:run -Dspring-boot.run.profiles=dev`.
- Run jar: `java -jar target/basic21-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev`.
- Execute all tests: `mvn -DskipTests=false test` (note: `pom.xml` excludes `**/*Test.java` from test-compile; temporarily remove/override that config if you need to compile and run tests).
- Run single test: `mvn -DskipTests=false test -Dtest=ClassNameTest` (after removing testExcludes from maven-compiler-plugin).
## Code Style Guidelines
- Java 21. Use 4-space indentation; UTF-8; no wildcard imports.
- Packages: `com.ycwl.basic.*`; classes PascalCase; methods/fields camelCase; constants UPPER_SNAKE_CASE.
- Controllers in `controller`, business logic in `service`, persistence in `mapper` + `resources/mapper/*.xml`.
- Prefer Lombok for boilerplate and constructor injection where applicable.
- Error handling: Use custom exceptions in `exception` package; proper logging with SLF4J.
- Testing: Spring Boot testing + JUnit; test names end with `Test` or `Tests` and mirror package structure.
## Project Structure
- Application code: `src/main/java/com/ycwl/basic/**` (controllers, services, mapper/repository, dto/model, config, util).
- Resources: `src/main/resources/**` (Spring configs, `mapper/*.xml`, static assets, logging).
- Tests: `src/test/java/**` mirrors main packages.
- Build output: `target/` (never commit).
## Agent-Specific Notes
- Keep changes minimal and within existing package boundaries.
- Do not reorganize MyBatis XML names or mapper interfaces without updating both sides.
- If altering APIs, update affected tests and documentation in the same PR.

View File

@@ -1,27 +0,0 @@
# Repository Guidelines
## Build, Test, and Development Commands
- Build artifact: `mvn clean package` (tests are skipped by default via `pom.xml`).
- Run locally (dev): `mvn spring-boot:run -Dspring-boot.run.profiles=dev`.
- Run jar: `java -jar target/basic21-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev`.
- Execute all tests: `mvn -DskipTests=false test` (note: `pom.xml` excludes `**/*Test.java` from test-compile; temporarily remove/override that config if you need to compile and run tests).
- Run single test: `mvn -DskipTests=false test -Dtest=ClassNameTest` (after removing testExcludes from maven-compiler-plugin).
## Code Style Guidelines
- Java 21. Use 4-space indentation; UTF-8; no wildcard imports.
- Packages: `com.ycwl.basic.*`; classes PascalCase; methods/fields camelCase; constants UPPER_SNAKE_CASE.
- Controllers in `controller`, business logic in `service`, persistence in `mapper` + `resources/mapper/*.xml`.
- Prefer Lombok for boilerplate and constructor injection where applicable.
- Error handling: Use custom exceptions in `exception` package; proper logging with SLF4J.
- Testing: Spring Boot testing + JUnit; test names end with `Test` or `Tests` and mirror package structure.
## Project Structure
- Application code: `src/main/java/com/ycwl/basic/**` (controllers, services, mapper/repository, dto/model, config, util).
- Resources: `src/main/resources/**` (Spring configs, `mapper/*.xml`, static assets, logging).
- Tests: `src/test/java/**` mirrors main packages.
- Build output: `target/` (never commit).
## Agent-Specific Notes
- Keep changes minimal and within existing package boundaries.
- Do not reorganize MyBatis XML names or mapper interfaces without updating both sides.
- If altering APIs, update affected tests and documentation in the same PR.

12
Jenkinsfile vendored
View File

@@ -1,20 +1,20 @@
pipeline {
agent any
tools {
jdk 'openjdk21'
maven 'Default'
environment {
JAVA_HOME = "/opt/openjdk21.0.7"
MAVEN_HOME = "/opt/apache-maven-3.9.9"
PATH = "${env.JAVA_HOME}/bin:${env.MAVEN_HOME}/bin:${env.PATH}"
}
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests=true'
bat 'mvn clean package -DskipTests=true'
}
}
}
post {
always {
archiveArtifacts artifacts: 'target/*.jar', allowEmptyArchive: true, onlyIfSuccessful: true
publishGiteaAssets assets: 'target/*.jar', followSymlinks: false, onlyIfSuccessful: true
archiveArtifacts artifacts: 'target/*.jar', allowEmptyArchive: false
}
}
}

0
README.md Normal file
View File

153
pom.xml
View File

@@ -19,30 +19,21 @@
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<spring.cloud.alibaba.version>2023.0.1.2</spring.cloud.alibaba.version>
<spring.cloud.version>2023.0.3</spring.cloud.version>
<hutool-all.version>5.8.24</hutool-all.version>
<mysql-connector.version>8.0.33</mysql-connector.version>
<fastjson.version>1.2.83</fastjson.version>
<knife4j-spring-boot-starter.version>2.0.7</knife4j-spring-boot-starter.version>
<pagehelper.version>5.3.1</pagehelper.version>
<!--跳过单元测试-->
<skipTests>true</skipTests>
</properties>
<!-- Dependency Management -->
<!-- OpenTelemetry -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.16.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -75,47 +66,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Nacos服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 引入redis,并且redis使用jedis连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -124,9 +74,9 @@
<!-- 引入mysql连接 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
<scope>runtime</scope>
</dependency>
@@ -173,30 +123,24 @@
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!-- Jackson JSON处理库 -->
<!-- json处理工具 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- 引入commons-lang3 工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
<!-- 引入接口文档工具 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j-spring-boot-starter.version}</version>
</dependency>
<!-- pageHelper -->
@@ -225,7 +169,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.11.0</version>
<version>1.1</version>
</dependency>
<!-- 阿里云对象存储 -->
@@ -266,50 +210,9 @@
<version>4.16.19</version>
</dependency>
<!-- 阿里云媒体处理 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>mts20140618</artifactId>
<version>5.0.0</version>
</dependency>
<!-- 智谱AI SDK -->
<dependency>
<groupId>ai.z.openapi</groupId>
<artifactId>zai-sdk</artifactId>
<version>0.1.3</version>
</dependency>
<!-- Spring Kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Caffeine Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- Apache POI - 处理Excel文件 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.4.0</version>
</dependency>
<!-- ClickHouse JDBC Driver -->
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.8.5</version>
<classifier>all</classifier>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
</dependencies>
@@ -338,6 +241,16 @@
<skip>${skipTests}</skip>
</configuration>
</plugin>
<!-- 跳过测试编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<testExcludes>
<testExclude>**/*Test.java</testExclude>
</testExcludes>
</configuration>
</plugin>
</plugins>
</build>

View File

@@ -1,13 +1,12 @@
package com.ycwl.basic;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan(basePackages = "com.ycwl.basic.mapper")
@MapperScan(basePackages = "com.ycwl.basic.*.mapper")
public class Application {
public static void main(String[] args) {

View File

@@ -1,6 +1,6 @@
package com.ycwl.basic.aspectj;
import com.ycwl.basic.utils.JacksonUtil;
import com.alibaba.fastjson.JSON;
import com.ycwl.basic.annotation.IgnoreLogReq;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -67,10 +67,10 @@ public class RequestParameterAspectj {
}
if (parameterUrlMap.isEmpty()) {
LOGGER.info("当前请求的路径为-> {} 请求方式为-> {} 参数为-> {}", requestURI, method, JacksonUtil.toJSONString(parameterValueSet));
LOGGER.info("当前请求的路径为-> {} 请求方式为-> {} 参数为-> {}", requestURI, method, JSON.toJSONString(parameterValueSet));
} else {
LOGGER.info("当前请求的路径为-> {} 请求方式为-> {} 参数为-> {} 路径传参为-> {}", requestURI, method,
JacksonUtil.toJSONString(parameterValueSet), JacksonUtil.toJSONString(parameterUrlMap));
JSON.toJSONString(parameterValueSet), JSON.toJSONString(parameterUrlMap));
}
}
return joinPoint.proceed();

View File

@@ -1,14 +1,13 @@
package com.ycwl.basic.biz;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.mapper.BrokerMapper;
import com.ycwl.basic.mapper.BrokerRecordMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.pc.broker.entity.BrokerRecord;
import com.ycwl.basic.model.pc.broker.resp.BrokerRespVO;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.repository.ScenicRepository;
import lombok.extern.slf4j.Slf4j;
@@ -35,7 +34,7 @@ public class BrokerBiz {
@Autowired
private ScenicRepository scenicRepository;
@Autowired
private StatsQueryService statsQueryService;
private StatisticsMapper statisticsMapper;
public void processOrder(Long orderId) {
log.info("开始处理订单分佣,订单ID:{}", orderId);
@@ -44,16 +43,16 @@ public class BrokerBiz {
log.info("订单不存在,订单ID:{}", orderId);
return;
}
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(order.getScenicId());
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(order.getScenicId());
if (scenicConfig == null) {
log.info("景区不存在,订单ID:{}", orderId);
return;
}
int expireDay = 3;
if (scenicConfig.getInteger("sample_store_day") != null) {
expireDay = scenicConfig.getInteger("sample_store_day");
if (scenicConfig.getSampleStoreDay() != null) {
expireDay = scenicConfig.getSampleStoreDay();
}
List<Long> brokerIdList = statsQueryService.getBrokerIdListForUser(order.getMemberId(), DateUtil.offsetDay(DateUtil.beginOfDay(order.getCreateAt()), -expireDay), order.getCreateAt());
List<Long> brokerIdList = statisticsMapper.getBrokerIdListForUser(order.getMemberId(), DateUtil.offsetDay(DateUtil.beginOfDay(order.getCreateAt()), -expireDay), order.getCreateAt());
if (brokerIdList == null || brokerIdList.isEmpty()) {
log.info("用户与推客无关,订单ID:{}", orderId);
return;
@@ -104,7 +103,7 @@ public class BrokerBiz {
BigDecimal realRate = broker.getBrokerRate();
BigDecimal brokerPrice = order.getPayPrice().multiply(realRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
// todo 需要计算实际提成比例
BigDecimal firstRate = scenicConfig.getBigDecimal("broker_direct_rate");
BigDecimal firstRate = scenicConfig.getBrokerDirectRate();
if (firstRate == null) {
firstRate = BigDecimal.ZERO;
}

View File

@@ -0,0 +1,91 @@
package com.ycwl.basic.biz;
import com.ycwl.basic.mapper.CouponMapper;
import com.ycwl.basic.mapper.CouponRecordMapper;
import com.ycwl.basic.model.pc.coupon.entity.CouponEntity;
import com.ycwl.basic.model.pc.couponRecord.entity.CouponRecordEntity;
import com.ycwl.basic.model.pc.couponRecord.resp.CouponRecordQueryResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Component
public class CouponBiz {
@Autowired
private CouponMapper couponMapper;
@Autowired
private CouponRecordMapper couponRecordMapper;
public CouponRecordQueryResp queryUserCouponRecord(Long scenicId, Long memberId, Long faceId, String goodsId) {
CouponRecordQueryResp resp = new CouponRecordQueryResp();
List<CouponRecordEntity> recordList = couponRecordMapper.queryByUserWithGoodsId(scenicId, memberId, goodsId);
if (recordList != null && !recordList.isEmpty()) {
Optional<CouponRecordEntity> record = recordList.stream().filter(item -> item.getStatus() == 0).filter(item -> item.getFaceId() == null || item.getFaceId().equals(faceId)).findAny();
if (record.isPresent()) {
CouponRecordEntity recordEntity = record.get();
resp.setExist(true);
resp.setId(recordEntity.getId());
resp.setCouponId(recordEntity.getCouponId());
CouponEntity coupon = couponMapper.getById(recordEntity.getCouponId());
if (coupon != null) {
resp.setMemberId(recordEntity.getMemberId());
resp.setFaceId(recordEntity.getFaceId());
resp.setStatus(recordEntity.getStatus());
resp.setCreateTime(recordEntity.getCreateTime());
resp.setUsedTime(recordEntity.getUsedTime());
resp.setUsedOrderId(recordEntity.getUsedOrderId());
resp.setCoupon(coupon);
} else {
resp.setExist(false);
}
} else {
Optional<CouponRecordEntity> usedRecord = recordList.stream().filter(item -> item.getStatus() != 0).filter(item -> item.getFaceId() == null || item.getFaceId().equals(faceId)).findAny();
if (usedRecord.isPresent()) {
CouponRecordEntity recordEntity = usedRecord.get();
resp.setExist(true);
resp.setId(recordEntity.getId());
resp.setCouponId(recordEntity.getCouponId());
CouponEntity coupon = couponMapper.getById(recordEntity.getCouponId());
if (coupon != null) {
resp.setMemberId(recordEntity.getMemberId());
resp.setFaceId(recordEntity.getFaceId());
resp.setStatus(recordEntity.getStatus());
resp.setCreateTime(recordEntity.getCreateTime());
resp.setUsedTime(recordEntity.getUsedTime());
resp.setUsedOrderId(recordEntity.getUsedOrderId());
resp.setCoupon(coupon);
} else {
resp.setExist(false);
}
}
}
}
return resp;
}
public boolean userGetCoupon(Long memberId, Long faceId, Integer couponId) {
CouponEntity coupon = couponMapper.getById(couponId);
if (coupon == null) {
return false;
}
CouponRecordEntity entity = new CouponRecordEntity();
entity.setCouponId(couponId);
entity.setFaceId(faceId);
entity.setMemberId(memberId);
entity.setStatus(0);
entity.setCreateTime(new Date());
return couponRecordMapper.insert(entity) > 0;
}
public boolean userUseCoupon(Long memberId, Long faceId, Integer couponRecordId, Long orderId) {
CouponRecordEntity entity = new CouponRecordEntity();
entity.setId(couponRecordId);
entity.setStatus(1);
entity.setUsedTime(new Date());
entity.setUsedOrderId(orderId);
return couponRecordMapper.updateById(entity) > 0;
}
}

View File

@@ -1,384 +0,0 @@
package com.ycwl.basic.biz;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ycwl.basic.enums.FaceCutStatus;
import com.ycwl.basic.enums.FacePieceUpdateStatus;
import com.ycwl.basic.enums.TemplateRenderStatus;
import com.ycwl.basic.mapper.TaskMapper;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 人脸状态缓存管理器
* 统一管理人脸相关的内存缓存状态(使用Caffeine)
*/
@Slf4j
@Component
public class FaceStatusManager {
/**
* 默认过期时间:1小时
*/
private static final long DEFAULT_EXPIRE_SECONDS = 3600L;
/**
* 人脸切片状态缓存
*/
private final Cache<String, Integer> faceCutStatusCache;
/**
* 人脸片段更新状态缓存(全局和模板级)
* 键存在=无新片段,键不存在=有新片段
*/
private final Cache<String, Boolean> faceNoPieceUpdateCache;
/**
* 人脸模板渲染状态缓存
*/
private final Cache<String, Integer> templateRenderCache;
/**
* 拼图素材版本缓存
* 键:faceId:puzzleTemplateId -> 当时的图片源数量
* 用于判断拼图模板的素材是否发生变化,避免重复生成
*/
private final Cache<String, Integer> puzzleSourceVersionCache;
@Autowired
private TaskMapper taskMapper;
public FaceStatusManager() {
// 初始化三个独立的缓存实例
this.faceCutStatusCache = Caffeine.newBuilder()
.expireAfterWrite(DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS)
.maximumSize(10000)
.build();
this.faceNoPieceUpdateCache = Caffeine.newBuilder()
.expireAfterWrite(DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS)
.maximumSize(10000)
.build();
this.templateRenderCache = Caffeine.newBuilder()
.expireAfterWrite(DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS)
.maximumSize(10000)
.build();
this.puzzleSourceVersionCache = Caffeine.newBuilder()
.expireAfterWrite(DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS)
.maximumSize(10000)
.build();
}
// ==================== 切片状态相关方法 ====================
/**
* 设置人脸切片状态
* @param faceId 人脸ID
* @param status 切片状态
*/
public void setFaceCutStatus(Long faceId, FaceCutStatus status) {
if (faceId == null || status == null) {
log.warn("设置切片状态参数为空: faceId={}, status={}", faceId, status);
return;
}
faceCutStatusCache.put(String.valueOf(faceId), status.getCode());
log.debug("设置切片状态: faceId={}, status={}", faceId, status.getDescription());
}
/**
* 获取人脸切片状态
* @param faceId 人脸ID
* @return 切片状态,缓存不存在时返回 COMPLETED(已完成)
*/
public FaceCutStatus getFaceCutStatus(Long faceId) {
if (faceId == null) {
log.warn("获取切片状态参数为空: faceId={}", faceId);
return FaceCutStatus.COMPLETED;
}
Integer code = faceCutStatusCache.getIfPresent(String.valueOf(faceId));
if (code == null) {
log.debug("切片状态缓存不存在,返回默认值COMPLETED: faceId={}", faceId);
return FaceCutStatus.COMPLETED;
}
return FaceCutStatus.fromCodeOrDefault(code, FaceCutStatus.COMPLETED);
}
/**
* 删除人脸切片状态缓存
* @param faceId 人脸ID
*/
public void deleteFaceCutStatus(Long faceId) {
if (faceId == null) {
return;
}
faceCutStatusCache.invalidate(String.valueOf(faceId));
log.debug("删除切片状态缓存: faceId={}", faceId);
}
// ==================== 片段更新状态相关方法 ====================
/**
* 标记无新片段(设置缓存键)
* @param faceId 人脸ID
* @param templateId 模板ID(可选,为null时标记全局状态)
*/
public void markNoNewPieces(Long faceId, Long templateId) {
if (faceId == null) {
log.warn("标记无新片段参数为空: faceId={}", faceId);
return;
}
if (templateId == null) {
// 全局标记:该人脸的所有模板都无新片段
faceNoPieceUpdateCache.put(String.valueOf(faceId), Boolean.TRUE);
log.debug("标记无新片段(全局): faceId={}", faceId);
} else {
// 模板级标记:该人脸在该模板下无新片段
faceNoPieceUpdateCache.put(faceId + ":" + templateId, Boolean.TRUE);
log.debug("标记无新片段(模板): faceId={}, templateId={}", faceId, templateId);
}
}
/**
* 标记有新片段(删除缓存键)
* @param faceId 人脸ID
* @param templateId 模板ID(可选,为null时标记全局状态)
*/
public void markHasNewPieces(Long faceId, Long templateId) {
if (faceId == null) {
log.warn("标记有新片段参数为空: faceId={}", faceId);
return;
}
if (templateId == null) {
// 全局标记:该人脸有新片段
faceNoPieceUpdateCache.invalidate(String.valueOf(faceId));
log.debug("标记有新片段(全局): faceId={}", faceId);
} else {
// 模板级标记:该人脸在该模板下有新片段
faceNoPieceUpdateCache.invalidate(faceId + ":" + templateId);
log.debug("标记有新片段(模板): faceId={}, templateId={}", faceId, templateId);
}
}
/**
* 获取人脸片段更新状态
* @param faceId 人脸ID
* @param templateId 模板ID(可选,为null时查询全局状态)
* @return 片段更新状态,键存在=无新片段,键不存在=有新片段
*/
public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId, Long templateId) {
if (faceId == null) {
log.warn("获取片段更新状态参数为空: faceId={}", faceId);
return FacePieceUpdateStatus.HAS_NEW_PIECES;
}
String key = templateId == null ? String.valueOf(faceId) : faceId + ":" + templateId;
boolean exists = faceNoPieceUpdateCache.getIfPresent(key) != null;
FacePieceUpdateStatus status = FacePieceUpdateStatus.fromKeyExists(exists);
if (templateId == null) {
log.debug("获取片段更新状态(全局): faceId={}, status={}", faceId, status.getDescription());
} else {
log.debug("获取片段更新状态(模板): faceId={}, templateId={}, status={}",
faceId, templateId, status.getDescription());
}
return status;
}
/**
* 获取人脸片段更新状态 - 全局版本
* @param faceId 人脸ID
* @return 片段更新状态,键存在=无新片段,键不存在=有新片段
*/
public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId) {
return getFacePieceUpdateStatus(faceId, null);
}
/**
* 判断是否有新片段
* @param faceId 人脸ID
* @param templateId 模板ID(可选,为null时查询全局状态)
* @return true=有新片段,false=无新片段;如果templateId为null则默认返回true(有新片段)
*/
public boolean hasNewPieces(Long faceId, Long templateId) {
if (templateId == null) {
// 如果没有指定templateId,默认认为有新片段
log.debug("未指定templateId,默认返回有新片段: faceId={}", faceId);
return true;
}
return getFacePieceUpdateStatus(faceId, templateId).hasNewPieces();
}
/**
* 判断是否有新片段 - 全局版本
* @param faceId 人脸ID
* @return true=有新片段,false=无新片段
*/
public boolean hasNewPieces(Long faceId) {
return getFacePieceUpdateStatus(faceId, null).hasNewPieces();
}
// ==================== 模板渲染状态相关方法 ====================
/**
* 设置人脸模板渲染状态
* @param faceId 人脸ID
* @param templateId 模板ID
* @param status 渲染状态
*/
public void setTemplateRenderStatus(Long faceId, Long templateId, TemplateRenderStatus status) {
if (faceId == null || templateId == null || status == null) {
log.warn("设置模板渲染状态参数为空: faceId={}, templateId={}, status={}", faceId, templateId, status);
return;
}
templateRenderCache.put(faceId + ":" + templateId, status.getCode());
log.debug("设置模板渲染状态: faceId={}, templateId={}, status={}", faceId, templateId, status.getDescription());
}
/**
* 获取人脸模板渲染状态
* @param faceId 人脸ID
* @param templateId 模板ID
* @return 渲染状态,缓存不存在时返回 null
*/
public TemplateRenderStatus getTemplateRenderStatus(Long faceId, Long templateId) {
if (faceId == null || templateId == null) {
log.warn("获取模板渲染状态参数为空: faceId={}, templateId={}", faceId, templateId);
return null;
}
Integer code = templateRenderCache.getIfPresent(faceId + ":" + templateId);
if (code == null) {
log.debug("模板渲染状态缓存不存在: faceId={}, templateId={}", faceId, templateId);
// 查询数据库
TaskEntity task = taskMapper.listLastFaceTemplateTask(faceId, templateId);
if (task == null) {
setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
return TemplateRenderStatus.NONE;
}
if (Integer.valueOf(2).equals(task.getStatus())) {
setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERING);
}
if (Integer.valueOf(1).equals(task.getStatus())) {
setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERED);
}
return TemplateRenderStatus.NONE;
}
return TemplateRenderStatus.fromCode(code);
}
/**
* 删除人脸模板渲染状态缓存
* @param faceId 人脸ID
* @param templateId 模板ID
*/
public void deleteTemplateRenderStatus(Long faceId, Long templateId) {
if (faceId == null || templateId == null) {
return;
}
templateRenderCache.invalidate(faceId + ":" + templateId);
log.debug("删除模板渲染状态缓存: faceId={}, templateId={}", faceId, templateId);
}
/**
* 删除人脸的所有模板渲染状态(使用模式匹配)
* 注意:此操作可能影响性能,谨慎使用
* @param faceId 人脸ID
*/
public void deleteAllTemplateRenderStatus(Long faceId) {
if (faceId == null) {
return;
}
String prefix = faceId + ":";
long count = templateRenderCache.asMap().keySet().stream()
.filter(key -> key.startsWith(prefix))
.peek(templateRenderCache::invalidate)
.count();
if (count > 0) {
log.debug("批量删除模板渲染状态缓存: faceId={}, count={}", faceId, count);
}
}
// ==================== 拼图素材版本相关方法 ====================
/**
* 标记拼图素材版本(记录当前的图片源数量)
* 在拼图生成成功后调用,用于后续判断素材是否变化
*
* @param faceId 人脸ID
* @param puzzleTemplateId 拼图模板ID(全局唯一)
* @param sourceCount 当前的图片源数量
*/
public void markPuzzleSourceVersion(Long faceId, Long puzzleTemplateId, int sourceCount) {
if (faceId == null || puzzleTemplateId == null) {
log.warn("标记拼图素材版本参数为空: faceId={}, puzzleTemplateId={}", faceId, puzzleTemplateId);
return;
}
String key = faceId + ":" + puzzleTemplateId;
puzzleSourceVersionCache.put(key, sourceCount);
log.debug("标记拼图素材版本: faceId={}, puzzleTemplateId={}, sourceCount={}", faceId, puzzleTemplateId, sourceCount);
}
/**
* 判断拼图素材是否发生变化
* 通过比较当前的图片源数量与缓存中记录的数量
*
* @param faceId 人脸ID
* @param puzzleTemplateId 拼图模板ID(全局唯一)
* @param currentSourceCount 当前的图片源数量
* @return true=素材已变化(需要重新生成),false=素材未变化(可以跳过生成)
*/
public boolean isPuzzleSourceChanged(Long faceId, Long puzzleTemplateId, int currentSourceCount) {
if (faceId == null || puzzleTemplateId == null) {
log.warn("判断拼图素材变化参数为空: faceId={}, puzzleTemplateId={}", faceId, puzzleTemplateId);
return true; // 参数不合法时默认认为有变化
}
String key = faceId + ":" + puzzleTemplateId;
Integer cachedCount = puzzleSourceVersionCache.getIfPresent(key);
if (cachedCount == null) {
// 缓存不存在,认为有变化(首次生成或缓存过期)
log.debug("拼图素材版本缓存不存在,需要生成: faceId={}, puzzleTemplateId={}", faceId, puzzleTemplateId);
return true;
}
boolean changed = !cachedCount.equals(currentSourceCount);
if (changed) {
log.debug("拼图素材已变化: faceId={}, puzzleTemplateId={}, cachedCount={}, currentCount={}",
faceId, puzzleTemplateId, cachedCount, currentSourceCount);
} else {
log.debug("拼图素材未变化,可跳过生成: faceId={}, puzzleTemplateId={}, sourceCount={}",
faceId, puzzleTemplateId, currentSourceCount);
}
return changed;
}
/**
* 使指定人脸的所有拼图素材版本缓存失效
* 当人脸的图片关联发生变化时调用(如人脸匹配后新增了关联)
*
* @param faceId 人脸ID
*/
public void invalidatePuzzleSourceVersion(Long faceId) {
if (faceId == null) {
return;
}
String prefix = faceId + ":";
long count = puzzleSourceVersionCache.asMap().keySet().stream()
.filter(key -> key.startsWith(prefix))
.peek(puzzleSourceVersionCache::invalidate)
.count();
if (count > 0) {
log.debug("批量使拼图素材版本缓存失效: faceId={}, count={}", faceId, count);
}
}
}

View File

@@ -1,24 +1,29 @@
package com.ycwl.basic.biz;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.mapper.OrderMapper;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.order.PriceObj;
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
import com.ycwl.basic.model.pc.coupon.entity.CouponEntity;
import com.ycwl.basic.model.pc.couponRecord.resp.CouponRecordQueryResp;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.entity.OrderItemEntity;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.order.resp.OrderAppRespVO;
import com.ycwl.basic.model.pc.order.resp.OrderItemVO;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.pricing.dto.PriceCalculationRequest;
import com.ycwl.basic.pricing.dto.PriceCalculationResult;
import com.ycwl.basic.pricing.dto.ProductItem;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.service.IPriceCalculationService;
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
import com.ycwl.basic.profitsharing.biz.ProfitSharingBiz;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.OrderRepository;
@@ -28,18 +33,23 @@ import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Component
public class OrderBiz {
@Autowired
private VideoMapper videoMapper;
@Autowired
private ScenicRepository scenicRepository;
@Autowired
@@ -57,26 +67,28 @@ public class OrderBiz {
@Autowired
private OrderMapper orderMapper;
@Autowired
private SourceMapper sourceMapper;
@Autowired
private ProfitSharingBiz profitSharingBiz;
@Autowired
private VideoTaskRepository videoTaskRepository;
@Autowired
private BrokerBiz brokerBiz;
@Autowired
private CouponBiz couponBiz;
@Autowired
@Lazy
private PrinterService printerService;
@Autowired
private IPriceCalculationService iPriceCalculationService;
@Autowired
private StatsQueryService statsQueryService;
public PriceObj queryPrice(Long scenicId, Long memberId, int goodsType, Long goodsId) {
public PriceObj queryPrice(Long scenicId, int goodsType, Long goodsId) {
PriceObj priceObj = new PriceObj();
priceObj.setGoodsType(goodsType);
priceObj.setGoodsId(goodsId);
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
ScenicEntity scenic = scenicRepository.getScenic(scenicId);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
priceObj.setScenicAllPrice(scenic.getPrice());
if (scenicConfig != null) {
if (Boolean.TRUE.equals(scenicConfig.getBoolean("all_free"))) {
if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) {
// 景区全免
priceObj.setFree(true);
priceObj.setPrice(BigDecimal.ZERO);
@@ -90,92 +102,39 @@ public class OrderBiz {
if (video == null) {
return null;
}
priceObj.setFaceId(video.getFaceId());
TaskEntity task = videoTaskRepository.getTaskById(video.getTaskId());
if (task != null) {
priceObj.setFaceId(task.getFaceId());
}
TemplateRespVO template = templateRepository.getTemplate(video.getTemplateId());
if (template == null) {
return priceObj;
}
PriceCalculationRequest vlogCalculationRequest = new PriceCalculationRequest();
ProductItem vlogProductItem = new ProductItem();
vlogProductItem.setProductType(ProductType.VLOG_VIDEO);
vlogProductItem.setProductId(template.getId().toString());
vlogProductItem.setQuantity(videoTaskRepository.getTaskLensNum(video.getTaskId()));
vlogProductItem.setScenicId(scenicId.toString());
vlogCalculationRequest.setProducts(Collections.singletonList(vlogProductItem));
vlogCalculationRequest.setUserId(memberId);
vlogCalculationRequest.setFaceId(priceObj.getFaceId());
vlogCalculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
vlogCalculationRequest.setAutoUseCoupon(true);
PriceCalculationResult vlogCalculationResult = iPriceCalculationService.calculatePrice(vlogCalculationRequest);
priceObj.setPrice(vlogCalculationResult.getFinalAmount());
priceObj.setSlashPrice(vlogCalculationResult.getOriginalAmount());
priceObj.setFaceId(goodsId);
priceObj.setPrice(template.getPrice());
BigDecimal slashPrice = template.getSlashPrice();
if (slashPrice == null) {
priceObj.setSlashPrice(priceObj.getPrice());
} else {
priceObj.setSlashPrice(slashPrice);
}
priceObj.setScenicId(video.getScenicId());
break;
case 1: // source
priceObj.setPrice(scenic.getSourceVideoPrice());
priceObj.setSlashPrice(scenic.getSourceVideoPrice());
priceObj.setFaceId(goodsId);
break;
case 2: // source
FaceEntity face = faceRepository.getFace(goodsId);
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(goodsType == 1 ? ProductType.RECORDING_SET : ProductType.PHOTO_SET);
productItem.setProductId(scenicId.toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(scenicId.toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
if (face != null) {
calculationRequest.setUserId(face.getMemberId());
}
calculationRequest.setUserId(memberId);
calculationRequest.setFaceId(goodsId);
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
calculationRequest.setAutoUseCoupon(true);
PriceCalculationResult priceCalculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
priceObj.setPrice(priceCalculationResult.getFinalAmount());
priceObj.setSlashPrice(priceCalculationResult.getOriginalAmount());
priceObj.setPrice(scenic.getSourceImagePrice());
priceObj.setSlashPrice(scenic.getSourceImagePrice());
priceObj.setFaceId(goodsId);
break;
case 5:
PriceCalculationRequest plogCalculationRequest = new PriceCalculationRequest();
ProductItem plogProductItem = new ProductItem();
plogProductItem.setProductType(ProductType.PHOTO_LOG);
plogProductItem.setProductId(scenicId.toString());
plogProductItem.setPurchaseCount(1);
plogProductItem.setScenicId(scenicId.toString());
plogCalculationRequest.setProducts(Collections.singletonList(plogProductItem));
plogCalculationRequest.setUserId(memberId);
plogCalculationRequest.setFaceId(goodsId);
plogCalculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
plogCalculationRequest.setAutoUseCoupon(true);
PriceCalculationResult plogPriceCalculationResult = iPriceCalculationService.calculatePrice(plogCalculationRequest);
priceObj.setPrice(plogPriceCalculationResult.getFinalAmount());
priceObj.setSlashPrice(plogPriceCalculationResult.getOriginalAmount());
priceObj.setFaceId(goodsId);
priceObj.setScenicId(scenicId);
break;
case 13:
PriceCalculationRequest aiCamCalculationRequest = new PriceCalculationRequest();
ProductItem aiCamProductItem = new ProductItem();
aiCamProductItem.setProductType(ProductType.AI_CAM_PHOTO_SET);
aiCamProductItem.setProductId(scenicId.toString());
aiCamProductItem.setPurchaseCount(1);
aiCamProductItem.setScenicId(scenicId.toString());
aiCamCalculationRequest.setProducts(Collections.singletonList(aiCamProductItem));
aiCamCalculationRequest.setUserId(memberId);
aiCamCalculationRequest.setFaceId(goodsId);
aiCamCalculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
aiCamCalculationRequest.setAutoUseCoupon(true);
PriceCalculationResult aiCamPriceCalculationResult = iPriceCalculationService.calculatePrice(aiCamCalculationRequest);
priceObj.setPrice(aiCamPriceCalculationResult.getFinalAmount());
priceObj.setSlashPrice(aiCamPriceCalculationResult.getOriginalAmount());
priceObj.setFaceId(goodsId);
priceObj.setScenicId(scenicId);
break;
}
return priceObj;
}
public OrderEntity hasTypeOrder(Long userId, Long faceId, Long scenicId, int orderType, Integer configId) {
OrderEntity orderEntity = orderMapper.queryTypeOrder(userId, faceId, scenicId, orderType, configId);
public OrderEntity hasTypeOrder(Long userId, Long scenicId, int orderType, Integer configId) {
OrderEntity orderEntity = orderMapper.queryTypeOrder(userId, scenicId, orderType, configId);
if (orderEntity == null) {
return null;
}
@@ -185,46 +144,82 @@ public class OrderBiz {
return null;
}
}
public IsBuyRespVO isBuy(Long scenicId, Long memberId, Long faceId, int goodsType, Long goodsId) {
public IsBuyRespVO isBuy(Long userId, Long scenicId, int goodsType, Long goodsId) {
IsBuyRespVO respVO = new IsBuyRespVO();
respVO.setGoodsType(goodsType);
respVO.setGoodsId(goodsId);
OrderEntity orderEntity = orderMapper.getUserBuyFaceItem(memberId, faceId, goodsType, goodsId);
if (orderEntity != null) {
respVO.setOrderId(orderEntity.getId());
respVO.setBuy(true);
respVO.setFree(false);
return respVO;
}
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
if (Boolean.TRUE.equals(scenicConfig.getBoolean("all_free"))) {
// 景区全免
respVO.setFree(true);
respVO.setOrigPrice(BigDecimal.ZERO);
respVO.setSlashPrice(BigDecimal.ZERO);
return respVO;
}
// 未来模板一口价
if (goodsType == 0) {
// 视频,可以买断模板
VideoEntity video = videoRepository.getVideo(goodsId);
if (video != null && video.getTemplateId() != null) {
OrderEntity templateBuy = orderMapper.getUserBuyFaceItem(memberId, faceId, -1, video.getTemplateId());
if (templateBuy != null) {
respVO.setOrderId(templateBuy.getId());
respVO.setBuy(true);
respVO.setFree(false);
return respVO;
boolean isBuy = orderRepository.checkUserBuyItem(userId, goodsType, goodsId);
// 模板购买逻辑
if (!isBuy) {
if (goodsType == 0) {
VideoEntity video = videoRepository.getVideo(goodsId);
TaskEntity task = videoTaskRepository.getTaskById(video.getTaskId());
Long templateId = video.getTemplateId();
// -1为整个模板购买
OrderEntity orderEntity = orderRepository.getUserBuyItem(userId, -1, templateId);
if (orderEntity != null && task != null) {
respVO.setOrderId(orderEntity.getId());
if (orderEntity.getFaceId() != null && task.getFaceId() != null) {
isBuy = orderEntity.getFaceId().equals(task.getFaceId());
}
}
}
}
PriceObj priceObj = queryPrice(scenicId, memberId, goodsType, goodsId);
if (priceObj == null) {
return respVO;
// 免费送逻辑,之前已经赠送了的
if (!isBuy) {
isBuy = switch (goodsType) {
case 0 -> videoRepository.getUserIsBuy(userId, goodsId);
case 1, 2 -> sourceRepository.getUserIsBuy(userId, goodsType, goodsId);
default -> false;
};
} else {
OrderEntity orderEntity = orderRepository.getUserBuyItem(userId, goodsType, goodsId);
if (orderEntity != null) {
respVO.setOrderId(orderEntity.getId());
}
}
respVO.setBuy(isBuy);
// 还是没买
if (!isBuy) {
PriceObj priceObj = queryPrice(scenicId, goodsType, goodsId);
if (priceObj == null) {
return respVO;
}
respVO.setFree(priceObj.isFree());
respVO.setGoodsType(goodsType);
respVO.setGoodsId(goodsId);
respVO.setOrigPrice(priceObj.getPrice());
respVO.setSlashPrice(priceObj.getSlashPrice());
switch (goodsType) {
case 0: // vlog
VideoEntity video = videoRepository.getVideo(goodsId);
TaskEntity taskById = videoTaskRepository.getTaskById(video.getTaskId());
if (taskById != null) {
CouponRecordQueryResp recordQueryResp = couponBiz.queryUserCouponRecord(scenicId, userId, taskById.getFaceId(), taskById.getTemplateId().toString());
if (recordQueryResp.isUsable()) {
respVO.setCouponId(recordQueryResp.getCouponId());
respVO.setCouponRecordId(recordQueryResp.getId());
CouponEntity coupon = recordQueryResp.getCoupon();
if (coupon != null) {
respVO.setCouponPrice(coupon.calculateDiscountPrice(priceObj.getPrice()));
}
}
}
break;
case 1:
case 2:
CouponRecordQueryResp recordQueryResp = couponBiz.queryUserCouponRecord(scenicId, userId, goodsId, String.valueOf(goodsType));
if (recordQueryResp.isUsable()) {
respVO.setCouponId(recordQueryResp.getCouponId());
respVO.setCouponRecordId(recordQueryResp.getId());
CouponEntity coupon = recordQueryResp.getCoupon();
if (coupon != null) {
respVO.setCouponPrice(coupon.calculateDiscountPrice(priceObj.getPrice()));
}
}
break;
}
}
respVO.setBuy(false);
respVO.setOrigPrice(priceObj.getPrice());
respVO.setSlashPrice(priceObj.getSlashPrice());
return respVO;
}
@@ -238,26 +233,54 @@ public class OrderBiz {
orderRepository.updateOrder(orderId, orderUpdate);
orderItems.forEach(item -> {
switch (item.getGoodsType()) {
case -1: // vlog视频模板
videoRepository.setUserIsBuyTemplate(order.getMemberId(), item.getGoodsId(), order.getId(), order.getFaceId());
break;
case 0: // vlog视频
videoRepository.setUserIsBuyItem(order.getMemberId(), item.getGoodsId(), order.getId());
break;
case 1: // 视频原素材
case 2: // 照片原素材
case 13: // AI微单
sourceRepository.setUserIsBuyItem(order.getMemberId(), item.getGoodsType(), item.getGoodsId(), order.getId());
break;
case 3:
printerService.setUserIsBuyItem(order.getMemberId(), item.getGoodsId(), order.getId());
break;
}
});
orderRepository.clearOrderCache(orderId); // 更新完了,清理下
if (order.getCouponRecordId() != null) {
couponBiz.userUseCoupon(order.getMemberId(), order.getFaceId(), order.getCouponRecordId(), orderId);
}
//支付时间
OrderAppRespVO orderDetail = orderMapper.appDetail(orderId);
Date payAt = orderDetail.getPayAt();
//商品创建时间
Date goodsCreateTime = new Date();
if (!orderDetail.getOrderItemList().isEmpty()) {
OrderItemVO orderItemVO = orderDetail.getOrderItemList().getFirst();
switch (orderItemVO.getGoodsType()) {
case 0:
VideoEntity video = videoRepository.getVideo(orderItemVO.getGoodsId());
if (video != null) {
goodsCreateTime = video.getCreateTime();
}
break;
case 1:
List<SourceEntity> imageSource = sourceMapper.listImageByFaceRelation(order.getMemberId(), orderItemVO.getGoodsId());
Optional<SourceEntity> min = imageSource.stream().min(Comparator.comparing(SourceEntity::getCreateTime));
if (min.isPresent()) {
goodsCreateTime = min.get().getCreateTime();
}
break;
case 2:
List<SourceEntity> videoSource = sourceMapper.listImageByFaceRelation(order.getMemberId(), orderItemVO.getGoodsId());
Optional<SourceEntity> minTime = videoSource.stream().min(Comparator.comparing(SourceEntity::getCreateTime));
if (minTime.isPresent()) {
goodsCreateTime = minTime.get().getCreateTime();
}
break;
}
}
StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq();
statisticsRecordAddReq.setMemberId(order.getMemberId());
Long enterType = statsQueryService.getUserRecentEnterType(order.getMemberId(), order.getCreateAt());
Long enterType = statisticsMapper.getUserRecentEnterType(order.getMemberId(), order.getCreateAt());
if(!Long.valueOf(1014).equals(enterType)){//
statisticsRecordAddReq.setType(StatisticEnum.ON_SITE_PAYMENT.code);
}else {
@@ -282,11 +305,9 @@ public class OrderBiz {
switch (item.getGoodsType()) {
case 0: // vlog视频
videoRepository.setUserNotBuyItem(order.getMemberId(), item.getGoodsId());
break;
case 1: // 视频原素材
case 2: // 照片原素材
sourceRepository.setUserNotBuyItem(order.getMemberId(), item.getGoodsType(), item.getGoodsId());
break;
}
});
orderRepository.clearOrderCache(orderId); // 更新完了,清理下
@@ -306,15 +327,12 @@ public class OrderBiz {
switch (item.getGoodsType()) {
case 0: // vlog视频
videoRepository.setUserNotBuyItem(order.getMemberId(), item.getGoodsId());
break;
case 1: // 视频原素材
case 2: // 照片原素材
sourceRepository.setUserNotBuyItem(order.getMemberId(), item.getGoodsType(), item.getGoodsId());
break;
}
});
orderRepository.clearOrderCache(orderId); // 更新完了,清理下
profitSharingBiz.revokeProfitSharing(order.getScenicId(), orderId, "订单已退款");
}
}

View File

@@ -1,29 +1,20 @@
package com.ycwl.basic.biz;
import com.ycwl.basic.model.mobile.order.IsBuyBatchRespVO;
import com.ycwl.basic.model.pc.coupon.entity.CouponEntity;
import com.ycwl.basic.model.pc.couponRecord.resp.CouponRecordQueryResp;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.price.entity.PriceConfigEntity;
import com.ycwl.basic.model.pc.price.resp.GoodsListRespVO;
import com.ycwl.basic.model.pc.price.resp.SimpleGoodsRespVO;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.product.capability.ProductTypeCapability;
import com.ycwl.basic.product.service.IProductTypeCapabilityManagementService;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.mapper.PuzzleTemplateMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.MemberRelationRepository;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.repository.PriceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.service.pc.FaceService;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@@ -45,15 +36,7 @@ public class PriceBiz {
@Autowired
private FaceRepository faceRepository;
@Autowired
private MemberRelationRepository memberRelationRepository;
@Autowired
private PuzzleTemplateMapper puzzleTemplateMapper;
@Autowired
private PuzzleRepository puzzleRepository;
@Autowired
private IProductTypeCapabilityManagementService productTypeCapabilityManagementService;
@Autowired
private OrderRepository orderRepository;
private CouponBiz couponBiz;
public List<GoodsListRespVO> listGoodsByScenic(Long scenicId) {
List<GoodsListRespVO> goodsList = new ArrayList<>();
@@ -63,131 +46,17 @@ public class PriceBiz {
GoodsListRespVO goods = new GoodsListRespVO();
goods.setGoodsId(template.getId());
goods.setGoodsName(template.getName());
goods.setGoodsType(0);
return goods;
}).forEach(goodsList::add);
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null) {
if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_video"))) {
goodsList.add(new GoodsListRespVO(1L, "录像集", 1));
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) {
goodsList.add(new GoodsListRespVO(1L, "录像集"));
}
if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"))) {
goodsList.add(new GoodsListRespVO(2L, "照片集", 2));
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) {
goodsList.add(new GoodsListRespVO(2L, "照片集"));
}
}
// 拼图(使用缓存)
puzzleRepository.listTemplateByScenic(scenicId).forEach(puzzleTemplate -> {
GoodsListRespVO goods = new GoodsListRespVO();
goods.setGoodsId(puzzleTemplate.getId());
goods.setGoodsName(puzzleTemplate.getName());
goods.setGoodsType(5);
goodsList.add(goods);
});
return goodsList;
}
/**
* 根据景区ID和商品类型查询简化的商品列表
*
* @param scenicId 景区ID
* @param productType 商品类型(可选,为空时返回所有商品)
* @return 简化的商品列表
*/
public List<SimpleGoodsRespVO> listSimpleGoodsByScenic(Long scenicId, String productType) {
List<SimpleGoodsRespVO> goodsList = new ArrayList<>();
// 如果 productType 为空,兼容旧逻辑
if (productType == null || productType.isEmpty()) {
return listAllSimpleGoods(scenicId);
}
// 根据 productType 查询不同数据源
switch (productType) {
case "VLOG_VIDEO":
// 从 template 表查询视频模板
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(scenicId);
templateList.stream()
.map(template -> new SimpleGoodsRespVO(template.getId(), template.getName(), productType))
.forEach(goodsList::add);
break;
case "PHOTO_VLOG":
// TODO
goodsList.add(new SimpleGoodsRespVO(scenicId, "【待实现】pLog视频", productType));
break;
case "PHOTO":
goodsList.add(new SimpleGoodsRespVO(scenicId, "单张照片", productType));
break;
case "PHOTO_SET":
// 返回固定的照片集条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "照片集", productType));
break;
case "AI_CAM_PHOTO_SET":
// 返回固定的照片集条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "AI微单照片集", productType));
break;
case "PHOTO_LOG":
// 从 template 表查询pLog模板
List<PuzzleTemplateEntity> puzzleList = puzzleRepository.listTemplateByScenic(scenicId);
puzzleList.stream()
.map(template -> new SimpleGoodsRespVO(template.getId(), template.getName(), productType))
.forEach(goodsList::add);
if (!puzzleList.isEmpty()) {
goodsList.addFirst(new SimpleGoodsRespVO(scenicId, "pLog图<景区打包>", productType));
}
break;
case "RECORDING_SET":
// 返回固定的录像集条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "录像集", productType));
break;
case "PHOTO_PRINT":
// 打印类返回单一通用条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "照片打印", productType));
break;
case "PHOTO_PRINT_MU":
// 打印类返回单一通用条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "手机照片打印", productType));
break;
case "PHOTO_PRINT_FX":
// 打印类返回单一通用条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "效果图片打印", productType));
break;
case "MACHINE_PRINT":
// 打印类返回单一通用条目
goodsList.add(new SimpleGoodsRespVO(scenicId, "一体机打印", productType));
break;
default:
// 不支持的 productType,返回空列表
break;
}
return goodsList;
}
/**
* 兼容旧逻辑:返回所有商品
* 通过查询系统中所有已知的 productType,将结果综合到一起
*/
private List<SimpleGoodsRespVO> listAllSimpleGoods(Long scenicId) {
List<SimpleGoodsRespVO> goodsList = new ArrayList<>();
// 从 ProductTypeCapability 服务查询所有已知的商品类型(仅包含启用的)
List<ProductTypeCapability> capabilities = productTypeCapabilityManagementService.queryAll(false);
// 轮询每个商品类型,获取对应的商品列表
for (ProductTypeCapability capability : capabilities) {
String productType = capability.getProductType();
List<SimpleGoodsRespVO> typeGoodsList = listSimpleGoodsByScenic(scenicId, productType);
goodsList.addAll(typeGoodsList);
}
return goodsList;
}
@@ -203,7 +72,7 @@ public class PriceBiz {
String[] goodsIds = priceConfig.getGoodsIds().split(",");
return goodsList.stream().filter(goods -> {
for (String goodsId : goodsIds) {
if (Strings.CS.equals(goods.getGoodsId().toString(), goodsId)) {
if (StringUtils.equals(goods.getGoodsId().toString(), goodsId)) {
return true;
}
}
@@ -211,7 +80,7 @@ public class PriceBiz {
}).collect(Collectors.toList());
}
public IsBuyBatchRespVO isOnePriceBuy(Long userId, Long faceId, Long scenicId, Integer type, String goodsIds) {
public IsBuyBatchRespVO isBuy(Long userId, Long faceId, Long scenicId, Integer type, String goodsIds) {
IsBuyBatchRespVO respVO = new IsBuyBatchRespVO();
PriceConfigEntity priceConfig = priceRepository.getPriceConfigByScenicTypeGoods(scenicId, type, goodsIds);
if (priceConfig == null) {
@@ -221,15 +90,39 @@ public class PriceBiz {
if (face != null && !face.getMemberId().equals(userId)) {
return null;
}
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
if (scenicConfig != null) {
if (Boolean.TRUE.equals(scenicConfig.getBoolean("all_free"))) {
if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) {
// 景区全免
respVO.setFree(true);
respVO.setSlashPrice(BigDecimal.ZERO);
return respVO;
}
}
switch (type) {
case 0: // 单个定价
CouponRecordQueryResp recordQueryResp = couponBiz.queryUserCouponRecord(scenicId, userId, faceId, goodsIds);
if (recordQueryResp.isUsable()) {
respVO.setCouponId(recordQueryResp.getCouponId());
respVO.setCouponRecordId(recordQueryResp.getId());
CouponEntity coupon = recordQueryResp.getCoupon();
if (coupon != null) {
respVO.setCouponPrice(coupon.calculateDiscountPrice(priceConfig.getPrice()));
}
}
break;
case -1:
CouponRecordQueryResp oneCouponRecordQueryResp = couponBiz.queryUserCouponRecord(scenicId, userId, faceId, "-1");
if (oneCouponRecordQueryResp.isUsable()) {
respVO.setCouponId(oneCouponRecordQueryResp.getCouponId());
respVO.setCouponRecordId(oneCouponRecordQueryResp.getId());
CouponEntity coupon = oneCouponRecordQueryResp.getCoupon();
if (coupon != null) {
respVO.setCouponPrice(coupon.calculateDiscountPrice(priceConfig.getPrice()));
}
}
break;
}
respVO.setConfigId(priceConfig.getId());
respVO.setGoodsIds(goodsIds);
respVO.setType(type);
@@ -239,59 +132,12 @@ public class PriceBiz {
// 查询用户是否有此类订单
respVO.setBuy(false);
if (userId != null) {
OrderEntity orderEntity = orderBiz.hasTypeOrder(userId, faceId, scenicId, type, priceConfig.getId());
OrderEntity orderEntity = orderBiz.hasTypeOrder(userId, scenicId, type, priceConfig.getId());
if (orderEntity != null) {
respVO.setOrderId(orderEntity.getId());
respVO.setBuy(Integer.valueOf(1).equals(orderEntity.getStatus()));
}
}
if (type == -1 && !respVO.isBuy()) {
// 直接查询用户购买状态,避免调用faceContentList造成循环调用
boolean allContentsPurchased = true;
// 检查视频模板购买状态
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(scenicId);
for (TemplateRespVO template : templateList) {
// 使用OrderRepository直接检查是否购买了该模板下的内容
List<MemberVideoEntity> videoEntities = memberRelationRepository.listRelationByFaceAndTemplate(faceId, template.getId());
if (videoEntities == null || videoEntities.isEmpty()) {
allContentsPurchased = false;
break;
}
boolean hasPurchasedTemplate = orderRepository.checkUserBuyFaceItem(userId, faceId, -1, videoEntities.getFirst().getVideoId());
if (!hasPurchasedTemplate) {
allContentsPurchased = false;
break;
}
}
// 检查源文件购买状态(录像集和照片集)
if (allContentsPurchased) {
if (scenicConfig != null) {
// 检查录像集
if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_video"))) {
boolean hasPurchasedRecording = orderRepository.checkUserBuyFaceItem(userId, faceId, 1, faceId);
if (!hasPurchasedRecording) {
allContentsPurchased = false;
}
}
// 检查照片集
if (allContentsPurchased && !Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"))) {
boolean hasPurchasedPhoto = orderRepository.checkUserBuyFaceItem(userId, faceId, 2, faceId);
if (!hasPurchasedPhoto) {
allContentsPurchased = false;
}
}
}
}
// 如果所有内容都已购买,则认为已购买套餐
if (allContentsPurchased) {
respVO.setBuy(true);
}
}
respVO.setShare(false);
return respVO;
}
}

View File

@@ -0,0 +1,192 @@
package com.ycwl.basic.biz;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.TaskMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.mobile.goods.VideoTaskStatusVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.task.req.TaskReqQuery;
import com.ycwl.basic.model.pc.task.resp.TaskRespVO;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.TemplateRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Component
public class TaskStatusBiz {
public static final String TASK_STATUS_USER_CACHE_KEY = "task:status:user:%s:face:%s";
public static final String TASK_STATUS_FACE_CACHE_KEY = "task:status:face:%s";
public static final String TASK_STATUS_FACE_CACHE_KEY_CUT = "task:status:face:%s:cut";
public static final String TASK_STATUS_FACE_CACHE_KEY_TEMPLATE = "task:status:face:%s:tpl:%s";
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private FaceRepository faceRepository;
@Autowired
private TemplateRepository templateRepository;
@Autowired
private FaceMapper faceMapper;
@Autowired
private TaskMapper taskMapper;
@Autowired
private VideoMapper videoMapper;
@Autowired
private TemplateBiz templateBiz;
public boolean getUserHaveFace(Long userId, Long faceId) {
if (userId == null || faceId == null) {
return false;
}
if (redisTemplate.hasKey(String.format(TASK_STATUS_USER_CACHE_KEY, userId, faceId))) {
return true;
}
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
return false;
}
if (face.getMemberId().equals(userId)) {
redisTemplate.opsForValue().set(String.format(TASK_STATUS_USER_CACHE_KEY, userId, faceId), "1", 3600, TimeUnit.SECONDS);
return true;
} else {
return false;
}
}
public void setFaceCutStatus(Long faceId, int status) {
redisTemplate.opsForValue().set(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId), String.valueOf(status), 3600, TimeUnit.SECONDS);
}
public void setFaceTemplateStatus(Long faceId, Long templateId, Long videoId) {
redisTemplate.opsForValue().set(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId), String.valueOf(videoId), 3600, TimeUnit.SECONDS);
}
public VideoTaskStatusVO getScenicUserStatus(Long scenicId, Long userId) {
FaceRespVO lastFace = faceMapper.findLastFaceByScenicAndUserId(scenicId, userId);
VideoTaskStatusVO response = new VideoTaskStatusVO();
if (lastFace == null) {
response.setStatus(-1);
return response;
}
return getFaceStatus(lastFace.getId());
}
public VideoTaskStatusVO getFaceStatus(Long faceId) {
FaceEntity face = faceRepository.getFace(faceId);
VideoTaskStatusVO response = new VideoTaskStatusVO();
if (face == null) {
response.setStatus(-1);
return response;
}
response.setScenicId(face.getScenicId());
response.setFaceId(faceId);
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(face.getScenicId());
response.setMaxCount(templateList.size());
int alreadyFinished = 0;
for (TemplateRespVO template : templateList) {
response.setTemplateId(template.getId());
long videoId = getFaceTemplateVideoId(faceId, template.getId());
if (videoId <= 0) {
response.setStatus(2);
} else {
response.setVideoId(videoId);
alreadyFinished++;
}
}
response.setCount(alreadyFinished);
if (alreadyFinished == 0) {
response.setStatus(0);
} else {
response.setStatus(1);
}
if (alreadyFinished == 0) {
int faceCutStatus = getFaceCutStatus(faceId);
if (faceCutStatus != 1) {
// 正在切片
if (templateBiz.determineTemplateCanGenerate(templateList.getFirst().getId(), faceId, false)) {
response.setStatus(2);
} else {
response.setStatus(0);
}
}
}
return response;
}
public VideoTaskStatusVO getFaceTemplateStatus(Long faceId, Long templateId) {
FaceEntity face = faceRepository.getFace(faceId);
VideoTaskStatusVO response = new VideoTaskStatusVO();
if (face == null) {
response.setStatus(-1);
return response;
}
response.setScenicId(face.getScenicId());
response.setFaceId(faceId);
response.setTemplateId(templateId);
long videoId = getFaceTemplateVideoId(faceId, templateId);
if (videoId < 0) {
int faceCutStatus = getFaceCutStatus(faceId);
if (faceCutStatus != 1) {
// 正在切片
response.setStatus(2);
return response;
}
} else if (videoId == 0) {
response.setStatus(2);
} else {
response.setVideoId(videoId);
response.setStatus(1);
}
return response;
}
public int getFaceCutStatus(Long faceId) {
if (redisTemplate.hasKey(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId))) {
String status = redisTemplate.opsForValue().get(String.format(TASK_STATUS_FACE_CACHE_KEY_CUT, faceId));
if (status != null) {
return Integer.parseInt(status);
}
}
return 1;
}
public long getFaceTemplateVideoId(Long faceId, Long templateId) {
if (redisTemplate.hasKey(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId))) {
String status = redisTemplate.opsForValue().get(String.format(TASK_STATUS_FACE_CACHE_KEY_TEMPLATE, faceId, templateId));
if (status != null) {
return Long.parseLong(status);
}
}
TaskReqQuery taskReqQuery = new TaskReqQuery();
taskReqQuery.setFaceId(faceId);
taskReqQuery.setTemplateId(templateId);
List<TaskRespVO> list = taskMapper.list(taskReqQuery);
Optional<TaskRespVO> min = list.stream().min(Comparator.comparing(TaskRespVO::getCreateTime));
if (min.isPresent()) {
TaskRespVO task = min.get();
long taskStatus = 0;
if (task.getStatus() == 1) {
// 已完成
VideoEntity video = videoMapper.findByTaskId(task.getId());
if (video != null) {
taskStatus = video.getId();
}
}
setFaceTemplateStatus(faceId, templateId, taskStatus);
} else {
// 从来没生成过
setFaceTemplateStatus(faceId, templateId, -1L);
return -1;
}
return 0;
}
}

View File

@@ -14,11 +14,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -32,6 +29,8 @@ public class TemplateBiz {
private FaceRepository faceRepository;
@Autowired
private SourceMapper sourceMapper;
@Autowired
private SourceRepository sourceRepository;
public boolean determineTemplateCanGenerate(Long templateId, Long faceId) {
return determineTemplateCanGenerate(templateId, faceId, true);
@@ -54,7 +53,6 @@ public class TemplateBiz {
if (scanSource) {
List<SourceEntity> sourceEntities = sourceMapper.listVideoByScenicFaceRelation(face.getScenicId(), faceId);
if (sourceEntities == null || sourceEntities.isEmpty()) {
log.info("faceId:{} has no source", faceId);
return false;
}
count = sourceEntities.stream()
@@ -66,7 +64,6 @@ public class TemplateBiz {
} else {
List<FaceSampleEntity> faceSampleList = faceRepository.getFaceSampleList(faceId);
if (faceSampleList == null || faceSampleList.isEmpty()) {
log.info("faceId:{} has no faceSample", faceId);
return false;
}
count = faceSampleList.stream()
@@ -94,8 +91,8 @@ public class TemplateBiz {
}
if (minimalPlaceholderFill == null) {
// 未开启
log.info("模板:{},未配置最小自动生成功能,默认生成", templateId);
minimalPlaceholderFill = 1;
log.info("模板:{},未配置最小自动生成功能,默认生成", templateId);
return false;
}
if (minimalPlaceholderFill <= 0) {
return true;
@@ -124,97 +121,4 @@ public class TemplateBiz {
return count >= minimalPlaceholderFill;
}
public Map<String, List<SourceEntity>> filterTaskParams(Long templateId, Map<String, List<SourceEntity>> allTaskParams) {
if (allTaskParams == null || allTaskParams.isEmpty()) {
return Map.of();
}
List<String> templatePlaceholders = templateRepository.getTemplatePlaceholder(templateId);
if (templatePlaceholders == null || templatePlaceholders.isEmpty()) {
log.info("filterTaskParams: templateId:{} has no placeholders", templateId);
return Map.of();
}
TemplateConfigEntity templateConfig = templateRepository.getTemplateConfig(templateId);
// 统计每个 placeholder 在模板中出现的次数
Map<String, Long> placeholderCounts = templatePlaceholders.stream()
.collect(Collectors.groupingBy(
placeholder -> placeholder,
Collectors.counting()
));
Map<String, List<SourceEntity>> filteredParams = new HashMap<>();
// 判断是否允许片段重复
boolean allowDuplicate = templateConfig != null && Integer.valueOf(1).equals(templateConfig.getDuplicateEnable());
for (Map.Entry<String, Long> entry : placeholderCounts.entrySet()) {
String placeholder = entry.getKey();
Long requiredCount = entry.getValue();
if (placeholder.startsWith("P")) {
// 图片源:占位符格式为 "P{deviceId}"
String imageKey = placeholder;
if (allTaskParams.containsKey(imageKey)) {
List<SourceEntity> allSources = allTaskParams.get(imageKey);
List<SourceEntity> selectedSources = selectSources(allSources, requiredCount.intValue(), allowDuplicate);
if (!selectedSources.isEmpty()) {
filteredParams.put(imageKey, selectedSources);
}
}
} else {
// 视频源:占位符直接对应设备ID
String videoKey = placeholder;
if (allTaskParams.containsKey(videoKey)) {
List<SourceEntity> allSources = allTaskParams.get(videoKey);
List<SourceEntity> selectedSources = selectSources(allSources, requiredCount.intValue(), allowDuplicate);
if (!selectedSources.isEmpty()) {
filteredParams.put(videoKey, selectedSources);
}
}
}
}
log.debug("filterTaskParams: templateId:{}, original keys:{}, filtered keys:{}, placeholder counts:{}, allowDuplicate:{}",
templateId, allTaskParams.keySet().size(), filteredParams.keySet().size(), placeholderCounts, allowDuplicate);
return filteredParams;
}
private List<SourceEntity> selectSources(List<SourceEntity> allSources, int requiredCount, boolean allowDuplicate) {
if (allSources == null || allSources.isEmpty()) {
return new ArrayList<>();
}
if (!allowDuplicate) {
// 不允许重复,使用原有逻辑
int actualCount = Math.min(requiredCount, allSources.size());
return new ArrayList<>(allSources.subList(0, actualCount));
}
// 允许重复,循环填充到所需数量
List<SourceEntity> selectedSources = new ArrayList<>();
int sourceIndex = 0;
for (int i = 0; i < requiredCount; i++) {
selectedSources.add(allSources.get(sourceIndex));
sourceIndex = (sourceIndex + 1) % allSources.size();
}
return selectedSources;
}
public Long findFirstAvailableTemplate(List<Long> templateIds, Long faceId, boolean scanSource) {
if (templateIds == null || templateIds.isEmpty() || faceId == null) {
return null;
}
for (Long templateId : templateIds) {
if (determineTemplateCanGenerate(templateId, faceId, scanSource)) {
return templateId;
}
}
return null;
}
}

View File

@@ -1,104 +0,0 @@
package com.ycwl.basic.clickhouse.service;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* 统计数据查询服务接口
* 用于抽象 t_stats 和 t_stats_record 表的查询
* 支持 MySQL 和 ClickHouse 两种实现
*/
public interface StatsQueryService {
/**
* 统计预览视频人数
*/
Integer countPreviewVideoOfMember(CommonQueryReq query);
/**
* 统计扫码访问人数
*/
Integer countScanCodeOfMember(CommonQueryReq query);
/**
* 统计推送订阅人数
*/
Integer countPushOfMember(CommonQueryReq query);
/**
* 统计上传头像人数
*/
Integer countUploadFaceOfMember(CommonQueryReq query);
/**
* 统计生成视频人数
*/
Integer countCompleteVideoOfMember(CommonQueryReq query);
/**
* 统计生成视频条数
*/
Integer countCompleteOfVideo(CommonQueryReq query);
/**
* 统计总访问人数
*/
Integer countTotalVisitorOfMember(CommonQueryReq query);
/**
* 统计预览视频条数
*/
Integer countPreviewOfVideo(CommonQueryReq query);
/**
* 获取用户分销员 ID 列表
*/
List<Long> getBrokerIdListForUser(Long memberId, Date startTime, Date endTime);
/**
* 获取用户最近进入类型
*/
Long getUserRecentEnterType(Long memberId, Date endTime);
/**
* 获取用户项目 ID 列表
*/
List<Long> getProjectIdListForUser(Long memberId, Date startTime, Date endTime);
/**
* 统计分销员扫码次数
*/
Integer countBrokerScanCount(Long brokerId);
/**
* 按日期统计分销员扫码数据
*/
List<HashMap<String, Object>> getDailyScanStats(Long brokerId, Date startTime, Date endTime);
/**
* 按小时统计扫码人数(仅返回统计数据,不含订单)
* 返回格式: [{t: "MM-dd HH", count: "xxx"}, ...]
*/
List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query);
/**
* 按日期统计扫码人数(仅返回统计数据,不含订单)
* 返回格式: [{t: "MM-dd", count: "xxx"}, ...]
*/
List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query);
/**
* 按小时统计访问打印样片页面人数
* 返回格式: [{t: "MM-dd HH", count: "xxx"}, ...]
*/
List<HashMap<String, String>> printerFromSampleChartByHour(CommonQueryReq query);
/**
* 按日期统计访问打印样片页面人数
* 返回格式: [{t: "MM-dd", count: "xxx"}, ...]
*/
List<HashMap<String, String>> printerFromSampleChartByDate(CommonQueryReq query);
}

View File

@@ -1,536 +0,0 @@
package com.ycwl.basic.clickhouse.service.impl;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.mapper.TaskMapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
* ClickHouse 统计数据查询服务实现
* 当 clickhouse.enabled=true 时启用
*
* 注意:ClickHouse JDBC 驱动 0.6.x 对参数绑定支持有问题,
* 因此使用字符串格式化方式构建 SQL(参数均为内部生成的数值或日期,无 SQL 注入风险)
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true")
public class ClickHouseStatsQueryServiceImpl implements StatsQueryService {
private static final TimeZone CLICKHOUSE_TIMEZONE = TimeZone.getTimeZone("Asia/Shanghai");
/**
* 创建日期格式化器(SimpleDateFormat 非线程安全,每次创建新实例)
*/
private SimpleDateFormat createDateFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(CLICKHOUSE_TIMEZONE);
return sdf;
}
/**
* 创建日期时间格式化器
*/
private SimpleDateFormat createDateTimeFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(CLICKHOUSE_TIMEZONE);
return sdf;
}
@Autowired
@Qualifier("clickHouseJdbcTemplate")
private NamedParameterJdbcTemplate namedJdbcTemplate;
private JdbcTemplate jdbcTemplate;
@Autowired
private TaskMapper taskMapper;
private JdbcTemplate getJdbcTemplate() {
if (jdbcTemplate == null) {
jdbcTemplate = namedJdbcTemplate.getJdbcTemplate();
}
return jdbcTemplate;
}
/**
* 格式化日期时间为 ClickHouse 可识别的字符串
*/
private String formatDateTime(Date date) {
return date != null ? "'" + createDateTimeFormat().format(date) + "'" : null;
}
/**
* 格式化日期为 ClickHouse 可识别的字符串
*/
private String formatDate(Date date) {
return date != null ? "'" + createDateFormat().format(date) + "'" : null;
}
/**
* 拼接“进入景区”的 trace_id 子查询。
* <p>
* ClickHouse 上 t_stats_record 往往按时间分区/排序;给子查询补充时间范围可显著减少扫描量。
*/
private void appendEnterScenicTraceIdSubQuery(StringBuilder sql, Long scenicId, Date startTime, Date endTime) {
sql.append("SELECT DISTINCT trace_id FROM t_stats_record ");
sql.append("WHERE action = 'ENTER_SCENIC' ");
if (scenicId != null) {
sql.append("AND identifier = '").append(scenicId).append("' ");
}
if (startTime != null) {
sql.append("AND create_time >= ").append(formatDateTime(startTime)).append(" ");
}
if (endTime != null) {
sql.append("AND create_time <= ").append(formatDateTime(endTime)).append(" ");
}
}
@Override
public Integer countPreviewVideoOfMember(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt32(uniqExact(s.member_id)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'LOAD' ");
sql.append("AND r.identifier = 'pages/videoSynthesis/buy' ");
sql.append("AND r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND JSONExtractString(r.params, 'share') = '' ");
if (query.getStartTime() != null) {
sql.append("AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
@Override
public Integer countScanCodeOfMember(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt32(uniqExact(s.member_id)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'LAUNCH' ");
sql.append("AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) ");
if (query.getStartTime() != null) {
sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
@Override
public Integer countPushOfMember(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt32(uniqExact(s.member_id)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'PERM_REQ' ");
sql.append("AND r.identifier = 'NOTIFY' ");
if (query.getStartTime() != null) {
sql.append("AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
@Override
public Integer countUploadFaceOfMember(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt32(uniqExact(s.member_id)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'FACE_UPLOAD' ");
sql.append("AND r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
if (query.getStartTime() != null) {
sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
private List<String> listFaceIdsWithUpload(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT DISTINCT r.identifier FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'FACE_UPLOAD' ");
sql.append("AND r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
if (query.getStartTime() != null) {
sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForList(sql.toString(), String.class);
}
@Override
public Integer countCompleteVideoOfMember(CommonQueryReq query) {
List<String> faceIds = listFaceIdsWithUpload(query);
if (faceIds == null || faceIds.isEmpty()) {
return 0;
}
return taskMapper.countCompletedTaskMembersByFaceIds(faceIds);
}
@Override
public Integer countCompleteOfVideo(CommonQueryReq query) {
List<String> faceIds = listFaceIdsWithUpload(query);
if (faceIds == null || faceIds.isEmpty()) {
return 0;
}
return taskMapper.countCompletedTasksByFaceIds(faceIds);
}
@Override
public Integer countTotalVisitorOfMember(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt32(uniqExact(s.member_id)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'LAUNCH' ");
if (query.getStartTime() != null) {
sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
@Override
public Integer countPreviewOfVideo(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("WITH JSONExtractString(params, 'id') AS videoId, ");
sql.append(" JSONExtractString(params, 'share') AS share ");
sql.append("SELECT toInt32(uniqExact(videoId)) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'LOAD' ");
sql.append("AND r.identifier = 'pages/videoSynthesis/buy' ");
sql.append("AND videoId != '' ");
sql.append("AND share = '' ");
if (query.getStartTime() != null) {
sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
return getJdbcTemplate().queryForObject(sql.toString(), Integer.class);
}
@Override
public List<Long> getBrokerIdListForUser(Long memberId, Date startTime, Date endTime) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt64(r.identifier) AS identifier ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'CODE_SCAN' ");
sql.append(" AND s.member_id = ").append(memberId).append(" ");
if (startTime != null) {
sql.append(" AND r.create_time >= ").append(formatDateTime(startTime)).append(" ");
}
if (endTime != null) {
sql.append(" AND r.create_time <= ").append(formatDateTime(endTime)).append(" ");
}
sql.append("GROUP BY r.identifier ");
sql.append("ORDER BY max(r.create_time) DESC");
return getJdbcTemplate().queryForList(sql.toString(), Long.class);
}
@Override
public Long getUserRecentEnterType(Long memberId, Date endTime) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT JSONExtractInt(r.params, 'scene') AS scene ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'LAUNCH' ");
sql.append(" AND s.member_id = ").append(memberId).append(" ");
if (endTime != null) {
sql.append(" AND r.create_time <= ").append(formatDateTime(endTime)).append(" ");
}
sql.append("ORDER BY r.create_time DESC LIMIT 1");
try {
return getJdbcTemplate().queryForObject(sql.toString(), Long.class);
} catch (Exception e) {
return null;
}
}
@Override
public List<Long> getProjectIdListForUser(Long memberId, Date startTime, Date endTime) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT toInt64(r.identifier) AS identifier ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE s.member_id = ").append(memberId).append(" ");
sql.append(" AND r.action = 'ENTER_PROJECT' ");
sql.append(" AND r.create_time < ").append(formatDateTime(endTime)).append(" ");
sql.append(" AND r.create_time > ").append(formatDateTime(startTime)).append(" ");
sql.append("ORDER BY r.create_time DESC LIMIT 1");
return getJdbcTemplate().queryForList(sql.toString(), Long.class);
}
@Override
public Integer countBrokerScanCount(Long brokerId) {
String sql = "SELECT count(1) AS count FROM t_stats_record " +
"WHERE action = 'CODE_SCAN' AND identifier = '" + brokerId + "'";
return getJdbcTemplate().queryForObject(sql, Integer.class);
}
@Override
public List<HashMap<String, Object>> getDailyScanStats(Long brokerId, Date startTime, Date endTime) {
SimpleDateFormat dateFormat = createDateFormat();
String startDateStr = dateFormat.format(startTime);
String endDateStr = dateFormat.format(endTime);
String startDateTimeStr = "'" + startDateStr + " 00:00:00'";
String endDateTimeStr = "'" + endDateStr + " 23:59:59'";
String sql = "SELECT toDate(create_time) AS date, count(DISTINCT id) AS scanCount " +
"FROM t_stats_record " +
"WHERE action = 'CODE_SCAN' " +
" AND identifier = '" + brokerId + "' " +
" AND create_time >= " + startDateTimeStr + " " +
" AND create_time <= " + endDateTimeStr + " " +
"GROUP BY toDate(create_time) " +
"ORDER BY toDate(create_time)";
return getJdbcTemplate().query(sql, (rs, rowNum) -> {
HashMap<String, Object> map = new HashMap<>();
map.put("date", rs.getDate("date"));
map.put("scanCount", rs.getLong("scanCount"));
return map;
});
}
@Override
public List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT formatDateTime(toStartOfHour(s.create_time), '%m-%d %H') AS t, ");
sql.append(" uniqExact(s.member_id) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'LAUNCH' ");
sql.append("AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) ");
sql.append("GROUP BY toStartOfHour(s.create_time) ");
sql.append("ORDER BY toStartOfHour(s.create_time)");
List<HashMap<String, String>> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> {
HashMap<String, String> map = new HashMap<>();
map.put("t", rs.getString("t"));
map.put("count", rs.getString("count"));
return map;
});
return fillHourSeries(rawData, query.getStartTime(), query.getEndTime());
}
@Override
public List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT formatDateTime(toStartOfDay(s.create_time), '%m-%d') AS t, ");
sql.append(" uniqExact(s.member_id) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.trace_id IN (");
appendEnterScenicTraceIdSubQuery(sql, query.getScenicId(), query.getStartTime(), query.getEndTime());
sql.append(") ");
sql.append("AND r.action = 'LAUNCH' ");
sql.append("AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) ");
sql.append("GROUP BY toStartOfDay(s.create_time) ");
sql.append("ORDER BY toStartOfDay(s.create_time)");
List<HashMap<String, String>> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> {
HashMap<String, String> map = new HashMap<>();
map.put("t", rs.getString("t"));
map.put("count", rs.getString("count"));
return map;
});
return fillDateSeries(rawData, query.getStartTime(), query.getEndTime());
}
@Override
public List<HashMap<String, String>> printerFromSampleChartByHour(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT formatDateTime(toStartOfHour(s.create_time), '%m-%d %H') AS t, ");
sql.append(" uniqExact(s.member_id) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'LOAD' ");
sql.append("AND r.identifier = 'pages/printer/hello' ");
if (query.getScenicId() != null) {
sql.append("AND JSONExtractString(r.params, 'scenicId') = '").append(query.getScenicId()).append("' ");
}
if (query.getStartTime() != null) {
sql.append("AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
sql.append("GROUP BY toStartOfHour(s.create_time) ");
sql.append("ORDER BY toStartOfHour(s.create_time)");
List<HashMap<String, String>> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> {
HashMap<String, String> map = new HashMap<>();
map.put("t", rs.getString("t"));
map.put("count", rs.getString("count"));
return map;
});
return fillHourSeries(rawData, query.getStartTime(), query.getEndTime());
}
@Override
public List<HashMap<String, String>> printerFromSampleChartByDate(CommonQueryReq query) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT formatDateTime(toStartOfDay(s.create_time), '%m-%d') AS t, ");
sql.append(" uniqExact(s.member_id) AS count ");
sql.append("FROM t_stats_record r ");
sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id ");
sql.append("WHERE r.action = 'LOAD' ");
sql.append("AND r.identifier = 'pages/printer/hello' ");
if (query.getScenicId() != null) {
sql.append("AND JSONExtractString(r.params, 'scenicId') = '").append(query.getScenicId()).append("' ");
}
if (query.getStartTime() != null) {
sql.append("AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" ");
}
if (query.getEndTime() != null) {
sql.append("AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" ");
}
sql.append("GROUP BY toStartOfDay(s.create_time) ");
sql.append("ORDER BY toStartOfDay(s.create_time)");
List<HashMap<String, String>> rawData = getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> {
HashMap<String, String> map = new HashMap<>();
map.put("t", rs.getString("t"));
map.put("count", rs.getString("count"));
return map;
});
return fillDateSeries(rawData, query.getStartTime(), query.getEndTime());
}
/**
* 填充小时序列,确保每个小时都有数据(缺失的填充为0)
*/
private List<HashMap<String, String>> fillHourSeries(List<HashMap<String, String>> rawData, Date startTime, Date endTime) {
if (startTime == null || endTime == null) {
return rawData;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH");
LocalDateTime start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().truncatedTo(ChronoUnit.HOURS);
LocalDateTime end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().truncatedTo(ChronoUnit.HOURS);
// 将原始数据转为 Map 以便快速查找
Map<String, String> dataMap = rawData.stream()
.collect(Collectors.toMap(
m -> m.get("t"),
m -> m.get("count"),
(existing, replacement) -> existing
));
List<HashMap<String, String>> result = new ArrayList<>();
LocalDateTime current = start;
while (!current.isAfter(end)) {
String timeKey = current.format(formatter);
HashMap<String, String> item = new HashMap<>();
item.put("t", timeKey);
item.put("count", dataMap.getOrDefault(timeKey, "0"));
result.add(item);
current = current.plusHours(1);
}
return result;
}
/**
* 填充日期序列,确保每天都有数据(缺失的填充为0)
*/
private List<HashMap<String, String>> fillDateSeries(List<HashMap<String, String>> rawData, Date startTime, Date endTime) {
if (startTime == null || endTime == null) {
return rawData;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
LocalDate start = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate end = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
// 将原始数据转为 Map 以便快速查找
Map<String, String> dataMap = rawData.stream()
.collect(Collectors.toMap(
m -> m.get("t"),
m -> m.get("count"),
(existing, replacement) -> existing
));
List<HashMap<String, String>> result = new ArrayList<>();
LocalDate current = start;
while (!current.isAfter(end)) {
String timeKey = current.format(formatter);
HashMap<String, String> item = new HashMap<>();
item.put("t", timeKey);
item.put("count", dataMap.getOrDefault(timeKey, "0"));
result.add(item);
current = current.plusDays(1);
}
return result;
}
}

View File

@@ -1,111 +0,0 @@
package com.ycwl.basic.clickhouse.service.impl;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* MySQL 统计数据查询服务实现
* 当 clickhouse.enabled 未启用时使用此实现(兜底)
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "false", matchIfMissing = true)
public class MySqlStatsQueryServiceImpl implements StatsQueryService {
@Autowired
private StatisticsMapper statisticsMapper;
@Override
public Integer countPreviewVideoOfMember(CommonQueryReq query) {
return statisticsMapper.countPreviewVideoOfMember(query);
}
@Override
public Integer countScanCodeOfMember(CommonQueryReq query) {
return statisticsMapper.countScanCodeOfMember(query);
}
@Override
public Integer countPushOfMember(CommonQueryReq query) {
return statisticsMapper.countPushOfMember(query);
}
@Override
public Integer countUploadFaceOfMember(CommonQueryReq query) {
return statisticsMapper.countUploadFaceOfMember(query);
}
@Override
public Integer countCompleteVideoOfMember(CommonQueryReq query) {
return statisticsMapper.countCompleteVideoOfMember(query);
}
@Override
public Integer countCompleteOfVideo(CommonQueryReq query) {
return statisticsMapper.countCompleteOfVideo(query);
}
@Override
public Integer countTotalVisitorOfMember(CommonQueryReq query) {
return statisticsMapper.countTotalVisitorOfMember(query);
}
@Override
public Integer countPreviewOfVideo(CommonQueryReq query) {
return statisticsMapper.countPreviewOfVideo(query);
}
@Override
public List<Long> getBrokerIdListForUser(Long memberId, Date startTime, Date endTime) {
return statisticsMapper.getBrokerIdListForUser(memberId, startTime, endTime);
}
@Override
public Long getUserRecentEnterType(Long memberId, Date endTime) {
return statisticsMapper.getUserRecentEnterType(memberId, endTime);
}
@Override
public List<Long> getProjectIdListForUser(Long memberId, Date startTime, Date endTime) {
return statisticsMapper.getProjectIdListForUser(memberId, startTime, endTime);
}
@Override
public Integer countBrokerScanCount(Long brokerId) {
return statisticsMapper.countBrokerScanCount(brokerId);
}
@Override
public List<HashMap<String, Object>> getDailyScanStats(Long brokerId, Date startTime, Date endTime) {
return statisticsMapper.getDailyScanStats(brokerId, startTime, endTime);
}
@Override
public List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query) {
return statisticsMapper.scanCodeMemberChartByHour(query);
}
@Override
public List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query) {
return statisticsMapper.scanCodeMemberChartByDate(query);
}
@Override
public List<HashMap<String, String>> printerFromSampleChartByHour(CommonQueryReq query) {
return statisticsMapper.printerFromSampleChartByHour(query);
}
@Override
public List<HashMap<String, String>> printerFromSampleChartByDate(CommonQueryReq query) {
return statisticsMapper.printerFromSampleChartByDate(query);
}
}

View File

@@ -1,37 +0,0 @@
package com.ycwl.basic.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.sql.DataSource;
/**
* ClickHouse 数据源配置
* 用于 t_stats 和 t_stats_record 表的查询
*
* 使用 NamedParameterJdbcTemplate 而非 MyBatis,以避免干扰 MyBatis-Plus 的自动配置
*/
@Configuration
@ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true")
public class ClickHouseDataSourceConfig {
/**
* ClickHouse 数据源(非 Primary)
*/
@Bean(name = "clickHouseDataSource")
@ConfigurationProperties(prefix = "clickhouse.datasource")
public DataSource clickHouseDataSource() {
return new HikariDataSource();
}
@Bean(name = "clickHouseJdbcTemplate")
public NamedParameterJdbcTemplate clickHouseJdbcTemplate(
@Qualifier("clickHouseDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}

View File

@@ -1,26 +1,34 @@
package com.ycwl.basic.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author wenshijia
* @date 2021年07月05日 18:34
* 修改redis缓存序列化器
*/
@Configuration
public class CustomRedisCacheManager {
@Autowired
private ObjectMapper objectMapper;
@EnableCaching
public class CustomRedisCacheManager extends CachingConfigurerSupport {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofMinutes(1));
return configuration;
}
/**
* 处理redis连接工具显示redis key值显示乱码问题,value值没处理
@@ -37,23 +45,10 @@ public class CustomRedisCacheManager {
final StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// Configure Jackson2JsonRedisSerializer with JavaTimeModule for value serialization
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
// Configure type handling to prevent ClassCastException
PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Object.class)
.build();
objectMapper.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

View File

@@ -1,16 +1,10 @@
package com.ycwl.basic.config;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class JacksonConfiguration {
@@ -19,16 +13,6 @@ public class JacksonConfiguration {
return builder -> {
// 把 Long 类型序列化为 String
builder.serializerByType(Long.class, ToStringSerializer.instance);
// 添加 JavaTimeModule 以支持 Java 8 时间类型
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
@Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
}

View File

@@ -1,109 +0,0 @@
package com.ycwl.basic.config;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;
import org.springframework.kafka.listener.ContainerProperties;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ConditionalOnProperty(name = "kafka.enabled", havingValue = "true", matchIfMissing = false)
public class KafkaConfig {
@Value("${kafka.bootstrap-servers:100.64.0.12:39092}")
private String bootstrapServers;
@Value("${kafka.consumer.group-id:liuying-microservice}")
private String consumerGroupId;
@Value("${kafka.consumer.auto-offset-reset:earliest}")
private String autoOffsetReset;
@Value("${kafka.producer.acks:all}")
private String acks;
@Value("${kafka.producer.retries:3}")
private Integer retries;
@Value("${kafka.producer.batch-size:16384}")
private Integer batchSize;
@Value("${kafka.producer.linger-ms:1}")
private Integer lingerMs;
@Value("${kafka.producer.buffer-memory:33554432}")
private Integer bufferMemory;
@Value("${kafka.producer.enable-idempotence:true}")
private boolean enableIdempotence;
@Value("${kafka.producer.compression-type:snappy}")
private String compressionType;
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.ACKS_CONFIG, acks);
configProps.put(ProducerConfig.RETRIES_CONFIG, retries);
configProps.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
configProps.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
configProps.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, enableIdempotence);
configProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, compressionType);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
return new DefaultKafkaConsumerFactory<>(props);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> manualCommitKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
factory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(props));
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}
}

View File

@@ -1,41 +0,0 @@
package com.ycwl.basic.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* MySQL 主数据源配置
*
* 当 ClickHouse 启用时,需要显式配置 MySQL 数据源并标记为 @Primary,
* 以确保 MyBatis-Plus 和其他组件使用正确的数据源
*/
@Configuration
@ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true")
public class MySqlPrimaryDataSourceConfig {
/**
* MySQL 数据源属性
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSourceProperties mysqlDataSourceProperties() {
return new DataSourceProperties();
}
/**
* MySQL 主数据源
* 使用 @Primary 确保这是默认数据源
*/
@Primary
@Bean(name = "dataSource")
public DataSource mysqlDataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}

View File

@@ -3,7 +3,6 @@ package com.ycwl.basic.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -12,15 +11,6 @@ import org.springframework.context.annotation.Configuration;
* @date 2021年06月04日 9:42
*/
@Configuration
@MapperScan(basePackages = {
"com.ycwl.basic.mapper",
"com.ycwl.basic.order.mapper",
"com.ycwl.basic.pricing.mapper",
"com.ycwl.basic.product.mapper",
"com.ycwl.basic.profitsharing.mapper",
"com.ycwl.basic.puzzle.mapper",
"com.ycwl.basic.stats.mapper"
})
public class MybatisPlusPageConfig {
/* 旧版本配置

View File

@@ -0,0 +1,74 @@
package com.ycwl.basic.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger 配置类
* 原生: /swagger-ui.html
* 美化: /doc.html
*/
@Configuration
@EnableSwagger2WebMvc
@Profile({"test"})
public class SwaggerConfig {
/**
* Swagger 实例 Bean 是 Docket, 所以通过配置 Docket 实例来配置 Swagger
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
// 展示在 Swagger 页面上的自定义工程描述信息
.apiInfo(apiInfo())
// 选择展示哪些接口
.select()
//只有com.zcy.e.firstaid包内的才去展示
.apis(RequestHandlerSelectors.basePackage("com.ycwl.basic.controller.mobile"))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(getGlobalRequestParameters());
}
/**
* Swagger 的描述信息
*/
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("liuyin-re")
.description("流影重构")
.contact(new Contact("ycwl", "www.xxx.com", "xxxxxxxxx.com"))
.version("1.0")
.build();
}
private List<Parameter> getGlobalRequestParameters() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(new ParameterBuilder()
.name("token")
.description("登录令牌")
.parameterType("header")
.modelRef(new ModelRef("String"))
.required(true)
.build());
return parameters;
}
}

View File

@@ -1,32 +0,0 @@
package com.ycwl.basic.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 视频更新检查配置
* @author Claude
*/
@Data
@Component
@ConfigurationProperties(prefix = "video.update")
public class VideoUpdateConfig {
/**
* 是否将片段变化检测为新增
* true: 任何变化都视为新增
* false: 只有数量增加才视为新增
*/
private boolean detectChangesAsNew = true;
/**
* 最小新增片段数量才认为可更新
*/
private int minNewSegmentCount = 1;
/**
* 是否启用视频更新检查功能
*/
private boolean enabled = true;
}

View File

@@ -3,7 +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.puzzle.edge.interceptor.PuzzleEdgeWorkerIpInterceptor;
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;
@@ -25,19 +25,20 @@ import java.util.List;
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private PuzzleEdgeWorkerIpInterceptor puzzleEdgeWorkerIpInterceptor;
private StatsInterceptor statsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(puzzleEdgeWorkerIpInterceptor)
.addPathPatterns("/puzzle/render/v1/**");
registry.addInterceptor(authInterceptor)
// 拦截除指定接口外的所有请求,通过判断 注解 来决定是否需要做登录验证
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/api-docs", "/doc.html/**", "/error", "/");
registry.addInterceptor(statsInterceptor)
.addPathPatterns("/api/mobile/**");
}
/**

View File

@@ -1,71 +0,0 @@
package com.ycwl.basic.constant;
/**
* 购买状态枚举
* 定义源文件的已购买和未购买两种状态
*
* @author Claude
* @since 2025-10-31
*/
public enum BuyStatus {
/**
* 未购买状态
*/
NOT_BOUGHT(0, "未购买"),
/**
* 已购买状态
*/
BOUGHT(1, "已购买");
private final int code;
private final String description;
BuyStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 根据代码值获取枚举
*
* @param code 状态代码
* @return 对应的枚举值,如果不存在返回 null
*/
public static BuyStatus fromCode(int code) {
for (BuyStatus status : values()) {
if (status.code == code) {
return status;
}
}
return null;
}
/**
* 判断给定的代码是否为已购买状态
*
* @param code 状态代码
* @return true-已购买,false-未购买
*/
public static boolean isBought(Integer code) {
return code != null && code == BOUGHT.code;
}
/**
* 判断给定的代码是否为未购买状态
*
* @param code 状态代码
* @return true-未购买,false-已购买
*/
public static boolean isNotBought(Integer code) {
return code != null && code == NOT_BOUGHT.code;
}
}

View File

@@ -4,7 +4,4 @@ public class FaceConstant {
public static final String FACE_DB_NAME_PFX="face:db:";
public static final String USER_FACE_DB_NAME="userFace";
public static final String FACE_USER_URL_PFX="face:user:url:";
public static final String FACE_RECOGNITION_COUNT_PFX="face:recognition:count:";
public static final String FACE_CUSTOM_MATCH_COUNT_PFX="face:custom:match:count:";
public static final String FACE_LOW_THRESHOLD_PFX="face:low:threshold:";
}

View File

@@ -1,71 +0,0 @@
package com.ycwl.basic.constant;
/**
* 免费状态枚举
* 定义源文件的收费和免费两种状态
*
* @author Claude
* @since 2025-10-31
*/
public enum FreeStatus {
/**
* 收费状态
*/
PAID(0, "收费"),
/**
* 免费状态
*/
FREE(1, "免费");
private final int code;
private final String description;
FreeStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 根据代码值获取枚举
*
* @param code 状态代码
* @return 对应的枚举值,如果不存在返回 null
*/
public static FreeStatus fromCode(int code) {
for (FreeStatus status : values()) {
if (status.code == code) {
return status;
}
}
return null;
}
/**
* 判断给定的代码是否为免费状态
*
* @param code 状态代码
* @return true-免费,false-收费
*/
public static boolean isFree(Integer code) {
return code != null && code == FREE.code;
}
/**
* 判断给定的代码是否为收费状态
*
* @param code 状态代码
* @return true-收费,false-免费
*/
public static boolean isPaid(Integer code) {
return code != null && code == PAID.code;
}
}

View File

@@ -0,0 +1,18 @@
package com.ycwl.basic.constant;
public class ShareParkingSpaceRedisKeyConstant {
// 更改数量时候的锁
public final static String UPDATE_NUMBER_LOCK_KEY="ShareParking:updateNumberLockKey";
// 地上车位
public final static String GROUND_PARKING_SPACE_NUMBER="ShareParking:groundParkingSpaceNumber";
// 地下车位数
public final static String UNDERGROUND_PARKING_SPACE_NUMBER="ShareParking:undergroundParkingSpaceNumber";
// 每日开放预约时间
public final static String OPEN_TIME="ShareParking:openTime";
// 预约后当日车辆最晚停留时间
public final static String RESIDENCE_TIME="ShareParking:residenceTime";
//取消时间
public final static String CANCEL_TIME="ShareParking:cancelTime";
//支付时间
public final static String PAY_TIME="ShareParking:payTime";
}

View File

@@ -1,86 +0,0 @@
package com.ycwl.basic.constant;
/**
* 源文件类型枚举
* 定义视频和图片两种源文件类型
*
* @author Claude
* @since 2025-10-31
*/
public enum SourceType {
/**
* 视频类型
*/
VIDEO(1, "视频"),
/**
* 图片类型
*/
IMAGE(2, "图片"),
/**
* AI微单类型
*/
AI_CAM(3, "AI微单");
private final int code;
private final String description;
SourceType(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 根据代码值获取枚举
*
* @param code 类型代码
* @return 对应的枚举值,如果不存在返回 null
*/
public static SourceType fromCode(int code) {
for (SourceType type : values()) {
if (type.code == code) {
return type;
}
}
return null;
}
/**
* 判断给定的代码是否为视频类型
*
* @param code 类型代码
* @return true-是视频,false-不是视频
*/
public static boolean isVideo(Integer code) {
return code != null && code == VIDEO.code;
}
/**
* 判断给定的代码是否为图片类型
*
* @param code 类型代码
* @return true-是图片,false-不是图片
*/
public static boolean isImage(Integer code) {
return code != null && code == IMAGE.code;
}
/**
* 判断给定的代码是否为AI微单类型
*
* @param code 类型代码
* @return true-是AI微单,false-不是AI微单
*/
public static boolean isAiCam(Integer code) {
return code != null && code == AI_CAM.code;
}
}

View File

@@ -3,7 +3,8 @@ package com.ycwl.basic.constant;
public class StorageConstant {
public static final String VLOG_PATH = "vlog";
public static final String VIDEO_PIECE_PATH = "source_video";
public static final String PHOTO_PATH = "viid";
public static final String PHOTO_PATH = "source_photo";
public static final String PHOTO_WATERMARKED_PATH = "photo_w";
public static final String USER_FACE = "user_face";
public static final String VIID_FACE = "viid_face";
public static final String USER_FACE = "user_face";
}

View File

@@ -5,6 +5,8 @@ import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,10 +26,10 @@ import java.util.UUID;
@RestController
@RequestMapping("/api/file/v1")
@Slf4j
// 文件接口
@Api(tags = "文件接口")
public class FileController {
// 上传文件
@ApiOperation(value = "上传文件")
@PostMapping("/upload")
@IgnoreToken
public ApiResponse<?> upload(@RequestParam(value = "file") MultipartFile file) throws IOException {
@@ -37,7 +39,7 @@ public class FileController {
return ApiResponse.success(url);
}
// 删除文件
@ApiOperation(value = "删除文件")
@PostMapping("/delete")
@IgnoreToken
public ApiResponse<?> delete(@RequestParam(value = "fileName") String fileName) throws IOException {

View File

@@ -1,112 +0,0 @@
package com.ycwl.basic.controller;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckReqDTO;
import com.ycwl.basic.model.pc.videoreview.dto.VideoPurchaseCheckRespDTO;
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewAddReqDTO;
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewListReqDTO;
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewRespDTO;
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewStatisticsRespDTO;
import com.ycwl.basic.service.VideoReviewService;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.URLEncoder;
/**
* 视频评价Controller
* 管理端使用,通过token角色控制权限
*/
@Slf4j
@RestController
@RequestMapping("/api/video-review/v1")
public class VideoReviewController {
@Autowired
private VideoReviewService videoReviewService;
/**
* 新增视频评价
*
* @param reqDTO 评价信息
* @return 评价ID
*/
@PostMapping("/add")
public ApiResponse<Long> addReview(@RequestBody VideoReviewAddReqDTO reqDTO) {
log.info("新增视频评价,videoId: {}", reqDTO.getVideoId());
Long reviewId = videoReviewService.addReview(reqDTO);
return ApiResponse.success(reviewId);
}
/**
* 分页查询评价列表
*
* @param reqDTO 查询条件
* @return 分页结果
*/
@GetMapping("/list")
public ApiResponse<PageInfo<VideoReviewRespDTO>> getReviewList(VideoReviewListReqDTO reqDTO) {
log.info("查询视频评价列表,pageNum: {}, pageSize: {}", reqDTO.getPageNum(), reqDTO.getPageSize());
PageInfo<VideoReviewRespDTO> pageInfo = videoReviewService.getReviewList(reqDTO);
return ApiResponse.success(pageInfo);
}
/**
* 获取评价统计数据
*
* @return 统计结果
*/
@GetMapping("/statistics")
public ApiResponse<VideoReviewStatisticsRespDTO> getStatistics() {
log.info("获取视频评价统计数据");
VideoReviewStatisticsRespDTO statistics = videoReviewService.getStatistics();
return ApiResponse.success(statistics);
}
/**
* 导出评价数据到Excel
*
* @param reqDTO 查询条件
* @param response HTTP响应
*/
@GetMapping("/export")
public void exportReviews(VideoReviewListReqDTO reqDTO, HttpServletResponse response) {
log.info("导出视频评价数据");
try {
// 设置响应头
String fileName = "video_reviews_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 导出数据
videoReviewService.exportReviews(reqDTO, response.getOutputStream());
response.getOutputStream().flush();
} catch (IOException e) {
log.error("导出视频评价数据失败", e);
throw new RuntimeException("导出失败: " + e.getMessage());
}
}
/**
* 检查视频是否已被购买
* 购买条件:
* 1. 直接购买视频(order_item中goods_type=0且goods_id=视频id)
* 2. 购买整个模板(order的face_id与video关联的task的face_id相同,goods_type=-1,goods_id为video的templateId)
*
* @param reqDTO 查询条件
* @return 购买状态及订单ID列表
*/
@PostMapping("/check-purchase")
public ApiResponse<VideoPurchaseCheckRespDTO> checkVideoPurchase(@RequestBody VideoPurchaseCheckReqDTO reqDTO) {
log.info("检查视频购买状态,videoId: {}", reqDTO.getVideoId());
VideoPurchaseCheckRespDTO respDTO = videoReviewService.checkVideoPurchase(reqDTO);
return ApiResponse.success(respDTO);
}
}

View File

@@ -1,262 +0,0 @@
package com.ycwl.basic.controller.extern;
import cn.hutool.http.HttpUtil;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.image.enhancer.adapter.BceImageEnhancer;
import com.ycwl.basic.image.enhancer.entity.BceEnhancerConfig;
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
import com.ycwl.basic.image.pipeline.stages.DownloadStage;
import com.ycwl.basic.image.pipeline.stages.ImageEnhanceStage;
import com.ycwl.basic.image.pipeline.stages.ImageSRStage;
import com.ycwl.basic.image.pipeline.stages.SourcePhotoUpdateStage;
import com.ycwl.basic.image.pipeline.stages.CleanupStage;
import com.ycwl.basic.pipeline.core.Pipeline;
import com.ycwl.basic.pipeline.core.PipelineBuilder;
import com.ycwl.basic.mapper.AioDeviceMapper;
import com.ycwl.basic.mapper.MemberMapper;
import com.ycwl.basic.model.aio.entity.AioDeviceBannerEntity;
import com.ycwl.basic.model.aio.entity.AioDeviceEntity;
import com.ycwl.basic.model.aio.entity.AioDevicePriceConfigEntity;
import com.ycwl.basic.model.aio.req.AioDeviceCreateOrderReq;
import com.ycwl.basic.model.aio.resp.AioDeviceCreateOrderResp;
import com.ycwl.basic.model.aio.resp.AioDeviceInfoResp;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
import com.ycwl.basic.model.mobile.goods.GoodsReqQuery;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.member.entity.MemberEntity;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.service.aio.AioDeviceService;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.service.pc.SourceService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.SnowFlakeUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@IgnoreToken
@RestController
@RequestMapping("/api/aio")
public class AioDeviceController {
@Autowired
private GoodsService goodsService;
@Autowired
private FaceService faceService;
@Autowired
private MemberMapper memberMapper;
@Autowired
private AioDeviceMapper aioDeviceMapper;
@Autowired
private AioDeviceService aioDeviceService;
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private SourceService sourceService;
@GetMapping("/info")
public ApiResponse<AioDeviceInfoResp> getDeviceInfo(HttpServletRequest request) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
List<AioDeviceBannerEntity> banners = aioDeviceMapper.getBannerByDeviceId(aioDevice.getId());
return ApiResponse.success(new AioDeviceInfoResp(aioDevice, banners));
}
@GetMapping("/banners")
public ApiResponse<List<AioDeviceBannerEntity>> getBanners(HttpServletRequest request) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
List<AioDeviceBannerEntity> banners = aioDeviceMapper.getBannerByDeviceId(aioDevice.getId());
return ApiResponse.success(banners);
}
@GetMapping("/config")
public ApiResponse<AioDevicePriceConfigEntity> getPriceConfig(HttpServletRequest request) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
AioDevicePriceConfigEntity config = aioDeviceMapper.getPriceConfigByDeviceId(aioDevice.getId());
return ApiResponse.success(config);
}
@PostMapping("/faceUpload")
public ApiResponse<FaceRecognizeResp> faceUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
MemberEntity memberEntity = new MemberEntity();
memberEntity.setScenicId(aioDevice.getScenicId());
memberEntity.setCreateDate(new Date());
memberEntity.setId(SnowFlakeUtil.getLongId());
memberEntity.setNickname("用户");
memberMapper.add(memberEntity);
FaceRecognizeResp resp = faceService.faceUpload(file, aioDevice.getScenicId(), memberEntity.getId(), "");
// 尝试超分
new Thread(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
return;
}
GoodsReqQuery query = new GoodsReqQuery();
query.setSourceType(2);
query.setFaceId(resp.getFaceId());
List<GoodsDetailVO> sourcePhotoList = goodsService.sourceGoodsList(query);
if (sourcePhotoList == null || sourcePhotoList.isEmpty()) {
log.info("无源图片");
redisTemplate.opsForValue().set("aio:faceId:"+resp.getFaceId().toString()+":pass", "1", 1, TimeUnit.DAYS);
return;
}
log.info("超分开始!共{}张图片待处理", sourcePhotoList.size());
sourcePhotoList.forEach(photo -> {
if (StringUtils.contains(photo.getUrl(), "_q_")) {
log.debug("跳过已增强的图片: {}", photo.getUrl());
return;
}
try {
// 创建超分Pipeline
Pipeline<PhotoProcessContext> superResolutionPipeline = createSuperResolutionPipeline(photo.getGoodsId());
// 使用静态工厂方法创建Context
PhotoProcessContext context = PhotoProcessContext.forSuperResolution(
photo.getGoodsId(), photo.getUrl(), photo.getScenicId()
);
// 启用图像增强和超分的Stage
context.enableStage("image_enhance");
context.enableStage("image_sr");
// 执行Pipeline
boolean success = superResolutionPipeline.execute(context);
if (success) {
log.info("超分成功: {} -> {}", photo.getUrl(), context.getResultUrl());
} else {
log.error("超分失败: {}", photo.getGoodsId());
}
} catch (Exception e) {
log.error("超分失败:{}", photo.getGoodsId(), e);
}
});
redisTemplate.opsForValue().set("aio:faceId:"+sourcePhotoList.getFirst().getFaceId().toString()+":pass", "1", 1, TimeUnit.DAYS);
}).start();
return ApiResponse.success(resp);
}
// 人脸信息
@GetMapping("/{faceId}")
public ApiResponse<FaceRespVO> faceInfo(@PathVariable Long faceId) {
return faceService.getById(faceId);
}
@GetMapping("/face/{faceId}/check")
public ApiResponse<Boolean> faceCheck(@PathVariable Long faceId) {
if (redisTemplate.hasKey("aio:faceId:"+faceId.toString()+":pass")) {
return ApiResponse.success(true);
} else {
return ApiResponse.success(false);
}
}
@GetMapping("/face/{faceId}/bindWxaCode")
public ApiResponse<String> bindWxaCode(@PathVariable Long faceId) {
return ApiResponse.success(faceService.bindWxaCode(faceId));
}
// 照片商品列表
@GetMapping("/{faceId}/photo")
public ApiResponse<List<GoodsDetailVO>> sourceGoodsList(@PathVariable Long faceId) {
GoodsReqQuery query = new GoodsReqQuery();
query.setSourceType(2);
query.setFaceId(faceId);
List<GoodsDetailVO> goodsDetailVOS = goodsService.sourceGoodsList(query);
return ApiResponse.success(goodsDetailVOS);
}
// 创建订单
@PostMapping("/order")
public ApiResponse<AioDeviceCreateOrderResp> createOrder(HttpServletRequest request, @RequestBody AioDeviceCreateOrderReq req) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
return ApiResponse.success(aioDeviceService.createOrder(aioDevice, req));
}
// 查询订单
@GetMapping("/order/{orderId}")
public ApiResponse<PayResponse> queryOrder(HttpServletRequest request, @PathVariable("orderId") Long orderId) {
String deviceId = request.getHeader("X-DeviceId");
AioDeviceEntity aioDevice = aioDeviceMapper.getByKey(deviceId);
if (aioDevice == null) {
return ApiResponse.fail("设备不存在");
}
return ApiResponse.success(orderService.queryOrder(orderId));
}
/**
* 创建源图片超分辨率增强Pipeline
*
* @param sourceId 源图片ID
* @return 超分Pipeline
*/
private Pipeline<PhotoProcessContext> createSuperResolutionPipeline(Long sourceId) {
// 创建带有百度云配置的ImageEnhanceStage
BceEnhancerConfig config = new BceEnhancerConfig();
config.setQps(1);
config.setAppId("119554288");
config.setApiKey("OX6QoijgKio3eVtA0PiUVf7f");
config.setSecretKey("dYatXReVriPeiktTjUblhfubpcmYfuMk");
return new PipelineBuilder<PhotoProcessContext>("SourcePhotoSuperResolutionPipeline")
.addStage(new DownloadStage()) // 1. 下载图片
.addStage(new ImageEnhanceStage(config)).addStage(new ImageSRStage(config)) // 2. 图像增强(超分)
.addStage(new SourcePhotoUpdateStage(sourceService, sourceId)) // 3. 上传并更新数据库
.addStage(new CleanupStage()) // 4. 清理临时文件
.build();
}
private BceImageEnhancer getEnhancer() {
BceImageEnhancer enhancer = new BceImageEnhancer();
BceEnhancerConfig config = new BceEnhancerConfig();
config.setQps(1);
config.setAppId("119554288");
config.setApiKey("OX6QoijgKio3eVtA0PiUVf7f");
config.setSecretKey("dYatXReVriPeiktTjUblhfubpcmYfuMk");
enhancer.setConfig(config);
return enhancer;
}
}

View File

@@ -1,83 +0,0 @@
package com.ycwl.basic.controller.extern;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.custom.req.AliyunCallbackReq;
import com.ycwl.basic.model.custom.req.CreateUploadTaskReq;
import com.ycwl.basic.model.custom.req.UploadCompleteReq;
import com.ycwl.basic.model.custom.req.UploadFailedReq;
import com.ycwl.basic.model.custom.resp.CreateUploadTaskResp;
import com.ycwl.basic.service.custom.CustomUploadTaskService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/extern/custom-device")
@IgnoreToken
public class CustomDeviceController {
@Autowired
private CustomUploadTaskService customUploadTaskService;
@PostMapping("/upload/create")
public ApiResponse<CreateUploadTaskResp> createUploadTask(@RequestBody CreateUploadTaskReq req) {
try {
CreateUploadTaskResp resp = customUploadTaskService.createUploadTask(req);
return ApiResponse.success(resp);
} catch (Exception e) {
log.error("创建上传任务失败", e);
return ApiResponse.fail(e.getMessage());
}
}
@PostMapping("/upload/complete")
public ApiResponse<String> uploadComplete(@RequestBody UploadCompleteReq req) {
try {
customUploadTaskService.completeUpload(req.getAccessKey(), req.getTaskId());
return ApiResponse.success("上传完成,人脸识别任务已提交");
} catch (Exception e) {
log.error("上传完成处理失败", e);
return ApiResponse.fail(e.getMessage());
}
}
@PostMapping("/upload/failed")
public ApiResponse<String> uploadFailed(@RequestBody UploadFailedReq req) {
try {
customUploadTaskService.markTaskFailed(req.getAccessKey(), req.getTaskId(), req.getErrorMsg());
return ApiResponse.success("任务已标记为失败");
} catch (Exception e) {
log.error("标记任务失败处理异常", e);
return ApiResponse.fail(e.getMessage());
}
}
@PostMapping("/aliyun/mps/callback")
public ApiResponse<String> aliyunCallback(@RequestBody AliyunCallbackReq req) {
try {
customUploadTaskService.handleAliyunCallback(req.getJobId(), req.getStatus());
return ApiResponse.success("回调处理完成");
} catch (Exception e) {
log.error("阿里云回调处理失败", e);
return ApiResponse.fail(e.getMessage());
}
}
@GetMapping("/aliyun/mps/callback")
public ApiResponse<String> aliyunCallback(@RequestParam("jobId") String jobId, @RequestParam("status") String status) {
try {
customUploadTaskService.handleAliyunCallback(jobId, status);
return ApiResponse.success("回调处理完成");
} catch (Exception e) {
log.error("阿里云回调处理失败", e);
return ApiResponse.fail(e.getMessage());
}
}
}

View File

@@ -1,7 +1,7 @@
package com.ycwl.basic.controller.extern;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.utils.JacksonUtil;
import com.alibaba.fastjson.JSON;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.MemberMapper;
@@ -14,10 +14,9 @@ import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.member.entity.MemberEntity;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.AppScenicService;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.task.impl.TaskTaskServiceImpl;
@@ -58,9 +57,11 @@ public class LyCompatibleController {
@Autowired
private VideoRepository videoRepository;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private VideoMapper videoMapper;
@Autowired
private VideoTaskRepository videoTaskRepository;
private TaskTaskServiceImpl taskTaskServiceImpl;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@PostMapping("sendPhoto")
@IgnoreToken
@@ -83,7 +84,7 @@ public class LyCompatibleController {
if (StringUtils.isBlank(scid)) {
return R.error("景区ID为空!");
}
// if (Strings.CS.equals("288",scid)) {
// if (StringUtils.equals("288",scid)) {
scenicId = 3955650120997015552L;
// } else {
// scenicId = 3946669713328836608L;
@@ -109,7 +110,7 @@ public class LyCompatibleController {
}
FaceRecognizeResp resp;
try {
resp = faceService.faceUpload(file, scenicId, member.getId(), "");
resp = faceService.faceUpload(file, scenicId, member.getId());
} catch (Exception e) {
return R.error("上传失败!报错:"+e.getMessage());
}
@@ -179,7 +180,7 @@ public class LyCompatibleController {
}
String openId = headersMap.get("client");
if (redisTemplate.hasKey("ly:"+openId)) {
return JacksonUtil.parseObject(redisTemplate.opsForValue().get("ly:"+openId), R.class);
return JSON.parseObject(redisTemplate.opsForValue().get("ly:"+openId), R.class);
}
MemberRespVO member = memberMapper.getByOpenId(openId);
if (member == null) {
@@ -197,37 +198,25 @@ public class LyCompatibleController {
R response = R.ok();
if (collect.get(0) == null) {
response.put("isgen", 1)
.put("face_id", faceVO.getId().toString())
.put("newvideo", Collections.emptyList())
.put("newuservideo", Collections.emptyList());
return response;
}
List<Map<String, Object>> videoList = collect.get(0).stream().collect(Collectors.groupingBy(ContentPageVO::getTemplateId))
.values().stream()
.map(contentPageVOs -> {
ContentPageVO contentPageVO = contentPageVOs.stream().filter(vo -> vo.getContentId() != null).findFirst().orElse(null);
if (contentPageVO == null) {
return null;
}
VideoEntity videoRespVO = videoRepository.getVideo(contentPageVO.getContentId());
if (videoRespVO == null) {
return null;
}
.values().stream().map(contentPageVOs -> {
ContentPageVO contentPageVO = contentPageVOs.getFirst();
Map<String, Object> map = new HashMap<>();
VideoEntity videoRespVO = videoRepository.getVideo(contentPageVO.getContentId());
map.put("id", videoRespVO.getId().toString());
map.put("task_id", videoRespVO.getTaskId().toString());
if (videoRespVO.getFaceId() != null) {
map.put("face_id", String.valueOf(videoRespVO.getFaceId()));
}
map.put("template_cover_image", contentPageVO.getTemplateCoverUrl());
Date taskShotDate = videoTaskRepository.getTaskShotDate(videoRespVO.getTaskId());
Date taskShotDate = taskTaskServiceImpl.getTaskShotDate(videoRespVO.getTaskId());
map.put("shoottime", DateUtil.format(taskShotDate, "yyyy-MM-dd HH:mm"));
map.put("openid", openId);
map.put("scenicname", contentPageVO.getScenicName());
map.put("title", contentPageVO.getName());
map.put("ossurldm", videoRespVO.getVideoUrl());
return map;
}).filter(java.util.Objects::nonNull).collect(Collectors.toList());
}).collect(Collectors.toList());
GoodsReqQuery goodsReqQuery = new GoodsReqQuery();
goodsReqQuery.setFaceId(faceVO.getId());
goodsReqQuery.setSourceType(1);
@@ -235,7 +224,6 @@ public class LyCompatibleController {
List<Map<String, Object>> userVideoList = sourceGoodsList.stream().map(goodsDetailVO -> {
Map<String, Object> map = new HashMap<>();
map.put("id", goodsDetailVO.getGoodsId().toString());
map.put("face_id", String.valueOf(goodsDetailVO.getFaceId()));
map.put("openid", openId);
map.put("template_cover_image", goodsDetailVO.getUrl());
map.put("scenicname", goodsDetailVO.getScenicName());
@@ -245,11 +233,9 @@ public class LyCompatibleController {
}).collect(Collectors.toList());
response
.put("isgen", taskStatusVO.getStatus() == 1 ? 0 : 1)
.put("member_id", faceVO.getMemberId().toString())
.put("face_id", faceVO.getId().toString())
.put("newvideo", videoList)
.put("newuservideo", userVideoList);
redisTemplate.opsForValue().set("ly:"+openId, JacksonUtil.toJSONString(response), 5, TimeUnit.SECONDS);
redisTemplate.opsForValue().set("ly:"+openId, JSON.toJSONString(response), 5, TimeUnit.SECONDS);
log.info("> {}", response);
return response;
}

View File

@@ -1,81 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
import com.ycwl.basic.service.mobile.AppAiCamService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* AI相机相关接口
*/
@Slf4j
@RestController
@RequestMapping("/api/mobile/ai_cam/v1")
@RequiredArgsConstructor
public class AppAiCamController {
private final AppAiCamService appAiCamService;
/**
* 根据faceId获取AI相机识别到的商品列表
* @param faceId 人脸ID
* @return 商品详情列表
*/
@GetMapping("/{faceId}/content")
public ApiResponse<List<GoodsDetailVO>> getAiCamGoods(@PathVariable Long faceId) {
try {
List<GoodsDetailVO> goods = appAiCamService.getAiCamGoodsByFaceId(faceId);
return ApiResponse.success(goods);
} catch (Exception e) {
log.error("获取AI相机商品失败: faceId={}", faceId, e);
return ApiResponse.fail("获取商品列表失败");
}
}
/**
* 批量添加会员与source的关联关系
* @param faceId 人脸ID
* @param sourceIds source ID列表
* @return 添加结果
*/
@PostMapping("/{faceId}/relations")
public ApiResponse<String> addMemberSourceRelations(
@PathVariable Long faceId,
@RequestBody List<Long> sourceIds
) {
try {
int count = appAiCamService.addMemberSourceRelations(faceId, sourceIds);
return ApiResponse.success("成功添加" + count + "条关联记录");
} catch (IllegalArgumentException e) {
log.warn("添加关联失败: faceId={}, error={}", faceId, e.getMessage());
return ApiResponse.fail(e.getMessage());
} catch (Exception e) {
log.error("添加关联失败: faceId={}", faceId, e);
return ApiResponse.fail("添加关联失败");
}
}
/**
* 使用人脸样本创建或获取Face记录
* @param faceSampleId 人脸样本ID
* @return 人脸识别响应
*/
@GetMapping("/useSample/{faceSampleId}")
public ApiResponse<FaceRecognizeResp> useSample(@PathVariable Long faceSampleId) {
try {
JwtInfo worker = JwtTokenUtil.getWorker();
FaceRecognizeResp resp = appAiCamService.useSample(worker.getUserId(), faceSampleId);
return ApiResponse.success(resp);
} catch (Exception e) {
log.error("使用人脸样本失败: faceSampleId={}", faceSampleId, e);
return ApiResponse.fail("使用人脸样本失败: " + e.getMessage());
}
}
}

View File

@@ -1,98 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.chat.*;
import com.ycwl.basic.service.mobile.FaceChatService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import java.util.concurrent.CompletableFuture;
/**
* 小程序人脸智能聊天接口。
*/
@Slf4j
@RestController
@RequestMapping("/api/mobile/chat/v1")
@RequiredArgsConstructor
public class AppChatController {
private final FaceChatService faceChatService;
/**
* 获取或创建会话(同一人脸只保留一条)。
*/
@PostMapping("/faces/{faceId}/conversation")
public ApiResponse<ChatConversationVO> createConversation(@PathVariable Long faceId) {
JwtInfo worker = JwtTokenUtil.getWorker();
ChatConversationVO vo = faceChatService.getOrCreateConversation(faceId, worker.getUserId());
return ApiResponse.success(vo);
}
/**
* 同步发送消息,适用于短回复或前端自行轮询。
*/
@PostMapping("/conversations/{conversationId}/messages")
public ApiResponse<ChatSendMessageResp> sendMessage(@PathVariable Long conversationId,
@RequestBody ChatSendMessageReq req) {
JwtInfo worker = JwtTokenUtil.getWorker();
ChatSendMessageResp resp = faceChatService.sendMessage(conversationId, worker.getUserId(),
req.getContent(), req.getTraceId());
return ApiResponse.success(resp);
}
/**
* 流式返回,使用 HTTP chunked。小程序侧用 wx.request 的 onChunkReceived 消费。
*/
@PostMapping(value = "/conversations/{conversationId}/messages/stream", produces = "text/plain;charset=UTF-8")
public ResponseBodyEmitter streamMessage(@PathVariable Long conversationId,
@RequestBody ChatSendMessageReq req) {
JwtInfo worker = JwtTokenUtil.getWorker();
ResponseBodyEmitter emitter = new ResponseBodyEmitter(30_000L);
CompletableFuture.runAsync(() -> {
try {
faceChatService.sendMessageStream(
conversationId,
worker.getUserId(),
req.getContent(),
req.getTraceId(),
chunk -> {
try {
emitter.send(chunk, new MediaType("text", "plain", java.nio.charset.StandardCharsets.UTF_8));
} catch (Exception e) {
emitter.completeWithError(e);
}
});
emitter.complete();
} catch (Exception e) {
log.error("streamMessage error", e);
emitter.completeWithError(e);
}
});
return emitter;
}
/**
* 查询历史消息,cursor 为最后一条 seq,limit 为条数。
*/
@GetMapping("/conversations/{conversationId}/messages")
public ApiResponse<ChatMessagePageResp> listMessages(@PathVariable Long conversationId,
@RequestParam(value = "cursor", required = false) Integer cursor,
@RequestParam(value = "limit", required = false) Integer limit) {
JwtInfo worker = JwtTokenUtil.getWorker();
ChatMessagePageResp resp = faceChatService.listMessages(conversationId, cursor, limit, worker.getUserId());
return ApiResponse.success(resp);
}
@PostMapping("/conversations/{conversationId}/close")
public ApiResponse<String> closeConversation(@PathVariable Long conversationId) {
JwtInfo worker = JwtTokenUtil.getWorker();
faceChatService.closeConversation(conversationId, worker.getUserId());
return ApiResponse.success("OK");
}
}

View File

@@ -1,113 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.model.mobile.claim.ClaimReq;
import com.ycwl.basic.model.mobile.claim.ClaimResp;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.pricing.dto.CouponClaimRequest;
import com.ycwl.basic.pricing.dto.CouponClaimResult;
import com.ycwl.basic.pricing.dto.req.VoucherPrintReq;
import com.ycwl.basic.pricing.dto.resp.VoucherPrintResp;
import com.ycwl.basic.pricing.enums.CouponType;
import com.ycwl.basic.pricing.service.ICouponService;
import com.ycwl.basic.pricing.service.VoucherPrintService;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.utils.ApiResponse;
import lombok.AllArgsConstructor;
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 java.math.BigDecimal;
import java.math.RoundingMode;
@RestController
@RequestMapping("/api/mobile/claim/v1")
@AllArgsConstructor
public class AppClaimController {
private final FaceRepository faceRepository;
private final ScenicRepository scenicRepository;
private final VoucherPrintService voucherPrintService;
private final ICouponService couponService;
@PostMapping("tryClaim")
public ApiResponse<ClaimResp> tryClaim(@RequestBody ClaimReq req) {
FaceEntity face = faceRepository.getFace(req.getFaceId());
if (face == null) {
return ApiResponse.fail("请选择人脸");
}
ClaimResp claimResp = new ClaimResp();
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
if (Boolean.TRUE.equals(scenicConfig.getBoolean("voucher_enable"))) {
// 可以领券
VoucherPrintResp voucherPrintResp = voucherPrintService.queryPrintedVoucher(face.getId());
if (voucherPrintResp == null) {
// 打印
if (req.getMorphId() != null) {
voucherPrintResp = voucherPrintService.printVoucherTicket(new VoucherPrintReq(face.getId(), req.getMorphId(), face.getScenicId()));
}
}
if (voucherPrintResp != null) {
claimResp.setHasCoupon(false);
claimResp.setHasPrint(true);
claimResp.setPrintCode(voucherPrintResp.getCode());
claimResp.setPrintType(voucherPrintResp.getType());
return ApiResponse.success(claimResp);
}
}
if (Boolean.TRUE.equals(scenicConfig.getBoolean("booking_enable"))) {
VoucherPrintResp voucherPrintResp = voucherPrintService.queryPrintedVoucher(face.getId());
if (voucherPrintResp == null) {
// 打印
if (req.getMorphId() != null) {
voucherPrintResp = voucherPrintService.printBookingTicket(new VoucherPrintReq(face.getId(), req.getMorphId(), face.getScenicId()));
}
}
if (voucherPrintResp != null) {
claimResp.setHasCoupon(false);
claimResp.setHasPrint(true);
claimResp.setPrintCode(voucherPrintResp.getCode());
claimResp.setPrintType(voucherPrintResp.getType());
return ApiResponse.success(claimResp);
}
}
if (req.getType() != null) {
// 第几次进入
Integer couponId = scenicConfig.getInteger("coupon_id_for_type_" + req.getType());
if (couponId != null) {
// 可以领券
CouponClaimRequest request = new CouponClaimRequest(face.getMemberId(), Long.valueOf(couponId));
CouponClaimResult claimResult = couponService.claimCoupon(request);
if (claimResult.isSuccess()) {
// 领到了
claimResp.setHasCoupon(true);
switch (claimResult.getCoupon().getCouponType()) {
case CouponType.PERCENTAGE:
claimResp.setCouponType("折扣优惠券");
claimResp.setCouponDesc("" + (BigDecimal.valueOf(1).setScale(2, RoundingMode.HALF_UP).subtract(claimResult.getCoupon().getDiscountValue())).multiply(BigDecimal.valueOf(10)) + "");
break;
case CouponType.FIXED_AMOUNT:
if (claimResult.getCoupon().getMinAmount().compareTo(BigDecimal.ZERO) > 0) {
claimResp.setCouponType("满减优惠券");
claimResp.setCouponDesc("" + claimResult.getCoupon().getMinAmount() + "" + claimResult.getCoupon().getDiscountValue());
} else {
claimResp.setCouponType("直减优惠券");
claimResp.setCouponDesc("直减" + claimResult.getCoupon().getDiscountValue());
}
break;
default:
claimResp.setCouponType("普通优惠券");
break;
}
claimResp.setCouponDesc(scenicConfig.getString("coupon_desc_for_type_" + req.getType(), "专属折扣券"));
claimResp.setCouponCountdown(scenicConfig.getString("coupon_countdown_for_type_" + req.getType(), "送你优惠,保存美好!"));
return ApiResponse.success(claimResp);
}
}
}
return ApiResponse.fail("异常");
}
}

View File

@@ -0,0 +1,9 @@
package com.ycwl.basic.controller.mobile;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/mobile/controller/v1")
public class AppCouponController {
}

View File

@@ -1,19 +1,14 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.face.FaceStatusResp;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.mobile.face.FaceRecognitionUpdateReq;
import com.ycwl.basic.model.mobile.face.FaceRecognitionDetailVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -26,13 +21,12 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/mobile/face/v1")
// 用户人脸相关接口
public class AppFaceController {
@Api(tags = "用户人脸相关接口")
public class
AppFaceController {
@Autowired
private FaceService faceService;
@Autowired
private FaceRepository faceRepository;
/**
* 1、上传人脸照片
@@ -43,15 +37,13 @@ public class AppFaceController {
* @param scenicId
* @return
*/
// 人脸照片上传
@ApiOperation("人脸照片上传")
@PostMapping("/faceUPload")
public ApiResponse<FaceRecognizeResp> faceUpload(@RequestParam("file")MultipartFile file,
@RequestParam(value = "scene", defaultValue = "", required = false) String scene,
@RequestParam("scenicId") Long scenicId) {
public ApiResponse<FaceRecognizeResp> faceUpload(@RequestParam("file")MultipartFile file, @RequestParam("scenicId") Long scenicId) {
//获取用户id
JwtInfo worker = JwtTokenUtil.getWorker();
Long userId = worker.getUserId();
FaceRecognizeResp resp = faceService.faceUpload(file, scenicId, userId, scene);
FaceRecognizeResp resp = faceService.faceUpload(file, scenicId, userId);
return ApiResponse.success(resp);
}
@@ -59,23 +51,17 @@ public class AppFaceController {
public ApiResponse<List<FaceRespVO>> list(@PathVariable("scenicId") String scenicId) {
JwtInfo worker = JwtTokenUtil.getWorker();
Long userId = worker.getUserId();
List<FaceRespVO> list = faceService.listByUser(userId, Long.parseLong(scenicId));
List<FaceRespVO> list = faceService.listByUser(userId, scenicId);
return ApiResponse.success(list);
}
@GetMapping("/{faceId}")
public ApiResponse<FaceEntity> getById(@PathVariable("faceId") Long faceId) {
FaceEntity face = faceRepository.getFace(faceId);
return ApiResponse.success(face);
public ApiResponse<FaceRespVO> getById(@PathVariable("faceId") Long faceId) {
return faceService.getById(faceId);
}
@DeleteMapping("/{faceId}")
public ApiResponse<String> deleteFace(@PathVariable("faceId") Long faceId) {
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
throw new BaseException("人脸数据不存在");
}
return faceService.deleteFace(faceId);
}
@@ -86,54 +72,10 @@ public class AppFaceController {
}
// 景区视频源素材列表
@ApiOperation("景区视频源素材列表")
@GetMapping("/{faceId}/contentList")
public ApiResponse<List<ContentPageVO>> contentList(@PathVariable Long faceId) {
List<ContentPageVO> contentPageVOS = faceService.faceContentList(faceId);
return ApiResponse.success(contentPageVOS);
}
// 绑定人脸
@PostMapping("/{faceId}/bind")
public ApiResponse<String> bind(@PathVariable Long faceId) {
// dummy item
faceService.matchFaceId(faceId, true);
return ApiResponse.success("OK");
}
@GetMapping("/{faceId}/status")
public ApiResponse<FaceStatusResp> status(@PathVariable Long faceId) {
return ApiResponse.success(faceService.getFaceStatus(faceId));
}
@GetMapping("/{faceId}/extraCheck")
public ApiResponse<Boolean> hasExtraCheck(@PathVariable Long faceId) {
return ApiResponse.success(faceService.checkHasExtraCheck(faceId));
}
@GetMapping("/{faceId}/queryOtherFace")
public ApiResponse<List<FaceSampleEntity>> queryOtherFace(@PathVariable Long faceId) {
return ApiResponse.success(faceService.getLowMatchedFaceSamples(faceId));
}
@PostMapping("/{faceId}/queryOtherFace")
public ApiResponse<String> queryOtherFace(@PathVariable Long faceId, @RequestBody List<Long> faceIds) {
faceService.matchCustomFaceId(faceId, faceIds);
return ApiResponse.success("OK");
}
@PutMapping("/{faceId}/recognition")
public ApiResponse<?> updateRecognition(@PathVariable Long faceId,
@RequestBody FaceRecognitionUpdateReq req) {
req.setFaceId(faceId);
faceService.updateRecognition(req);
return ApiResponse.success("OK");
}
@GetMapping("/{faceId}/recognition/detail")
public ApiResponse<FaceRecognitionDetailVO> recognitionDetail(@PathVariable Long faceId) {
return ApiResponse.success(faceService.getRecognitionDetail(faceId));
}
}

View File

@@ -4,11 +4,11 @@ import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.exception.CheckTokenException;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.goods.*;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -21,38 +21,38 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/mobile/goods/v1")
// 商品相关接口
@Api(tags = "商品相关接口")
public class AppGoodsController {
@Autowired
private GoodsService goodsService;
@Autowired
private FaceService faceService;
// 商品列表
@ApiOperation("商品列表")
@PostMapping("/goodsList")
public ApiResponse<List<GoodsPageVO>> goodsList(@RequestBody GoodsReqQuery query) {
JwtInfo worker = JwtTokenUtil.getWorker();
Long userId = worker.getUserId();
List<FaceRespVO> faceRespVOS = faceService.listByUser(userId, query.getScenicId());
List<Long> faceIds = faceRespVOS.stream().map(FaceRespVO::getId).toList();
return goodsService.listGoodsByFaceIdList(faceIds, query.getIsBuy(), query.getScenicId());
return goodsService.goodsList(query);
}
// 源素材(原片/照片)商品列表
@ApiOperation("源素材(原片/照片)商品列表")
@PostMapping("/sourceGoodsList")
public ApiResponse<List<GoodsDetailVO>> sourceGoodsList(@RequestBody GoodsReqQuery query) {
List<GoodsDetailVO> goodsDetailVOS = goodsService.sourceGoodsList(query);
return ApiResponse.success(goodsDetailVOS);
}
// 源素材(原片/照片)商品数量
@ApiOperation("源素材(原片/照片)商品数量")
@PostMapping("/sourceGoodsCount")
public ApiResponse<Integer> sourceGoodsCount(@RequestBody GoodsReqQuery query) {
Integer count = goodsService.sourceGoodsCount(query);
return ApiResponse.success(count);
}
@PostMapping("/sourceGoodsList/preview")
public ApiResponse<List<GoodsUrlVO>> sourceGoodsListPreview(@RequestBody GoodsReqQuery query) {
List<GoodsUrlVO> goodsUrlList = goodsService.sourceGoodsListPreview(query);
return ApiResponse.success(goodsUrlList);
}
@PostMapping("/sourceGoodsList/download")
public ApiResponse<List<GoodsUrlVO>> sourceGoodsListDownload(@RequestBody GoodsReqQuery query) {
List<GoodsUrlVO> goodsUrlList = goodsService.sourceGoodsListDownload(query);
@@ -60,7 +60,7 @@ public class AppGoodsController {
}
// 成片vlog商品详情
@ApiOperation("成片vlog商品详情")
@GetMapping("/getVideoGoodsDetail/{videoId}")
@IgnoreToken
public ApiResponse<VideoGoodsDetailVO> videoGoodsDetail(@PathVariable("videoId") Long videoId) {
@@ -82,7 +82,7 @@ public class AppGoodsController {
*
* @return 0没有任务 1 合成中 2 合成成功
*/
// 查询用户当前景区的整体视频合成任务状态 0没有任务 1 合成中 2 合成成功
@ApiOperation("查询用户当前景区的整体视频合成任务状态 0没有任务 1 合成中 2 合成成功 ")
@GetMapping("/getTaskStatus/")
public ApiResponse<VideoTaskStatusVO> getAllTaskStatus() {
JwtInfo worker = JwtTokenUtil.getWorker();
@@ -95,21 +95,10 @@ public class AppGoodsController {
* @param templateId 模版id
* @return 1 合成中 2 合成成功
*/
// 查询用户当前景区的具体模版视频合成任务状态 1 合成中 2 合成成功
@ApiOperation("查询用户当前景区的具体模版视频合成任务状态 1 合成中 2 合成成功 ")
@GetMapping("/task/face/{faceId}/template/{templateId}")
public ApiResponse<VideoTaskStatusVO> getTemplateTaskStatus(@PathVariable("faceId") Long faceId, @PathVariable("templateId") Long templateId) {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(goodsService.getTaskStatusByTemplateId(faceId, templateId));
}
/**
* 检查视频是否可更新
*
* @param videoId 视频ID
* @return 视频更新检查结果
*/
@GetMapping("/video/{videoId}/updateCheck")
public ApiResponse<VideoUpdateCheckVO> checkVideoUpdate(@PathVariable("videoId") Long videoId) {
VideoUpdateCheckVO result = goodsService.checkVideoUpdate(videoId);
return ApiResponse.success(result);
}
}

View File

@@ -1,12 +1,13 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.service.mobile.AppMemberService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -17,7 +18,7 @@ import org.springframework.web.bind.annotation.*;
*/
@RestController
@RequestMapping("/api/mobile/member/v1")
// 用户相关接口
@Api(tags = "用户相关接口")
@Slf4j
public class AppMemberController {
@@ -31,29 +32,19 @@ public class AppMemberController {
* @return
* @throws Exception
*/
// 登录
@ApiOperation("登录")
@PostMapping("/{scenicId}/login")
@IgnoreToken
public ApiResponse<?> login(@PathVariable("scenicId") Long scenicId ,@RequestBody WeChatUserInfoDTO userInfoDTO) throws Exception {
return memberService.login(scenicId, userInfoDTO.getCode(), userInfoDTO);
}
@PostMapping("/undefined/login")
@IgnoreToken
public ApiResponse<?> loginUndefined(@RequestBody WeChatUserInfoDTO userInfoDTO) throws Exception {
return memberService.login(null, userInfoDTO.getCode(), userInfoDTO);
}
@PostMapping({"//login", "/login"})
@IgnoreToken
public ApiResponse<?> loginNoScenicId(@RequestBody WeChatUserInfoDTO userInfoDTO) throws Exception {
return memberService.login(null, userInfoDTO.getCode(), userInfoDTO);
}
/**
* 获取用户信息
*
* @return
*/
// 获取用户信息
@ApiOperation("获取用户信息")
@GetMapping("/getUserInfo")
public ApiResponse<MemberRespVO> getUserInfo() {
return memberService.getUserInfo();
@@ -65,20 +56,19 @@ public class AppMemberController {
* @param userInfoUpdateDTO
* @return
*/
// 修改用户信息
@ApiOperation("修改用户信息")
@PostMapping("/update")
public ApiResponse<?> update(@RequestBody WeChatUserInfoUpdateDTO userInfoUpdateDTO) {
Long userId = Long.parseLong(BaseContextHandler.getUserId());
return memberService.update(userId, userInfoUpdateDTO);
return memberService.update(userInfoUpdateDTO);
}
// 新增或修改景区服务通知状态
@ApiOperation("新增或修改景区服务通知状态")
@GetMapping("/updateScenicServiceNoticeStatus")
public ApiResponse<String> updateScenicServiceNoticeStatus(Long scenicId) {
return memberService.updateScenicServiceNoticeStatus(scenicId);
}
// 查看景区服务通知状态 0关闭 1开启
@ApiOperation("查看景区服务通知状态 0关闭 1开启")
@GetMapping("/getScenicServiceNoticeStatus")
public ApiResponse<Integer> getScenicServiceNoticeStatus(Long scenicId) {
return memberService.getScenicServiceNoticeStatus(scenicId);

View File

@@ -1,7 +1,6 @@
package com.ycwl.basic.controller.mobile;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.PriceBiz;
import com.ycwl.basic.constant.BaseContextHandler;
@@ -19,6 +18,8 @@ import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -30,7 +31,7 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/api/mobile/order/v1")
// 订单相关接口
@Api(tags = "订单相关接口")
public class AppOrderController {
@Autowired
@@ -42,7 +43,7 @@ public class AppOrderController {
@Autowired
private FaceMapper faceMapper;
// 用户端订单列表查询
@ApiOperation("用户端订单列表查询")
@PostMapping("/page")
public ApiResponse<PageInfo<OrderAppRespVO>> pageQuery(@RequestBody OrderAppPageReq orderReqQuery) {
String userId = BaseContextHandler.getUserId();
@@ -50,27 +51,26 @@ public class AppOrderController {
return orderService.appPageQuery(orderReqQuery);
}
// 用户端订单详情查询
@ApiOperation("用户端订单详情查询")
@GetMapping("getOrderDetails/{id}")
@IgnoreToken
public ApiResponse<OrderAppRespVO> getOrderDetails(@PathVariable("id") Long id) {
return orderService.appDetail(id);
}
// 用户端订单新增
@ApiOperation("用户端订单新增")
@PostMapping("/addOrder")
public ApiResponse<Map<String, Object>> addOrder(@RequestBody CreateOrderReqVO orderAddReq) throws Exception {
JwtInfo worker = JwtTokenUtil.getWorker();
return orderService.createOrder(worker.getUserId(), orderAddReq);
}
// 查询订单
@ApiOperation("查询订单")
@GetMapping("/queryOrder")
public ApiResponse<PayResponse> queryOrder(@RequestParam("orderId") Long orderId) {
return ApiResponse.success(orderService.queryOrder(orderId));
}
// 用户端打包订单新增
@ApiOperation("用户端打包订单新增")
@PostMapping("/addBatchOrder")
public ApiResponse<Map<String, Object>> addOrder(@RequestBody CreateBatchOrderReqVO batchOrderReqVO) throws Exception {
JwtInfo worker = JwtTokenUtil.getWorker();
@@ -78,7 +78,7 @@ public class AppOrderController {
}
// 获取用户订单数量
@ApiOperation("获取用户订单数量")
@GetMapping("/getUserOrderCount")
public ApiResponse<Integer> getUserOrderCount() {
Long userId = Long.parseLong(BaseContextHandler.getUserId());
@@ -86,16 +86,16 @@ public class AppOrderController {
return orderService.getOrderCountByUserId(userId);
}
// 发起退款
@ApiOperation(value = "发起退款", notes = "发起退款")
@PostMapping("/refundOrder")
public ApiResponse<?> refundOrder(@RequestBody RefundOrderReq refundOrderReq) {
return orderService.refundOrder(refundOrderReq);
}
@GetMapping("/scenic/{scenicId}/query")
public ApiResponse<IsBuyRespVO> isBuy(@PathVariable("scenicId") Long scenicId, @RequestParam("type") Integer type, @RequestParam("goodsId") Long goodsId, @RequestParam(value = "faceId", required = false) Long faceId) {
public ApiResponse<IsBuyRespVO> isBuy(@PathVariable("scenicId") Long scenicId, @RequestParam("type") Integer type, @RequestParam("goodsId") Long goodsId) {
Long userId = Long.parseLong(BaseContextHandler.getUserId());
return ApiResponse.success(orderBiz.isBuy(scenicId, userId, faceId, type, goodsId));
return ApiResponse.success(orderBiz.isBuy(userId, scenicId, type, goodsId));
}
@GetMapping("/scenic/{scenicId}/queryBatchPrice")
@@ -108,7 +108,7 @@ public class AppOrderController {
}
faceId = lastFaceByUserId.getId();
}
IsBuyBatchRespVO buy = priceBiz.isOnePriceBuy(userId, faceId, scenicId, type, goodsIds);
IsBuyBatchRespVO buy = priceBiz.isBuy(userId, faceId, scenicId, type, goodsIds);
if (buy == null) {
return ApiResponse.fail("该套餐暂未开放购买");
}

View File

@@ -1,435 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.pricing.dto.*;
import com.ycwl.basic.pricing.service.IPriceCalculationService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.PriceCacheService;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.dto.MobileOrderRequest;
import com.ycwl.basic.order.service.IOrderService;
import com.ycwl.basic.order.dto.OrderV2DetailResponse;
import com.ycwl.basic.order.dto.OrderV2ListResponse;
import com.ycwl.basic.order.dto.OrderV2PageRequest;
import com.ycwl.basic.order.dto.PaymentParamsRequest;
import com.ycwl.basic.order.dto.PaymentParamsResponse;
import com.ycwl.basic.order.dto.PaymentCallbackResponse;
import com.ycwl.basic.order.exception.DuplicatePurchaseException;
import com.ycwl.basic.order.factory.DuplicatePurchaseCheckerFactory;
import com.ycwl.basic.order.strategy.DuplicateCheckContext;
import com.ycwl.basic.order.strategy.IDuplicatePurchaseChecker;
import com.ycwl.basic.product.capability.DuplicateCheckStrategy;
import com.ycwl.basic.product.service.IProductTypeCapabilityService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 移动端订单控制器V2
* 包含价格查询和订单管理功能
*/
@Slf4j
@RestController
@RequestMapping("/api/mobile/order/v2")
@RequiredArgsConstructor
public class AppOrderV2Controller {
private final IPriceCalculationService priceCalculationService;
private final FaceService faceService;
private final PriceCacheService priceCacheService;
private final IOrderService orderService;
private final OrderService oldOrderService;
private final SourceMapper sourceMapper;
private final VideoMapper videoMapper;
private final VideoTaskRepository videoTaskRepository;
private final TemplateRepository templateRepository;
private final VideoRepository videoRepository;
private final RedisTemplate<String, Object> redisTemplate;
private final IProductTypeCapabilityService productTypeCapabilityService;
private final DuplicatePurchaseCheckerFactory duplicatePurchaseCheckerFactory;
/**
* 移动端价格计算
* 集成Redis缓存机制,提升查询性能
*/
@PostMapping("/calculate")
public ApiResponse<PriceCalculationResult> calculatePrice(@RequestBody MobilePriceCalculationRequest request) {
// 获取当前登录用户ID
String currentUserIdStr = BaseContextHandler.getUserId();
if (currentUserIdStr == null) {
log.warn("移动端价格计算:用户未登录");
return ApiResponse.fail("用户未登录");
}
Long currentUserId = Long.valueOf(currentUserIdStr);
log.info("移动端价格计算请求: userId={}, faceId={}, products={}",
currentUserId, request.getFaceId(), request.getProducts().size());
// 验证faceId参数
if (request.getFaceId() == null) {
log.warn("移动端价格计算:faceId参数缺失");
// return ApiResponse.fail("faceId参数不能为空");
// 兼容:兼容旧版本
ProductItem productItem = request.getProducts().getFirst();
switch (productItem.getProductType()) {
case VLOG_VIDEO -> {
VideoEntity video = videoRepository.getVideo(Long.valueOf(productItem.getProductId()));
request.setFaceId(video.getFaceId());
}
case RECORDING_SET, PHOTO_SET, AI_CAM_PHOTO_SET -> request.setFaceId(Long.valueOf(productItem.getProductId()));
}
}
// 查询人脸信息进行权限验证
ApiResponse<FaceRespVO> faceResponse = faceService.getById(request.getFaceId());
if (!faceResponse.isSuccess() || faceResponse.getData() == null) {
log.warn("移动端价格计算:人脸信息不存在, faceId={}", request.getFaceId());
return ApiResponse.fail("人脸信息不存在");
}
FaceRespVO face = faceResponse.getData();
Long scenicId = face.getScenicId();
request.getProducts().forEach(product -> {
// 获取商品的重复检查策略
DuplicateCheckStrategy strategy = productTypeCapabilityService
.getDuplicateCheckStrategy(product.getProductType().name());
boolean hasPurchasedFlag;
switch (product.getProductType()) {
case VLOG_VIDEO:
List<MemberVideoEntity> videoEntities = videoMapper.listRelationByFaceAndTemplate(face.getId(), Long.valueOf(product.getProductId()));
if (videoEntities != null && !videoEntities.isEmpty()) {
product.setQuantity(videoTaskRepository.getTaskLensNum(videoEntities.getFirst().getTaskId()));
} else {
product.setQuantity(1);
}
break;
case RECORDING_SET:
case PHOTO_SET:
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setMemberId(currentUserId);
sourceReqQuery.setType(product.getProductType() == ProductType.RECORDING_SET ? 1 : 2);
sourceReqQuery.setFaceId(face.getId());
Integer count = sourceMapper.countUser(sourceReqQuery);
product.setQuantity(count);
break;
case AI_CAM_PHOTO_SET:
SourceReqQuery aiPhotoSetReqQuery = new SourceReqQuery();
aiPhotoSetReqQuery.setMemberId(currentUserId);
aiPhotoSetReqQuery.setType(13);
aiPhotoSetReqQuery.setFaceId(face.getId());
Integer _count = sourceMapper.countUser(aiPhotoSetReqQuery);
product.setQuantity(_count);
break;
default:
log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType());
break;
}
// 使用 DuplicatePurchaseChecker 检查是否已购买
hasPurchasedFlag = checkIfPurchased(strategy, currentUserId, String.valueOf(scenicId),
product.getProductType().name(), product.getProductId(), face.getId());
// 设置是否已购买标识
product.setHasPurchased(hasPurchasedFlag);
});
// 转换为标准价格计算请求
PriceCalculationRequest standardRequest = request.toStandardRequest(currentUserId, scenicId);
// 执行价格计算
PriceCalculationResult result = priceCalculationService.calculatePrice(standardRequest);
// 设置是否已购买标识(基于请求中的商品 hasPurchased 判断)
// 只要有一个商品 hasPurchased = true,则整体 isPurchased = true
boolean isPurchased = request.getProducts().stream()
.anyMatch(product -> Boolean.TRUE.equals(product.getHasPurchased()));
result.setIsPurchased(isPurchased);
// 将计算结果缓存到Redis
String cacheKey = priceCacheService.cachePriceResult(currentUserId, scenicId, request.getProducts(), result);
log.info("移动端价格计算完成: userId={}, scenicId={}, originalAmount={}, finalAmount={}, cacheKey={}",
currentUserId, scenicId, result.getOriginalAmount(), result.getFinalAmount(), cacheKey);
return ApiResponse.success(result);
}
/**
* 移动端下单接口
* 验证价格缓存有效性,确保5分钟内使用缓存价格下单
*/
@PostMapping("/add")
public ApiResponse<String> addOrder(@RequestBody MobileOrderRequest request) {
// 获取当前登录用户ID
String currentUserIdStr = BaseContextHandler.getUserId();
if (currentUserIdStr == null) {
log.warn("移动端下单:用户未登录");
return ApiResponse.fail("用户未登录");
}
Long currentUserId = Long.valueOf(currentUserIdStr);
log.info("移动端下单请求: userId={}, faceId={}, products={}, expectedFinalAmount={}",
currentUserId, request.getFaceId(), request.getProducts().size(), request.getExpectedFinalAmount());
// 验证必填参数
if (request.getFaceId() == null) {
log.warn("移动端下单:faceId参数缺失");
return ApiResponse.fail("faceId参数不能为空");
}
if (request.getProducts() == null || request.getProducts().isEmpty()) {
log.warn("移动端下单:商品列表为空");
return ApiResponse.fail("商品列表不能为空");
}
if (request.getExpectedFinalAmount() == null) {
log.warn("移动端下单:预期价格参数缺失");
return ApiResponse.fail("预期价格不能为空");
}
// 查询人脸信息进行权限验证
ApiResponse<FaceRespVO> faceResponse = faceService.getById(request.getFaceId());
if (!faceResponse.isSuccess() || faceResponse.getData() == null) {
log.warn("移动端下单:人脸信息不存在, faceId={}", request.getFaceId());
return ApiResponse.fail("人脸信息不存在");
}
FaceRespVO face = faceResponse.getData();
Long scenicId = face.getScenicId();
// 验证并消费价格缓存(一次性使用)
PriceCalculationResult cachedResult = priceCacheService.validateAndConsumePriceCache(
currentUserId, scenicId, request.getProducts());
if (cachedResult == null) {
log.warn("移动端下单:价格缓存已过期或不存在, userId={}, scenicId={}", currentUserId, scenicId);
return ApiResponse.fail("请重新下单!");
}
// 验证价格是否匹配
if (cachedResult.getFinalAmount().compareTo(request.getExpectedFinalAmount()) != 0) {
log.warn("移动端下单:价格不匹配, cached={}, expected={}, userId={}, scenicId={}",
cachedResult.getFinalAmount(), request.getExpectedFinalAmount(), currentUserId, scenicId);
return ApiResponse.fail("价格信息变化,请退出后重新查询价格!");
}
// 验证原价是否匹配(可选)
if (request.getExpectedOriginalAmount() != null &&
cachedResult.getOriginalAmount().compareTo(request.getExpectedOriginalAmount()) != 0) {
log.warn("移动端下单:原价不匹配, cached={}, expected={}, userId={}, scenicId={}",
cachedResult.getOriginalAmount(), request.getExpectedOriginalAmount(), currentUserId, scenicId);
return ApiResponse.fail("原价信息不匹配,请重新查询价格后再下单");
}
log.info("价格缓存验证通过: userId={}, scenicId={}, finalAmount={}",
currentUserId, scenicId, cachedResult.getFinalAmount());
// 使用旧版创建订单逻辑
try {
Long orderId = oldOrderService.createOrderCompact(currentUserId, request, cachedResult);
return ApiResponse.success(String.valueOf(orderId));
} catch (Exception e) {
log.warn("移动端下单:订单创建失败, userId={}, scenicId={}, error={}", currentUserId, scenicId, e.getMessage(), e);
return ApiResponse.fail("订单创建失败,请稍后重试");
}
// 创建订单
// try {
// Long orderId = orderService.createOrder(request, currentUserId, scenicId, cachedResult);
//
// log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}",
// orderId, currentUserId, scenicId, cachedResult.getFinalAmount());
//
// return ApiResponse.success(orderId.toString());
//
// } catch (Exception e) {
// log.error("订单创建失败: userId={}, scenicId={}, error={}", currentUserId, scenicId, e.getMessage(), e);
// return ApiResponse.fail("订单创建失败,请稍后重试");
// }
}
// ====== 新增移动端订单查询功能 ======
/**
* 用户分页查询自己的订单列表
*/
@PostMapping("/page")
public ApiResponse<PageInfo<OrderV2ListResponse>> pageUserOrders(@RequestBody OrderV2PageRequest request) {
String currentUserIdStr = BaseContextHandler.getUserId();
if (currentUserIdStr == null) {
log.warn("用户未登录");
return ApiResponse.fail("用户未登录");
}
Long currentUserId = Long.valueOf(currentUserIdStr);
request.setMemberId(currentUserId); // 设置当前用户ID,确保只查询自己的订单
log.info("用户查询订单列表: userId={}, request={}", currentUserId, request);
try {
PageInfo<OrderV2ListResponse> pageInfo = orderService.pageOrdersByUser(request);
return ApiResponse.success(pageInfo);
} catch (Exception e) {
log.error("查询用户订单列表失败: userId={}", currentUserId, e);
return ApiResponse.fail("查询失败:" + e.getMessage());
}
}
/**
* 查询订单详情
*/
@GetMapping("/detail/{orderId}")
public ApiResponse<OrderV2DetailResponse> getUserOrderDetail(@PathVariable("orderId") Long orderId) {
log.info("查询订单详情: orderId={}", orderId);
try {
OrderV2DetailResponse detail = orderService.getOrderDetail(orderId);
if (detail == null) {
return ApiResponse.fail("订单不存在");
}
return ApiResponse.success(detail);
} catch (Exception e) {
log.error("查询订单详情失败: orderId={}", orderId, e);
return ApiResponse.fail("查询失败:" + e.getMessage());
}
}
// ====== 支付相关接口 ======
/**
* 获取订单支付参数
* 用于小程序调起支付
*/
@PostMapping("/{orderId}/payment-params")
public ApiResponse<PaymentParamsResponse> getPaymentParams(
@PathVariable("orderId") Long orderId,
@RequestBody PaymentParamsRequest request) {
String currentUserIdStr = BaseContextHandler.getUserId();
if (currentUserIdStr == null) {
log.warn("用户未登录");
return ApiResponse.fail("用户未登录");
}
Long currentUserId = Long.valueOf(currentUserIdStr);
log.info("获取支付参数: userId={}, orderId={}", currentUserId, orderId);
return oldOrderService.getPaymentParams(orderId, currentUserId, request);
//
// try {
// PaymentParamsResponse response = orderService.getPaymentParams(orderId, currentUserId, request);
// return ApiResponse.success(response);
// } catch (Exception e) {
// log.error("获取支付参数失败: userId={}, orderId={}", currentUserId, orderId, e);
// return ApiResponse.fail(e.getMessage());
// }
}
/**
* 支付回调处理接口
* 供第三方支付平台回调使用
*/
@PostMapping("/payment/callback/{scenicId}")
public String handlePaymentCallback(
@PathVariable("scenicId") Long scenicId,
HttpServletRequest request) {
log.info("接收支付回调: scenicId={}", scenicId);
try {
PaymentCallbackResponse response = orderService.handlePaymentCallback(scenicId, request);
if (response.isSuccess()) {
log.info("支付回调处理成功: scenicId={}, orderId={}, statusChangeType={}",
scenicId, response.getOrderId(), response.getStatusChangeType());
return "SUCCESS"; // 返回给第三方支付平台的成功标识
} else {
log.error("支付回调处理失败: scenicId={}, message={}", scenicId, response.getMessage());
return "FAIL"; // 返回给第三方支付平台的失败标识
}
} catch (Exception e) {
log.error("支付回调异常: scenicId={}", scenicId, e);
return "FAIL";
}
}
@GetMapping("/downloadable/{orderId}")
public ApiResponse<Boolean> getDownloadableOrder(@PathVariable("orderId") Long orderId) {
return ApiResponse.success(!redisTemplate.hasKey("order_content_not_downloadable_" + orderId));
}
/**
* 检查商品是否已购买
* 使用 DuplicatePurchaseChecker 通过异常捕获判断
*
* @param strategy 重复检查策略
* @param userId 用户ID
* @param scenicId 景区ID
* @param productType 商品类型
* @param productId 商品ID
* @param faceId 人脸ID
* @return true-已购买, false-未购买
*/
private boolean checkIfPurchased(DuplicateCheckStrategy strategy, Long userId, String scenicId,
String productType, String productId, Long faceId) {
// NO_CHECK 策略表示允许重复购买,直接返回 false
if (strategy == DuplicateCheckStrategy.NO_CHECK) {
return false;
}
try {
// 获取对应的检查器
IDuplicatePurchaseChecker checker = duplicatePurchaseCheckerFactory.getChecker(strategy);
// 构建检查上下文
DuplicateCheckContext context = new DuplicateCheckContext();
context.setUserId(String.valueOf(userId));
context.setScenicId(scenicId);
context.setProductType(productType);
context.setProductId(productId);
context.addParam("faceId", faceId);
// 执行检查,如果抛出异常则表示已购买
checker.check(context);
// 没有抛出异常,表示未购买
return false;
} catch (DuplicatePurchaseException e) {
// 捕获到重复购买异常,表示已购买
log.debug("检测到已购买: userId={}, scenicId={}, productType={}, productId={}",
userId, scenicId, productType, productId);
return true;
} catch (Exception e) {
// 其他异常,记录日志并返回 false(保守处理)
log.warn("检查是否已购买时发生异常: userId={}, scenicId={}, productType={}, productId={}, error={}",
userId, scenicId, productType, productId, e.getMessage(), e);
return false;
}
}
}

View File

@@ -2,7 +2,6 @@ package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.pc.printer.resp.MemberPrintResp;
import com.ycwl.basic.model.pc.printer.resp.PrinterResp;
import com.ycwl.basic.model.printer.req.FromSourceReq;
@@ -17,7 +16,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -37,16 +35,10 @@ public class AppPrinterController {
return ApiResponse.success(printerService.listByScenicId(scenicId));
}
@GetMapping("/useSample/{sampleId}")
public ApiResponse<FaceRecognizeResp> useSample(@PathVariable("sampleId") Long sampleId) throws IOException {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(printerService.useSample(worker.getUserId(), sampleId));
}
@GetMapping("/getListFor/{scenicId}")
public ApiResponse<List<MemberPrintResp>> getListFor(@PathVariable("scenicId") Long scenicId, @RequestParam(required = false) String faceId) {
public ApiResponse<List<MemberPrintResp>> getListFor(@PathVariable("scenicId") Long scenicId) {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(printerService.getUserPhotoList(worker.getUserId(), scenicId, parseFaceId(faceId)));
return ApiResponse.success(printerService.getUserPhotoList(worker.getUserId(), scenicId));
}
@GetMapping("/getItem/{scenicId}/{id}")
@@ -60,38 +52,31 @@ public class AppPrinterController {
}
@PostMapping("/deleteFrom/{scenicId}/{id}")
public ApiResponse<?> deleteFrom(@PathVariable("scenicId") Long scenicId, @PathVariable("id") Long id) {
public ApiResponse<?> deleteFrom(@PathVariable("scenicId") Long scenicId, @PathVariable("id") Long id) throws IOException {
JwtInfo worker = JwtTokenUtil.getWorker();
printerService.deleteUserPhoto(worker.getUserId(), scenicId, id);
return ApiResponse.success(null);
}
@PostMapping("/uploadTo/{scenicId}")
public ApiResponse<?> upload(@PathVariable("scenicId") Long scenicId,
@RequestParam(value = "file") MultipartFile file,
@RequestParam(value = "faceId", required = false) String faceId) {
public ApiResponse<?> upload(@PathVariable("scenicId") Long scenicId, @RequestParam(value = "file") MultipartFile file) throws IOException {
String[] split = file.getOriginalFilename().split("\\.");
String ext = split[split.length - 1];
String url = StorageFactory.use().uploadFile(file, "printer", UUID.randomUUID() + "." + ext);
Integer id = printerService.addUserPhoto(JwtTokenUtil.getWorker().getUserId(), scenicId, url, parseFaceId(faceId), null);
return ApiResponse.success(id);
printerService.addUserPhoto(JwtTokenUtil.getWorker().getUserId(), scenicId, url);
return ApiResponse.success(url);
}
@PostMapping(value = "/uploadTo/{scenicId}/cropped/{id}", consumes = "multipart/form-data")
public ApiResponse<?> uploadReplace(@PathVariable("scenicId") Long scenicId,
@PathVariable("id") Long id,
@RequestPart(value = "crop", required = false) String crop,
@RequestPart(value = "file") MultipartFile file) {
@PostMapping("/uploadTo/{scenicId}/cropped/{id}")
public ApiResponse<?> uploadReplace(@PathVariable("scenicId") Long scenicId, @PathVariable("id") Long id, @RequestParam(value = "file") MultipartFile file) throws IOException {
String[] split = file.getOriginalFilename().split("\\.");
String ext = split[split.length - 1];
String url = StorageFactory.use().uploadFile(file, "printer", UUID.randomUUID() + "." + ext);
printerService.setPhotoCropped(JwtTokenUtil.getWorker().getUserId(), scenicId, id, url, crop);
printerService.setPhotoCropped(JwtTokenUtil.getWorker().getUserId(), scenicId, id, url);
return ApiResponse.success(url);
}
@PostMapping("/uploadTo/{scenicId}/formSource")
public ApiResponse<?> uploadFromSource(@PathVariable("scenicId") Long scenicId,
@RequestBody FromSourceReq req,
@RequestParam(value = "faceId", required = false) String faceId) {
List<Integer> list = printerService.addUserPhotoFromSource(JwtTokenUtil.getWorker().getUserId(), scenicId, req, parseFaceId(faceId));
return ApiResponse.success(list);
public ApiResponse<?> uploadFromSource(@PathVariable("scenicId") Long scenicId, @RequestBody FromSourceReq req) throws IOException {
printerService.addUserPhotoFromSource(JwtTokenUtil.getWorker().getUserId(), scenicId, req);
return ApiResponse.success(null);
}
@PostMapping("/setQuantity/{scenicId}/{id}")
@@ -106,35 +91,16 @@ public class AppPrinterController {
return ApiResponse.success(null);
}
@GetMapping("/price/{scenicId}")
public ApiResponse<?> queryPrice(@PathVariable("scenicId") Long scenicId,
@RequestParam(value = "faceId", required = false) String faceId) {
return ApiResponse.success(printerService.queryPrice(JwtTokenUtil.getWorker().getUserId(), scenicId, parseFaceId(faceId)));
public ApiResponse<?> queryPrice(@PathVariable("scenicId") Long scenicId) {
return ApiResponse.success(printerService.queryPrice(JwtTokenUtil.getWorker().getUserId(), scenicId));
}
@PostMapping("/order/{scenicId}")
public ApiResponse<Map<String, Object>> createOrder(@PathVariable("scenicId") Long scenicId,
@RequestParam(value = "faceId", required = false) String faceId) {
return ApiResponse.success(printerService.createOrder(JwtTokenUtil.getWorker().getUserId(), scenicId, null, parseFaceId(faceId)));
public ApiResponse<Map<String, Object>> createOrder(@PathVariable("scenicId") Long scenicId) {
return ApiResponse.success(printerService.createOrder(JwtTokenUtil.getWorker().getUserId(), scenicId, null));
}
@PostMapping("/order/{scenicId}/toPrinter/{printerId}")
public ApiResponse<Map<String, Object>> createOrderToPrinter(@PathVariable("scenicId") Long scenicId,
@PathVariable("printerId") Integer printerId,
@RequestParam(value = "faceId", required = false) String faceId) {
return ApiResponse.success(printerService.createOrder(JwtTokenUtil.getWorker().getUserId(), scenicId, printerId, parseFaceId(faceId)));
}
/**
* 解析 faceId 字符串为 Long 类型
* 如果字符串不是有效数字,则返回 null
*/
private Long parseFaceId(String faceId) {
if (faceId == null || faceId.trim().isEmpty()) {
return null;
}
try {
return Long.parseLong(faceId.trim());
} catch (NumberFormatException e) {
return null;
}
public ApiResponse<Map<String, Object>> createOrderToPrinter(@PathVariable("scenicId") Long scenicId, @PathVariable("printerId") Integer printerId) {
return ApiResponse.success(printerService.createOrder(JwtTokenUtil.getWorker().getUserId(), scenicId, printerId));
}
}

View File

@@ -1,336 +0,0 @@
package com.ycwl.basic.controller.mobile;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.constant.FreeStatus;
import com.ycwl.basic.image.watermark.edge.PuzzleDefaultWatermarkTemplateBuilder;
import com.ycwl.basic.image.watermark.edge.WatermarkEdgeTaskCreator;
import com.ycwl.basic.image.watermark.edge.WatermarkRequest;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.puzzle.entity.MemberPuzzleEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity;
import com.ycwl.basic.pricing.dto.PriceCalculationRequest;
import com.ycwl.basic.pricing.dto.PriceCalculationResult;
import com.ycwl.basic.pricing.dto.ProductItem;
import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.service.IPriceCalculationService;
import com.ycwl.basic.puzzle.edge.task.PuzzleEdgeRenderTaskService;
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.mapper.MemberPuzzleMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/api/mobile/puzzle/v1")
@RequiredArgsConstructor
public class AppPuzzleController {
private final PuzzleRepository puzzleRepository;
private final FaceRepository faceRepository;
private final IPriceCalculationService iPriceCalculationService;
private final PrinterService printerService;
private final OrderBiz orderBiz;
private final MemberPuzzleMapper memberPuzzleMapper;
private final WatermarkEdgeTaskCreator watermarkEdgeTaskCreator;
private final FaceService faceService;
private final ScenicRepository scenicRepository;
/**
* 根据faceId查询三拼图数量
*/
@GetMapping("/count/{faceId}")
public ApiResponse<Integer> countByFaceId(@PathVariable("faceId") Long faceId) {
if (faceId == null) {
return ApiResponse.fail("faceId不能为空");
}
// 通过关联表查询数量
List<MemberPuzzleEntity> relations = memberPuzzleMapper.listByFaceId(faceId);
return ApiResponse.success(relations.size());
}
/**
* 根据faceId查询所有三拼图记录
*/
@GetMapping("/list/{faceId}")
public ApiResponse<List<ContentPageVO>> listByFaceId(@PathVariable("faceId") Long faceId) {
if (faceId == null) {
return ApiResponse.fail("faceId不能为空");
}
// 通过关联表查询,获取关联的拼图记录
List<MemberPuzzleEntity> relations = memberPuzzleMapper.listByFaceId(faceId);
List<ContentPageVO> result = relations.stream()
.map(relation -> {
PuzzleGenerationRecordEntity record = puzzleRepository.getRecordById(relation.getRecordId());
if (record == null) {
return null;
}
return convertToContentPageVO(record, relation);
})
.filter(vo -> vo != null)
.collect(Collectors.toList());
return ApiResponse.success(result);
}
/**
* 根据recordId查询单个三拼图记录
*/
@GetMapping("/detail/{recordId}")
public ApiResponse<ContentPageVO> getByRecordId(@PathVariable("recordId") Long recordId) {
if (recordId == null) {
return ApiResponse.fail("recordId不能为空");
}
PuzzleGenerationRecordEntity record = puzzleRepository.getRecordById(recordId);
if (record == null) {
return ApiResponse.fail("未找到对应的拼图记录");
}
// 查询关联记录
MemberPuzzleEntity relation = memberPuzzleMapper.getByFaceAndRecord(record.getFaceId(), recordId);
ContentPageVO result = convertToContentPageVO(record, relation);
return ApiResponse.success(result);
}
/**
* 根据recordId下载拼图资源
* 如果是免费赠送的拼图,会添加水印后返回
*/
@GetMapping("/download/{recordId}")
public ApiResponse<List<String>> download(@PathVariable("recordId") Long recordId) {
if (recordId == null) {
return ApiResponse.fail("recordId不能为空");
}
PuzzleGenerationRecordEntity record = puzzleRepository.getRecordById(recordId);
if (record == null) {
return ApiResponse.fail("未找到对应的拼图记录");
}
String resultImageUrl = record.getResultImageUrl();
if (resultImageUrl == null || resultImageUrl.isEmpty()) {
return ApiResponse.fail("该拼图记录没有可用的图片URL");
}
// 查询该拼图的关联记录,判断是否为免费赠送
Long faceId = record.getFaceId();
if (faceId != null) {
MemberPuzzleEntity memberPuzzle = memberPuzzleMapper.getByFaceAndRecord(faceId, recordId);
if (memberPuzzle != null && FreeStatus.isFree(memberPuzzle.getIsFree())) {
// 免费赠送的拼图,需要添加水印
String watermarkedUrl = addWatermarkForFreePuzzle(record);
if (watermarkedUrl != null) {
return ApiResponse.success(Collections.singletonList(watermarkedUrl));
}
// 如果水印添加失败,记录日志并返回原图
log.warn("免费拼图水印添加失败,返回原图: recordId={}", recordId);
}
}
return ApiResponse.success(Collections.singletonList(resultImageUrl));
}
/**
* 为免费赠送的拼图添加水印
*
* @param record 拼图记录
* @return 带水印的图片URL,失败返回null
*/
private String addWatermarkForFreePuzzle(PuzzleGenerationRecordEntity record) {
try {
Long faceId = record.getFaceId();
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
log.warn("添加水印失败:未找到人脸信息, faceId={}", faceId);
return null;
}
// 获取景区信息
ScenicEntity scenic = scenicRepository.getScenic(face.getScenicId());
String scenicLine = scenic != null ? scenic.getName() : "";
// 获取二维码URL
String qrcodeUrl = faceService.bindWxaCode(faceId);
// 格式化日期时间
String datetimeLine = record.getCreateTime() != null
? DateUtil.format(record.getCreateTime(), "yyyy-MM-dd")
: "";
// 构建水印请求
WatermarkRequest request = WatermarkRequest.builder()
.originalImageUrl(record.getResultImageUrl())
.imageWidth(record.getResultWidth() != null ? record.getResultWidth() : 0)
.imageHeight(record.getResultHeight() != null ? record.getResultHeight() : 0)
.qrcodeUrl(qrcodeUrl)
.faceUrl(face.getFaceUrl())
.scenicLine(scenicLine)
.datetimeLine(datetimeLine)
.outputFormat("JPEG")
.outputQuality(90)
.build();
// 创建水印任务并等待结果
PuzzleEdgeRenderTaskService.TaskWaitResult result = watermarkEdgeTaskCreator.createAndWait(
PuzzleDefaultWatermarkTemplateBuilder.STYLE,
request,
record.getId(),
faceId,
"free_puzzle_download",
30_000L // 30秒超时
);
if (result.isSuccess()) {
log.info("免费拼图水印添加成功: recordId={}, url={}", record.getId(), result.getImageUrl());
return result.getImageUrl();
} else {
log.error("免费拼图水印添加失败: recordId={}, error={}", record.getId(), result.getErrorMessage());
return null;
}
} catch (Exception e) {
log.error("免费拼图水印添加异常: recordId={}", record.getId(), e);
return null;
}
}
/**
* 根据recordId查询拼图价格
*/
@GetMapping("/price/{recordId}")
public ApiResponse<PriceCalculationResult> getPriceByRecordId(@PathVariable("recordId") Long recordId) {
if (recordId == null) {
return ApiResponse.fail("recordId不能为空");
}
PuzzleGenerationRecordEntity record = puzzleRepository.getRecordById(recordId);
if (record == null) {
return ApiResponse.fail("未找到对应的拼图记录");
}
FaceEntity face = faceRepository.getFace(record.getFaceId());
if (face == null) {
return ApiResponse.fail("未找到对应的人脸信息");
}
PriceCalculationRequest calculationRequest = new PriceCalculationRequest();
ProductItem productItem = new ProductItem();
productItem.setProductType(ProductType.PHOTO_LOG);
productItem.setProductId(record.getTemplateId().toString());
productItem.setPurchaseCount(1);
productItem.setScenicId(face.getScenicId().toString());
calculationRequest.setProducts(Collections.singletonList(productItem));
calculationRequest.setUserId(face.getMemberId());
calculationRequest.setFaceId(record.getFaceId());
calculationRequest.setPreviewOnly(true); // 仅查询价格,不实际使用优惠
PriceCalculationResult calculationResult = iPriceCalculationService.calculatePrice(calculationRequest);
return ApiResponse.success(calculationResult);
}
/**
* 将拼图导入到打印列表
*/
@PostMapping("/import-to-print/{recordId}")
public ApiResponse<Integer> importToPrint(@PathVariable("recordId") Long recordId) {
if (recordId == null) {
return ApiResponse.fail("recordId不能为空");
}
// 查询拼图记录
PuzzleGenerationRecordEntity record = puzzleRepository.getRecordById(recordId);
if (record == null) {
return ApiResponse.fail("未找到对应的拼图记录");
}
// 检查是否有图片URL
String imageUrl = record.getResultImageUrl();
if (imageUrl == null || imageUrl.isEmpty()) {
return ApiResponse.fail("该拼图记录没有可用的图片URL");
}
// 获取人脸信息
FaceEntity face = faceRepository.getFace(record.getFaceId());
if (face == null) {
return ApiResponse.fail("未找到对应的人脸信息");
}
// 调用服务添加到打印列表
Integer memberPrintId = printerService.addUserPhotoFromPuzzle(
face.getMemberId(),
face.getScenicId(),
record.getFaceId(),
imageUrl,
recordId // 拼图记录ID,用于关联 puzzle_record 表
);
if (memberPrintId == null) {
return ApiResponse.fail("添加到打印列表失败");
}
return ApiResponse.success(memberPrintId);
}
/**
* 将PuzzleGenerationRecordEntity转换为ContentPageVO
*
* @param record 拼图生成记录
* @param relation 会员拼图关联记录,用于获取免费状态
*/
private ContentPageVO convertToContentPageVO(PuzzleGenerationRecordEntity record, MemberPuzzleEntity relation) {
ContentPageVO vo = new ContentPageVO();
// 内容类型为3(拼图)
vo.setContentType(3);
// 源素材类型为3(拼图)
vo.setSourceType(3);
vo.setGroup("拼图");
// 只要存在记录,lockType不为0(设置为-1表示已生成)
vo.setLockType(-1);
// 通过faceId填充scenicId的信息
FaceEntity face = faceRepository.getFace(record.getFaceId());
if (record.getFaceId() != null) {
vo.setScenicId(face.getScenicId());
}
// contentId为生成记录id
vo.setContentId(record.getId());
// templateCoverUrl和生成的图是一致的
vo.setTemplateCoverUrl(record.getResultImageUrl());
// 设置模板ID
vo.setTemplateId(record.getTemplateId());
IsBuyRespVO isBuyScenic = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, face.getScenicId());
if (isBuyScenic.isBuy()) {
vo.setIsBuy(1);
} else {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), 5, record.getTemplateId());
if (isBuyRespVO.isBuy()) {
vo.setIsBuy(1);
} else {
vo.setIsBuy(0);
// 从关联记录读取免费状态
if (relation != null && FreeStatus.isFree(relation.getIsFree())) {
vo.setFreeCount(1);
} else {
vo.setFreeCount(0);
}
}
}
return vo;
}
}

View File

@@ -1,76 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.SubmitAnswerRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
import com.ycwl.basic.integration.questionnaire.service.QuestionnaireIntegrationService;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 移动端问卷接口控制器
*
* @author Claude Code
* @date 2025-09-05
*/
@Slf4j
@RestController
@RequestMapping("/api/mobile/questionnaire/v1")
@RequiredArgsConstructor
public class AppQuestionnaireController {
private final QuestionnaireIntegrationService questionnaireIntegrationService;
/**
* 获取问卷详情
* 包含问卷基本信息和所有题目
*/
@IgnoreToken
@GetMapping("/{id}")
public ApiResponse<QuestionnaireResponse> getQuestionnaire(@PathVariable Long id) {
log.info("移动端获取问卷详情, id: {}", id);
try {
QuestionnaireResponse questionnaire = questionnaireIntegrationService.getQuestionnaire(id);
// 检查问卷状态,只有已发布的问卷才能被移动端访问
if (questionnaire.getStatus() != 2) {
return ApiResponse.fail("问卷未发布或已停止");
}
return ApiResponse.success(questionnaire);
} catch (Exception e) {
log.error("移动端获取问卷详情失败, id: {}", id, e);
return ApiResponse.fail("获取问卷详情失败: " + e.getMessage());
}
}
/**
* 提交问卷答案
*/
@PostMapping("/{id}/submit")
public ApiResponse<ResponseDetailResponse> submitAnswer(
@PathVariable Long id,
@Valid @RequestBody SubmitAnswerRequest request) {
String userId = BaseContextHandler.getUserId();
log.info("移动端提交问卷答案, questionnaireId: {}, userId: {}, answers count: {}",
id, userId, request.getAnswers() != null ? request.getAnswers().size() : 0);
try {
// 设置问卷ID和用户ID
request.setQuestionnaireId(id);
request.setUserId(userId);
ResponseDetailResponse response = questionnaireIntegrationService.submitAnswer(request);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("移动端提交问卷答案失败, questionnaireId: {}, userId: {}", id, userId, e);
return ApiResponse.fail("提交问卷答案失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,118 @@
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;
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;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @Author:longbinbin
* @Date:2024/12/5 10:22
*/
@Slf4j
@Deprecated
@RestController
@RequestMapping("/api/mobile/scenic/v1")
@Api(tags = "景区相关接口")
public class AppScenicController {
@Autowired
private FaceService faceService;
@Autowired
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){
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
@GetMapping("/{id}")
public ApiResponse<ScenicRespVO> getDetails(@PathVariable Long id){
return appScenicService.getDetails(id);
}
@GetMapping("/{id}/config")
@IgnoreToken
public ApiResponse<ScenicConfigResp> getConfig(@PathVariable Long id){
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(id);
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("查询景区设备总数和拍到用户的机位数量")
@GetMapping("/{scenicId}/deviceCount/")
public ApiResponse<ScenicDeviceCountVO> deviceCountByScenicId(@PathVariable Long scenicId){
return appScenicService.deviceCountByScenicId(scenicId);
}
@ApiOperation("景区视频源素材列表")
@GetMapping("/contentList/")
public ApiResponse<List<ContentPageVO>> contentList() {
return faceService.contentListUseDefaultFace();
}
@ApiOperation("景区视频源素材列表")
@GetMapping("/face/{faceId}/contentList")
public ApiResponse<List<ContentPageVO>> contentList(@PathVariable Long faceId) {
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

@@ -8,6 +8,7 @@ import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.task.TaskService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -27,6 +28,7 @@ public class AppTaskController {
@GetMapping("/face/{faceId}")
@IgnoreLogReq
public ApiResponse<VideoTaskStatusVO> getTaskStatusByFaceId(@PathVariable("faceId") Long faceId) {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(goodsService.getTaskStatusByFaceId(faceId));
}
@GetMapping("/scenic/{scenicId}")
@@ -44,16 +46,17 @@ public class AppTaskController {
* @param templateId 模版id
* @return 1 合成中 2 合成成功
*/
// 查询用户当前景区的具体模版视频合成任务状态 1 合成中 2 合成成功
@ApiOperation("查询用户当前景区的具体模版视频合成任务状态 1 合成中 2 合成成功 ")
@GetMapping("/face/{faceId}/template/{templateId}")
@IgnoreLogReq
public ApiResponse<VideoTaskStatusVO> getTemplateTaskStatus(@PathVariable("faceId") Long faceId, @PathVariable("templateId") Long templateId) {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(goodsService.getTaskStatusByTemplateId(faceId, templateId));
}
@PostMapping("/submit")
public ApiResponse<String> submitVideoTask(@RequestBody VideoTaskReq videoTaskReq) {
taskService.createTaskByFaceIdAndTemplateId(videoTaskReq.getFaceId(),videoTaskReq.getTemplateId(),false);
taskService.createTaskByFaceIdAndTempalteId(videoTaskReq.getFaceId(),videoTaskReq.getTemplateId(),0);
return ApiResponse.success("成功");
}
}

View File

@@ -1,131 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.mobile.scenic.content.ScenicTemplateContentVO;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 移动端模板接口
*/
@RestController
@RequestMapping("/api/mobile/template/v1")
@RequiredArgsConstructor
public class AppTemplateController {
private final TemplateRepository templateRepository;
private final PuzzleRepository puzzleRepository;
/**
* 根据模板ID获取封面URL
*
* @param templateId 模板ID
* @return 模板封面URL
*/
@GetMapping("/cover/{templateId}")
public ApiResponse<String> getTemplateCoverUrl(@PathVariable("templateId") Long templateId) {
if (templateId == null) {
return ApiResponse.fail("模板ID不能为空");
}
TemplateRespVO template = templateRepository.getTemplate(templateId);
if (template == null) {
return ApiResponse.fail("未找到对应的模板");
}
String coverUrl = template.getCoverUrl();
if (coverUrl == null || coverUrl.isEmpty()) {
return ApiResponse.fail("该模板没有封面地址");
}
return ApiResponse.success(coverUrl);
}
/**
* 根据景区ID获取所有模板封面URL列表(用于前端预缓存)
*
* @param scenicId 景区ID
* @return 模板封面URL列表
*/
@GetMapping("/scenic/{scenicId}/covers")
@IgnoreToken
public ApiResponse<List<String>> getScenicTemplateCoverUrls(@PathVariable("scenicId") Long scenicId) {
if (scenicId == null) {
return ApiResponse.fail("景区ID不能为空");
}
List<String> coverUrls = new ArrayList<>();
// 获取普通模板封面
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(scenicId);
templateList.stream()
.map(TemplateRespVO::getCoverUrl)
.filter(Objects::nonNull)
.filter(url -> !url.isEmpty())
.forEach(coverUrls::add);
// 获取拼图模板封面(使用缓存)
List<PuzzleTemplateEntity> puzzleTemplateList = puzzleRepository.listTemplateByScenic(scenicId);
puzzleTemplateList.stream()
.map(PuzzleTemplateEntity::getCoverImage)
.filter(Objects::nonNull)
.filter(url -> !url.isEmpty())
.forEach(coverUrls::add);
return ApiResponse.success(coverUrls);
}
/**
* 根据景区ID获取所有模板内容列表(返回模板基础信息,与 faceId 无关)
*
* @param scenicId 景区ID
* @return 景区模板内容列表
*/
@GetMapping("/scenic/{scenicId}/contents")
@IgnoreToken
public ApiResponse<List<ScenicTemplateContentVO>> getScenicTemplateContents(@PathVariable("scenicId") Long scenicId) {
if (scenicId == null) {
return ApiResponse.fail("景区ID不能为空");
}
List<ScenicTemplateContentVO> contentList = new ArrayList<>();
// 获取普通模板
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(scenicId);
for (TemplateRespVO template : templateList) {
ScenicTemplateContentVO content = new ScenicTemplateContentVO();
content.setGoodsType(0); // 普通模板默认商品类型为 0
content.setName(template.getName());
content.setGroup(template.getGroup());
content.setTemplateId(template.getId());
content.setTemplateCoverUrl(template.getCoverUrl());
contentList.add(content);
}
// 获取拼图模板
List<PuzzleTemplateEntity> puzzleTemplateList = puzzleRepository.listTemplateByScenic(scenicId);
for (PuzzleTemplateEntity puzzleTemplate : puzzleTemplateList) {
ScenicTemplateContentVO content = new ScenicTemplateContentVO();
content.setGoodsType(3); // 拼图模板商品类型为 3
content.setName(puzzleTemplate.getName());
content.setGroup("氛围拼图"); // 拼图模板固定分组
content.setTemplateId(puzzleTemplate.getId());
content.setTemplateCoverUrl(puzzleTemplate.getCoverImage());
contentList.add(content);
}
return ApiResponse.success(contentList);
}
}

View File

@@ -1,124 +1,23 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.model.mobile.video.dto.VideoViewPermissionDTO;
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
import com.ycwl.basic.model.task.req.VideoInfoReq;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.service.mobile.VideoViewPermissionService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Deprecated
@RestController
@RequestMapping("/api/mobile/video/v1")
public class AppVideoController {
@Autowired
private VideoRepository videoRepository;
@Autowired
private VideoViewPermissionService videoViewPermissionService;
@PostMapping("/{videoId}/updateMeta")
public void updateMeta(@PathVariable("videoId") Long videoId, @RequestBody VideoInfoReq req) {
videoRepository.updateMeta(videoId, req);
}
/**
* 记录用户查看视频并返回权限信息
*
* @param videoId 视频ID
* @return 查看权限信息
*/
@PostMapping("/{videoId}/recordView")
public ApiResponse<VideoViewPermissionDTO> recordView(@PathVariable("videoId") Long videoId) {
try {
String userIdStr = BaseContextHandler.getUserId();
if (userIdStr == null || userIdStr.isEmpty()) {
log.warn("用户未登录,无法记录查看: videoId={}", videoId);
return ApiResponse.fail("用户未登录");
}
Long userId = Long.valueOf(userIdStr);
log.debug("记录用户查看视频: userId={}, videoId={}", userId, videoId);
VideoViewPermissionDTO permission = videoViewPermissionService.checkAndRecordView(userId, videoId);
return ApiResponse.success(permission);
} catch (NumberFormatException e) {
log.error("用户ID格式错误: userId={}, videoId={}", BaseContextHandler.getUserId(), videoId, e);
return ApiResponse.fail("用户信息无效");
} catch (Exception e) {
log.error("记录用户查看视频失败: videoId={}", videoId, e);
return ApiResponse.fail("记录查看失败,请稍后重试");
}
}
/**
* 检查用户查看权限(不记录查看次数)
*
* @param videoId 视频ID
* @return 查看权限信息
*/
@GetMapping("/{videoId}/checkPermission")
public ApiResponse<VideoViewPermissionDTO> checkPermission(@PathVariable("videoId") Long videoId) {
try {
String userIdStr = BaseContextHandler.getUserId();
if (userIdStr == null || userIdStr.isEmpty()) {
log.warn("用户未登录,无法查看权限: videoId={}", videoId);
return ApiResponse.fail("用户未登录");
}
Long userId = Long.valueOf(userIdStr);
log.debug("检查用户查看权限: userId={}, videoId={}", userId, videoId);
VideoViewPermissionDTO permission = videoViewPermissionService.checkViewPermission(userId, videoId);
return ApiResponse.success(permission);
} catch (NumberFormatException e) {
log.error("用户ID格式错误: userId={}, videoId={}", BaseContextHandler.getUserId(), videoId, e);
return ApiResponse.fail("用户信息无效");
} catch (Exception e) {
log.error("检查用户查看权限失败: videoId={}", videoId, e);
return ApiResponse.fail("权限检查失败,请稍后重试");
}
}
/**
* 通过faceId和templateId(可选)查询最新的视频记录
*
* @param faceId 人脸ID
* @param templateId 模板ID(可选)
* @return 最新的视频记录
*/
@GetMapping("/latest")
public ApiResponse<VideoRespVO> getLatestByFaceId(
@RequestParam("faceId") Long faceId,
@RequestParam(value = "templateId", required = false) Long templateId) {
try {
log.debug("查询最新视频记录: faceId={}, templateId={}", faceId, templateId);
VideoRespVO video = videoRepository.queryLatestByFaceIdAndTemplateId(faceId, templateId);
if (video == null) {
log.info("未找到视频记录: faceId={}, templateId={}", faceId, templateId);
return ApiResponse.fail("未找到视频记录");
}
return ApiResponse.success(video);
} catch (Exception e) {
log.error("查询最新视频记录失败: faceId={}, templateId={}", faceId, templateId, e);
return ApiResponse.fail("查询失败,请稍后重试");
}
}
}

View File

@@ -1,72 +0,0 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.pricing.dto.req.VoucherClaimReq;
import com.ycwl.basic.pricing.dto.req.VoucherPrintReq;
import com.ycwl.basic.pricing.dto.resp.VoucherCodeResp;
import com.ycwl.basic.pricing.dto.resp.VoucherPrintResp;
import com.ycwl.basic.pricing.service.VoucherCodeService;
import com.ycwl.basic.pricing.service.VoucherPrintService;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/api/mobile/voucher/v1")
public class AppVoucherController {
@Autowired
private VoucherPrintService voucherPrintService;
@Autowired
private VoucherCodeService voucherCodeService;
@Autowired
private FaceRepository faceRepository;
/**
* 打印小票
* @param request 打印请求
* @return 打印结果
*/
@PostMapping("/print")
public ApiResponse<VoucherPrintResp> printVoucherTicket(@RequestBody VoucherPrintReq request) {
log.info("收到打印小票请求: faceId={}, brokerId={}, scenicId={}",
request.getFaceId(), request.getBrokerId(), request.getScenicId());
VoucherPrintResp response = voucherPrintService.printVoucherTicket(request);
log.info("打印小票完成: code={}, voucherCode={}, status={}",
response.getCode(), response.getVoucherCode(), response.getPrintStatus());
return ApiResponse.success(response);
}
@GetMapping("/printed")
public ApiResponse<VoucherPrintResp> queryPrintedVoucher(
@RequestParam Long faceId
) {
return ApiResponse.success(voucherPrintService.queryPrintedVoucher(faceId));
}
@PostMapping("/claim")
public ApiResponse<VoucherCodeResp> claimVoucher(@RequestBody VoucherClaimReq req) {
FaceEntity face = faceRepository.getFace(req.getFaceId());
if (face == null) {
throw new BaseException("请选择人脸");
}
req.setScenicId(face.getScenicId());
VoucherCodeResp result = voucherCodeService.claimVoucher(req);
return ApiResponse.success(result);
}
}

View File

@@ -0,0 +1,68 @@
package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-06
* @Description: 微信消息模板通知
* @Version: 1.0
*/
@RestController
@RequestMapping("/api/mobile/wx/notify/v1")
@Api(tags = "微信消息模板通知")
public class AppWxNotifyController {
@Autowired
private ScenicRepository scenicRepository;
//
// @ApiOperation(value = "通知", notes = "通知")
// @PostMapping("/pushMessage")
// @IgnoreToken
// public ApiResponse<?> pushMessage(@RequestBody WechatMessageSubscribeForm req) {
// JSONObject resJson = wxNotifyService.pushMessage(req);
// return ApiResponse.success(resJson);
// }
@GetMapping({"/getIds", "/"})
@IgnoreToken
public ApiResponse<List<String>> getIds() {
return ApiResponse.success(new ArrayList<>() {{
add("5b8vTm7kvwYubqDxb3dxBs0BqxMsgVgGw573aahTEd8");
add("vPIzbkA0x4mMj-vdbWx6_45e8juWXzs3FGYnDsIPv3A");
add("HB1vp-0BXc2WyYeoYN3a3GuZV9HtPLXUTT7blCBq9eY");
}});
}
@GetMapping("/{scenicId}")
@IgnoreToken
public ApiResponse<List<String>> getIds(@PathVariable("scenicId") Long scenicId) {
return ApiResponse.success(new ArrayList<>() {{
String videoGeneratedTemplateId = scenicRepository.getVideoGeneratedTemplateId(scenicId);
if (StringUtils.isNotBlank(videoGeneratedTemplateId)) {
add(videoGeneratedTemplateId);
}
String videoDownloadTemplateId = scenicRepository.getVideoDownloadTemplateId(scenicId);
if (StringUtils.isNotBlank(videoDownloadTemplateId)) {
add(videoDownloadTemplateId);
}
String videoPreExpireTemplateId = scenicRepository.getVideoPreExpireTemplateId(scenicId);
if (StringUtils.isNotBlank(videoPreExpireTemplateId)) {
add(videoPreExpireTemplateId);
}
}});
}
}

View File

@@ -8,6 +8,8 @@ import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
@@ -29,13 +31,13 @@ import java.security.GeneralSecurityException;
*/
@RestController
@RequestMapping("/api/mobile/wx/pay/v1")
// 微信支付相关接口
@Api(tags = "微信支付相关接口")
public class AppWxPayController {
@Autowired
private WxPayService wxPayService;
// 微信支付回调
@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
@PostMapping("/payNotify")
@IgnoreToken
public ApiResponse<?> payNotify(HttpServletRequest request) {
@@ -56,7 +58,7 @@ public class AppWxPayController {
return ApiResponse.success(BizCodeEnum.REQUEST_OK);
}
// 微信支付退款回调
@ApiOperation(value = "微信支付退款回调", notes = "微信支付退款回调")
@PostMapping("/{scenicId}/refundNotify")
@IgnoreToken
public ApiResponse<?> refundNotify(@PathVariable Long scenicId, HttpServletRequest request) throws GeneralSecurityException, IOException {

View File

@@ -2,23 +2,21 @@ package com.ycwl.basic.controller.mobile.manage;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginOldRespVO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicRegisterReq;
import com.ycwl.basic.model.mobile.scenic.account.ScenicRegisterRespVO;
import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.AppScenicService;
import com.ycwl.basic.service.pc.ScenicAccountService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.utils.ApiResponse;
import org.apache.commons.lang3.Strings;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -27,10 +25,10 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN;
import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
/**
@@ -39,7 +37,7 @@ import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
*/
@RestController
@RequestMapping("/api/mobile/scenicAccount/v1")
// 景区账号相关接口
@Api(tags = "景区账号相关接口")
public class AppScenicAccountController {
@Autowired
private ScenicAccountService accountService;
@@ -47,51 +45,28 @@ public class AppScenicAccountController {
private AppScenicService scenicService;
@Autowired
private ScenicService adminScenicService;
@Autowired
private ScenicRepository scenicRepository;
// 登录
@ApiOperation("登录")
@PostMapping("/login")
@IgnoreToken
public ApiResponse<ScenicLoginOldRespVO> login(@RequestBody ScenicLoginReq scenicLoginReq) throws Exception {
ApiResponse<ScenicLoginRespVO> logined = scenicService.login(scenicLoginReq);
ScenicLoginOldRespVO vo = new ScenicLoginOldRespVO();
if (!logined.isSuccess()) {
return ApiResponse.fail(logined.getMsg());
}
vo.setId(logined.getData().getId());
vo.setScenicId(logined.getData().getScenicId().getFirst());
vo.setIsSuper(logined.getData().getIsSuper());
vo.setName(logined.getData().getName());
vo.setAccount(logined.getData().getAccount());
vo.setStatus(logined.getData().getStatus());
vo.setToken(logined.getData().getToken());
return ApiResponse.success(vo);
}
// 注册
@PostMapping("/register")
@IgnoreToken
public ApiResponse<ScenicRegisterRespVO> register(@RequestBody ScenicRegisterReq scenicRegisterReq) {
return scenicService.register(scenicRegisterReq);
public ApiResponse<ScenicLoginRespVO> login(@RequestBody ScenicLoginReq scenicLoginReq) throws Exception {
return scenicService.login(scenicLoginReq);
}
@GetMapping("/myScenicList")
public ApiResponse<List<ScenicV2DTO>> myScenicList() {
List<ScenicV2DTO> list = Collections.emptyList();
if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
public ApiResponse<List<ScenicRespVO>> myScenicList() {
List<ScenicRespVO> list = Collections.emptyList();
if (StringUtils.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
String userId = BaseContextHandler.getUserId();
ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId));
if (account == null || account.getScenicId().isEmpty()) {
return ApiResponse.fail("景区账号未绑定景区");
}
list = account.getScenicId().stream()
.map(id -> scenicRepository.getScenicBasic(id))
.toList();
} else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) {
ScenicReqQuery query = new ScenicReqQuery();
query.setPageSize(1000);
list = scenicRepository.list(query);
list = account.getScenicId().stream().map(id -> {
return scenicService.getDetails(id).getData();
}).toList();
} else {
list = adminScenicService.list(new ScenicReqQuery()).getData();
}
return ApiResponse.success(list);
}
@@ -120,7 +95,6 @@ public class AppScenicAccountController {
}
@GetMapping("/devices")
@Deprecated
public ApiResponse<List<DeviceRespVO>> getDeviceList() {
String userId = BaseContextHandler.getUserId();
ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId));

View File

@@ -9,6 +9,7 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
import com.ycwl.basic.service.pc.OrderService;
import com.ycwl.basic.service.pc.ScenicAccountService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -20,7 +21,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/mobile/scenic/order/v1")
// 景区账号相关接口
@Api(tags = "景区账号相关接口")
public class AppScenicOrderController {
@Autowired
private OrderService orderService;

View File

@@ -11,6 +11,8 @@ import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -22,38 +24,46 @@ import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api/mobile/statistics/v1")
// 数据统计相关接口
@Api(tags = "数据统计相关接口")
public class AppStatisticsController {
@Autowired
private AppStatisticsService statisticsService;
// 支付订单金额、预览_支付转化率、扫码_付费用户转化率
@ApiOperation("支付订单金额、预览_支付转化率、扫码_付费用户转化率")
@PostMapping("/one")
public ApiResponse<AppSta1VO> oneStatistics(@RequestBody CommonQueryReq query) {
return statisticsService.oneStatistics(query);
}
// 支付订单数、现场订单数、推送订单数统计
@ApiOperation("支付订单数、现场订单数、推送订单数统计")
@PostMapping("/two")
public ApiResponse<AppSta2VO> twoStatistics(@RequestBody CommonQueryReq query) {
return statisticsService.twoStatistics(query);
}
// 扫码访问人数、推送订阅人数、预览视频人数统计
@ApiOperation("扫码访问人数、推送订阅人数、预览视频人数统计")
@PostMapping("/free")
public ApiResponse<AppSta3VO> freeStatistics(@RequestBody CommonQueryReq query) {
return statisticsService.freeStatistics(query);
}
// 用户转化漏斗
@ApiOperation("用户转化漏斗")
@PostMapping("/userConversionFunnel")
public ApiResponse<AppStatisticsFunnelVO> userConversionFunnel(@RequestBody CommonQueryReq query) {
return statisticsService.userConversionFunnel(query);
}
@ApiOperation("统计数据记录")
@PostMapping("/addStatistics")
@IgnoreToken
public ApiResponse<String> addStatistics(@RequestBody StatisticsRecordAddReq req) {
return statisticsService.addStatistics(req);
}
}

View File

@@ -1,150 +0,0 @@
package com.ycwl.basic.controller.mobile.manage;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.mobile.video.dto.HlsStreamRequest;
import com.ycwl.basic.model.mobile.video.dto.HlsStreamResponse;
import com.ycwl.basic.service.mobile.HlsStreamService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 移动端视频流控制器
* 提供HLS视频流播放列表生成功能
*
* @author Claude Code
* @date 2025-12-26
*/
@Slf4j
@RestController
@RequestMapping("/api/mobile/video-stream/v1")
@RequiredArgsConstructor
public class AppVideoStreamController {
private final HlsStreamService hlsStreamService;
/**
* 生成设备视频的HLS播放列表(JSON格式)
* 返回包含m3u8内容和视频片段信息的JSON对象
*
* @param request HLS流请求参数
* @return HLS播放列表响应
*/
@PostMapping("/hls/playlist")
public ApiResponse<HlsStreamResponse> generateHlsPlaylist(@Validated @RequestBody HlsStreamRequest request) {
log.info("收到HLS播放列表生成请求: deviceId={}, durationMinutes={}",
request.getDeviceId(), request.getDurationMinutes());
try {
HlsStreamResponse response = hlsStreamService.generateHlsPlaylist(request);
log.info("HLS播放列表生成成功: deviceId={}, segmentCount={}, totalDuration={}s",
response.getDeviceId(), response.getSegmentCount(), response.getTotalDurationSeconds());
return ApiResponse.success(response);
} catch (Exception e) {
log.error("生成HLS播放列表失败: deviceId={}", request.getDeviceId(), e);
return ApiResponse.buildResponse(500, null, "生成失败: " + e.getMessage());
}
}
/**
* 生成设备视频的HLS播放列表(m3u8文件格式)
* 直接返回m3u8文件内容,可被视频播放器直接使用
*
* @param deviceId 设备ID
* @param durationMinutes 视频时长(分钟),默认2分钟
* @param eventPlaylist 是否为Event播放列表,默认true
* @param response HTTP响应对象
*/
@GetMapping("/hls/playlist.m3u8")
@IgnoreToken
public void generateHlsPlaylistFile(
@RequestParam Long deviceId,
@RequestParam(defaultValue = "2") Integer durationMinutes,
@RequestParam(defaultValue = "true") Boolean eventPlaylist,
HttpServletResponse response) {
log.info("收到m3u8文件生成请求: deviceId={}, durationMinutes={}",
deviceId, durationMinutes);
try {
// 构建请求参数
HlsStreamRequest request = new HlsStreamRequest();
request.setDeviceId(deviceId);
request.setDurationMinutes(durationMinutes);
request.setEventPlaylist(eventPlaylist);
// 生成播放列表
HlsStreamResponse hlsResponse = hlsStreamService.generateHlsPlaylist(request);
log.info("m3u8文件生成成功: deviceId={}, segmentCount={}, totalDuration={}s",
deviceId, hlsResponse.getSegmentCount(), hlsResponse.getTotalDurationSeconds());
// 设置响应头
response.setContentType("application/vnd.apple.mpegurl");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", "inline; filename=\"playlist.m3u8\"");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// 写入m3u8内容
response.getWriter().write(hlsResponse.getPlaylistContent());
response.getWriter().flush();
} catch (Exception e) {
log.error("生成m3u8文件失败: deviceId={}", deviceId, e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write("{\"code\":500,\"message\":\"生成失败: " + e.getMessage() + "\"}");
response.getWriter().flush();
} catch (IOException ioException) {
log.error("写入错误响应失败", ioException);
}
}
}
/**
* 获取设备最近的视频片段信息
* 仅返回视频片段列表,不包含m3u8内容
*
* @param deviceId 设备ID
* @param durationMinutes 视频时长(分钟),默认2分钟
* @return 视频片段列表
*/
@GetMapping("/segments")
public ApiResponse<HlsStreamResponse> getVideoSegments(
@RequestParam Long deviceId,
@RequestParam(defaultValue = "2") Integer durationMinutes) {
log.info("收到视频片段查询请求: deviceId={}, durationMinutes={}",
deviceId, durationMinutes);
try {
HlsStreamRequest request = new HlsStreamRequest();
request.setDeviceId(deviceId);
request.setDurationMinutes(durationMinutes);
request.setEventPlaylist(true);
HlsStreamResponse response = hlsStreamService.generateHlsPlaylist(request);
log.info("视频片段查询成功: deviceId={}, segmentCount={}",
deviceId, response.getSegmentCount());
return ApiResponse.success(response);
} catch (Exception e) {
log.error("查询视频片段失败: deviceId={}", deviceId, e);
return ApiResponse.buildResponse(500, null, "查询失败: " + e.getMessage());
}
}
}

View File

@@ -1,139 +0,0 @@
package com.ycwl.basic.controller.mobile.notify;
import com.ycwl.basic.model.mobile.notify.req.BatchRemainingCountReq;
import com.ycwl.basic.model.mobile.notify.req.NotificationAuthRecordReq;
import com.ycwl.basic.model.mobile.notify.resp.NotificationAuthRecordResp;
import com.ycwl.basic.model.mobile.notify.resp.ScenicTemplateAuthResp;
import com.ycwl.basic.model.pc.notify.entity.UserNotificationAuthorizationEntity;
import com.ycwl.basic.service.UserNotificationAuthorizationService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.repository.ScenicRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 用户通知授权记录Controller (移动端API)
* 只提供用户主动授权记录功能,支持批量授权,其他检查和消费功能由系统内部调用
*
* @Author: System
* @Date: 2024/12/28
*/
@RestController
@RequestMapping("/api/mobile/notify/auth")
@Slf4j
public class UserNotificationAuthController {
@Autowired
private UserNotificationAuthorizationService userNotificationAuthorizationService;
@Autowired
private ScenicRepository scenicRepository;
/**
* 记录用户通知授权 - 支持批量授权
* 用户主动同意通知授权时调用
*/
@PostMapping("/record")
public ApiResponse<NotificationAuthRecordResp> recordAuthorization(
@RequestBody NotificationAuthRecordReq req) {
log.debug("记录用户通知授权: templateIds={}, scenicId={}, requestId={}",
req.getTemplateIds(), req.getScenicId(), req.getRequestId());
try {
// 获取当前用户ID
Long memberId = JwtTokenUtil.getWorker().getUserId();
// 调用批量授权记录方法
List<UserNotificationAuthorizationService.AuthorizationRecord> records =
userNotificationAuthorizationService.batchRecordAuthorization(
memberId, req.getTemplateIds(), req.getScenicId(), req.getRequestId());
NotificationAuthRecordResp resp = new NotificationAuthRecordResp();
// 转换响应数据
List<NotificationAuthRecordResp.AuthorizationRecord> successRecords = new ArrayList<>();
List<String> failedTemplateIds = new ArrayList<>();
List<String> failureReasons = new ArrayList<>();
for (UserNotificationAuthorizationService.AuthorizationRecord record : records) {
if (record.isSuccess()) {
NotificationAuthRecordResp.AuthorizationRecord successRecord =
new NotificationAuthRecordResp.AuthorizationRecord();
successRecord.setId(record.getId());
successRecord.setTemplateId(record.getTemplateId());
successRecord.setScenicId(record.getScenicId());
successRecord.setAuthorizationCount(record.getAuthorizationCount());
successRecord.setConsumedCount(record.getConsumedCount());
successRecord.setRemainingCount(record.getRemainingCount());
successRecord.setLastAuthorizedTime(record.getLastAuthorizedTime());
successRecord.setLastConsumedTime(record.getLastConsumedTime());
successRecord.setStatus(record.getStatus());
successRecord.setCreateTime(record.getCreateTime());
successRecords.add(successRecord);
} else {
failedTemplateIds.add(record.getTemplateId());
failureReasons.add(record.getFailureReason());
}
}
resp.setAllSuccess(CollectionUtils.isEmpty(failedTemplateIds));
resp.setSuccessRecords(successRecords);
resp.setFailedTemplateIds(failedTemplateIds);
resp.setFailureReasons(failureReasons);
return ApiResponse.success(resp);
} catch (Exception e) {
log.error("记录用户通知授权失败", e);
return ApiResponse.fail("记录授权失败: " + e.getMessage());
}
}
/**
* 批量查询用户授权余额
* 返回 Map<wechatTemplateId, remainingCount>
*/
@PostMapping("/batch-remaining")
public ApiResponse<Map<String, Integer>> batchGetRemainingCount(
@RequestBody BatchRemainingCountReq req) {
log.debug("批量查询用户授权余额: templateIds={}, scenicId={}",
req.getTemplateIds(), req.getScenicId());
try {
Long memberId = JwtTokenUtil.getWorker().getUserId();
if (memberId == null) {
return ApiResponse.fail("用户未登录");
}
if (CollectionUtils.isEmpty(req.getTemplateIds())) {
return ApiResponse.success(new HashMap<>());
}
Map<String, UserNotificationAuthorizationEntity> authMap =
userNotificationAuthorizationService.batchCheckAuthorization(
memberId, req.getTemplateIds(), req.getScenicId());
// 转换为 templateId -> remainingCount
Map<String, Integer> result = new HashMap<>();
for (String templateId : req.getTemplateIds()) {
UserNotificationAuthorizationEntity entity = authMap.get(templateId);
int remaining = (entity != null && entity.getRemainingCount() != null)
? entity.getRemainingCount() : 0;
result.put(templateId, remaining);
}
return ApiResponse.success(result);
} catch (Exception e) {
log.error("批量查询用户授权余额失败", e);
return ApiResponse.fail("查询失败: " + e.getMessage());
}
}
}

View File

@@ -1,109 +0,0 @@
package com.ycwl.basic.controller.mobile.notify;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.mobile.notify.resp.WechatSubscribeAllScenesResp;
import com.ycwl.basic.model.mobile.notify.resp.WechatSubscribeSceneTemplatesResp;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeTemplateConfigEntity;
import com.ycwl.basic.service.notify.WechatSubscribeNotifyConfigService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.utils.NotificationAuthUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 微信小程序订阅消息:场景模板查询(移动端API)
*
* @Author: System
* @Date: 2025/12/31
*/
@RestController
@RequestMapping("/api/mobile/notify/subscribe")
@Slf4j
public class WechatSubscribeNotifyController {
private final WechatSubscribeNotifyConfigService configService;
private final NotificationAuthUtils notificationAuthUtils;
public WechatSubscribeNotifyController(WechatSubscribeNotifyConfigService configService,
NotificationAuthUtils notificationAuthUtils) {
this.configService = configService;
this.notificationAuthUtils = notificationAuthUtils;
}
/**
* 获取“场景”下可申请授权的模板列表(支持按 scenicId 覆盖模板ID/开关/文案)
*/
@GetMapping("/scenic/{scenicId}/scenes/{sceneKey}/templates")
@IgnoreToken
public ApiResponse<WechatSubscribeSceneTemplatesResp> listSceneTemplates(@PathVariable("scenicId") Long scenicId,
@PathVariable("sceneKey") String sceneKey) {
if (scenicId == null) {
return ApiResponse.fail("scenicId不能为空");
}
if (StringUtils.isBlank(sceneKey)) {
return ApiResponse.fail("sceneKey不能为空");
}
Long memberId = JwtTokenUtil.getWorker().getUserId();
List<WechatSubscribeTemplateConfigEntity> configs = configService.listSceneTemplateConfigs(scenicId, sceneKey);
WechatSubscribeSceneTemplatesResp resp = new WechatSubscribeSceneTemplatesResp();
resp.setScenicId(scenicId);
resp.setSceneKey(sceneKey);
if (memberId == null) {
return ApiResponse.success(resp);
}
List<WechatSubscribeSceneTemplatesResp.TemplateInfo> templates = new ArrayList<>();
for (WechatSubscribeTemplateConfigEntity cfg : configs) {
if (cfg == null || StringUtils.isBlank(cfg.getWechatTemplateId())) {
continue;
}
String title = StringUtils.isNotBlank(cfg.getTitleTemplate())
? cfg.getTitleTemplate()
: cfg.getTemplateKey();
int remaining = notificationAuthUtils.getRemainingCount(memberId, cfg.getWechatTemplateId(), scenicId);
WechatSubscribeSceneTemplatesResp.TemplateInfo info = new WechatSubscribeSceneTemplatesResp.TemplateInfo();
info.setTemplateKey(cfg.getTemplateKey());
info.setWechatTemplateId(cfg.getWechatTemplateId());
info.setTitle(title);
info.setDescription(cfg.getDescription());
info.setRemainingCount(remaining);
info.setHasAuthorization(remaining > 0);
templates.add(info);
}
resp.setTemplates(templates);
log.debug("场景模板查询: scenicId={}, sceneKey={}, memberId={}, templateCount={}",
scenicId, sceneKey, memberId, Objects.requireNonNullElse(templates.size(), 0));
return ApiResponse.success(resp);
}
/**
* 获取景区下所有场景及其模板列表(静态配置,带缓存)
* 不含用户授权信息,用户授权信息通过 /api/mobile/notify/auth/batch-remaining 接口获取
*/
@GetMapping("/scenic/{scenicId}/scenes")
@IgnoreToken
public ApiResponse<WechatSubscribeAllScenesResp> listAllSceneTemplates(@PathVariable("scenicId") Long scenicId) {
if (scenicId == null) {
return ApiResponse.fail("scenicId不能为空");
}
WechatSubscribeAllScenesResp resp = configService.getAllScenesWithTemplatesCached(scenicId);
log.debug("所有场景模板查询: scenicId={}, sceneCount={}",
scenicId, resp.getScenes() != null ? resp.getScenes().size() : 0);
return ApiResponse.success(resp);
}
}

View File

@@ -1,50 +0,0 @@
package com.ycwl.basic.controller.monitor;
import com.ycwl.basic.integration.kafka.scheduler.AccountFaceSchedulerManager;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 人脸识别监控接口
* 提供调度器状态查询功能
*/
@RestController
@RequestMapping("/api/monitor/face-recognition")
@RequiredArgsConstructor
public class FaceRecognitionMonitorController {
private final AccountFaceSchedulerManager schedulerManager;
/**
* 获取所有账号的调度器统计信息
* <p>
* 示例返回:
* {
* "LTAI5xxx": {
* "accountKey": "LTAI5xxx",
* "cloudType": "ALI",
* "activeThreads": 3,
* "executorQueueSize": 12,
* "schedulerQueueSize": 45
* },
* "245xxx": {
* "accountKey": "245xxx",
* "cloudType": "BAIDU",
* "activeThreads": 8,
* "executorQueueSize": 5,
* "schedulerQueueSize": 20
* }
* }
*
* @return 所有账号的调度器状态
*/
@GetMapping("/schedulers")
public ApiResponse<Map<String, AccountFaceSchedulerManager.AccountSchedulerStats>> getAllSchedulerStats() {
return ApiResponse.success(schedulerManager.getAllStats());
}
}

View File

@@ -8,6 +8,8 @@ import com.ycwl.basic.model.pc.adminUser.resp.AdminUserListRespVO;
import com.ycwl.basic.model.pc.adminUser.resp.StaffSimpleInfoRespVO;
import com.ycwl.basic.service.pc.AdminUserService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -17,7 +19,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/adminUser/v1")
@Slf4j
// 系统后台用户管理
@Api(tags = "系统后台用户管理")
public class AdminUserController {
@Autowired
@@ -25,7 +27,7 @@ public class AdminUserController {
@PostMapping(value = "/login")
// 登录
@ApiOperation(value = "登录")
@IgnoreToken
public ApiResponse login(@RequestBody LoginReqVO loginReqVO) throws Exception {
log.info("{}:开始登录管理后台", loginReqVO.getAccount());
@@ -33,49 +35,49 @@ public class AdminUserController {
}
@PostMapping(value = "/updatePassword")
// 用户自己修改密码
@ApiOperation(value = "用户自己修改密码")
public ApiResponse updatePassword(@RequestBody UpdatePasswordReqVO updatePasswordReqVO) throws Exception {
log.info("{}:开始修改管理后台密码", updatePasswordReqVO.getId());
return adminUserService.updatePassword(updatePasswordReqVO);
}
@PostMapping(value = "/list")
// 系统后台用户列表
@ApiOperation(value = "系统后台用户列表")
//@IgnoreToken
public ApiResponse<PageInfo<List<AdminUserListRespVO>>> list(@RequestBody AdminUserListReqVO adminUserListReqVO) {
return adminUserService.list(adminUserListReqVO);
}
@PostMapping(value = "/query/list")
// 系统后台用户列表查询
@ApiOperation(value = "系统后台用户列表查询")
@IgnoreToken
public ApiResponse<PageInfo<List<AdminUserListRespVO>>> queryList(@RequestBody AdminUserListReqVO adminUserListReqVO) {
return adminUserService.list(adminUserListReqVO);
}
@PostMapping(value = "/add")
// 添加系统后台用户
@ApiOperation(value = "添加系统后台用户")
//@IgnoreToken
public ApiResponse add(@RequestBody AddOrUpdateAdminUserReqVO addOrUpdateAdminUserReqVO) {
return adminUserService.addOrUpdate(addOrUpdateAdminUserReqVO);
}
@PostMapping(value = "/update")
// 更新系统后台用户
@ApiOperation(value = "更新系统后台用户")
//@IgnoreToken
public ApiResponse update(@RequestBody AddOrUpdateAdminUserReqVO addOrUpdateAdminUserReqVO) {
return adminUserService.addOrUpdate(addOrUpdateAdminUserReqVO);
}
@GetMapping(value = "/delete/{id}")
// 删除
@ApiOperation(value = "删除")
//@IgnoreToken
public ApiResponse delete(@PathVariable("id") String id) {
return adminUserService.delete(id);
}
@PostMapping(value = "/resetPassword")
// 重置密码
@ApiOperation(value = "重置密码")
//@IgnoreToken
public ApiResponse resetPassword(@RequestBody ResetPasswordReqVO resetPasswordReqVO) {
log.info("{}:开始重置后台密码", resetPasswordReqVO.getId());

View File

@@ -14,6 +14,8 @@ import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
@@ -28,7 +30,7 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/broker/v1")
// 推客(推广人)管理
@Api(tags = "推客(推广人)管理")
public class BrokerController {
@Autowired
private BrokerService brokerService;
@@ -39,64 +41,64 @@ public class BrokerController {
@Autowired
private ScenicRepository scenicRepository;
// 分页查询
@ApiOperation("分页查询")
@PostMapping("/page")
public ApiResponse page(@RequestBody BrokerReqQuery brokerReqQuery){
return ApiResponse.success(brokerService.pageQuery(brokerReqQuery));
}
// 列表查询
@ApiOperation("列表查询")
@PostMapping("/list")
public ApiResponse list(@RequestBody BrokerReqQuery brokerReqQuery){
return ApiResponse.success(brokerService.list(brokerReqQuery));
}
// 详情查询
@ApiOperation("详情查询")
@GetMapping("/getDetails/{id}")
public ApiResponse getDetails(@PathVariable("id") Long id){
return ApiResponse.success(brokerService.getById(id));
}
// 新增或修改
@ApiOperation("新增或修改")
@PostMapping("/addOrUpdate")
public ApiResponse addOrUpdate(@RequestBody BrokerEntity broker){
return ApiResponse.success(brokerService.addOrUpdate(broker));
}
// 删除
@ApiOperation("删除")
@DeleteMapping("/delete/{id}")
public ApiResponse delete(@PathVariable("id") Long id){
return ApiResponse.success(brokerService.delete(id));
}
// 修改状态
@ApiOperation("修改状态")
@PutMapping("/updateStatus/{id}")
public ApiResponse updateStatus(@PathVariable("id") Long id){
return ApiResponse.success(brokerService.updateStatus(id));
}
// 修改状态
@ApiOperation("修改状态")
@PutMapping("/updateBrokerEnable/{id}")
public ApiResponse updateBrokerEnable(@PathVariable("id") Long id){
return ApiResponse.success(brokerService.updateBrokerEnable(id));
}
// 推客记录分页查询
@ApiOperation("推客记录分页查询")
@PostMapping("/record/page")
public ApiResponse pageRecord(@RequestBody BrokerRecordReqQuery brokerRecordReqQuery) {
return ApiResponse.success(brokerRecordService.pageQuery(brokerRecordReqQuery));
}
// 推客记录列表查询
@ApiOperation("推客记录列表查询")
@PostMapping("/record/list")
public ApiResponse listRecord(@RequestBody BrokerRecordReqQuery brokerRecordReqQuery) {
return ApiResponse.success(brokerRecordService.list(brokerRecordReqQuery));
}
// 推客记录详情查询
@ApiOperation("推客记录详情查询")
@GetMapping("/record/getDetails/{id}")
public ApiResponse getRecordDetails(@PathVariable("id") Long id) {
return ApiResponse.success(brokerRecordService.getById(id));
}
// 根据brokerId和时间范围查询每天的记录数量和orderPrice汇总
@ApiOperation("根据brokerId和时间范围查询每天的记录数量和orderPrice汇总")
@GetMapping("/{id}/record/summary")
public ApiResponse<List<DailySummaryRespVO>> getDailySummaryByBrokerId(
@PathVariable("id") Long brokerId,
@@ -105,7 +107,7 @@ public class BrokerController {
return ApiResponse.success(brokerRecordService.getDailySummaryByBrokerId(brokerId, startTime, endTime));
}
// 根据景区ID下载小程序二维码
@ApiOperation("根据景区ID下载小程序二维码")
@GetMapping("/{id}/QRCode")
public ApiResponse<String> downloadQrCode(@PathVariable Long id) {
BrokerRespVO broker = brokerService.getById(id);

View File

@@ -0,0 +1,72 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.biz.PriceBiz;
import com.ycwl.basic.model.pc.coupon.entity.CouponEntity;
import com.ycwl.basic.model.pc.coupon.req.CouponQueryReq;
import com.ycwl.basic.model.pc.coupon.resp.CouponRespVO;
import com.ycwl.basic.model.pc.price.resp.GoodsListRespVO;
import com.ycwl.basic.service.pc.CouponService;
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.*;
import java.util.List;
@RestController
@RequestMapping("/api/coupon/v1")
@Api(tags = "优惠券管理")
public class CouponController {
@Autowired
private CouponService couponService;
@Autowired
private PriceBiz priceBiz;
@GetMapping("/{scenicId}/goodsList")
public ApiResponse<List<GoodsListRespVO>> scenicGoodsList(@PathVariable Long scenicId) {
List<GoodsListRespVO> data = priceBiz.listGoodsByScenic(scenicId);
data.add(new GoodsListRespVO(-1L, "一口价"));
return ApiResponse.success(data);
}
@ApiOperation("新增优惠券")
@PostMapping("/add")
public ApiResponse<Integer> add(@RequestBody CouponEntity coupon) {
return ApiResponse.success(couponService.add(coupon));
}
@ApiOperation("更新优惠券")
@PostMapping("/update/{id}")
public ApiResponse<Boolean> update(@PathVariable Integer id, @RequestBody CouponEntity coupon) {
coupon.setId(id);
return ApiResponse.success(couponService.update(coupon));
}
@PutMapping("/updateStatus/{id}")
public ApiResponse<Boolean> updateStatus(@PathVariable Integer id) {
return ApiResponse.success(couponService.updateStatus(id));
}
@ApiOperation("删除优惠券")
@DeleteMapping("/delete/{id}")
public ApiResponse<Boolean> delete(@PathVariable Integer id) {
return ApiResponse.success(couponService.delete(id));
}
@ApiOperation("根据ID查询优惠券")
@GetMapping("/get/{id}")
public ApiResponse<CouponEntity> getById(@PathVariable Integer id) {
return ApiResponse.success(couponService.getById(id));
}
@ApiOperation("分页查询优惠券列表")
@PostMapping("/page")
public ApiResponse<PageInfo<CouponRespVO>> list(@RequestBody CouponQueryReq couponQuery) {
PageHelper.startPage(couponQuery.getPageNum(), couponQuery.getPageSize());
List<CouponRespVO> list = couponService.list(couponQuery);
PageInfo<CouponRespVO> pageInfo = new PageInfo<>(list);
return ApiResponse.success(pageInfo);
}
}

View File

@@ -0,0 +1,81 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
import com.ycwl.basic.model.pc.device.req.DeviceAddOrUpdateReq;
import com.ycwl.basic.model.pc.device.req.DeviceBatchSortRequest;
import com.ycwl.basic.model.pc.device.req.DeviceReqQuery;
import com.ycwl.basic.model.pc.device.req.DeviceSortRequest;
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
import com.ycwl.basic.model.pc.template.req.TemplateSortRequest;
import com.ycwl.basic.service.pc.DeviceService;
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.*;
/**
* @Author:longbinbin
* @Date:2024/12/2 16:13
*/
@RestController
@RequestMapping("/api/device/v1")
@Api(tags = "设备管理")
public class DeviceController {
@Autowired
private DeviceService deviceService;
@ApiOperation("设备分页查询")
@PostMapping("/page")
public ApiResponse<PageInfo<DeviceRespVO>> pageQuery(@RequestBody DeviceReqQuery deviceReqQuery) {
return deviceService.pageQuery(deviceReqQuery);
}
@ApiOperation("设备列表查询")
@PostMapping("/list")
public ApiResponse list(@RequestBody DeviceReqQuery deviceReqQuery) {
return deviceService.list(deviceReqQuery);
}
@ApiOperation("设备详情查询")
@GetMapping("/getDetails/{id}")
public ApiResponse<DeviceRespVO> getDetails(@PathVariable("id") Long id) {
return deviceService.getById(id);
}
@ApiOperation("新增或修改设备")
@PostMapping("/addOrUpdate")
public ApiResponse addOrUpdate(@RequestBody DeviceAddOrUpdateReq deviceReqQuery) {
return deviceService.addOrUpdate(deviceReqQuery);
}
@ApiOperation("删除设备")
@DeleteMapping("/delete/{id}")
public ApiResponse delete(@PathVariable("id") Long id) {
return deviceService.deleteById(id);
}
@ApiOperation("修改设备状态")
@PutMapping("/updateStatus/{id}")
public ApiResponse updateStatus(@PathVariable("id") Long id) {
return deviceService.updateStatus(id);
}
@ApiOperation("排序设备")
@PostMapping("/sort")
public ApiResponse<Boolean> sortDevice(@RequestBody DeviceSortRequest request) {
return deviceService.sortDevice(request.getDeviceId(), request.getAfterDeviceId());
}
@PostMapping("/scenic/{scenicId}/sortBatch")
public ApiResponse<Boolean> sortDeviceBatch(@PathVariable("scenicId") Long scenicId, @RequestBody DeviceBatchSortRequest request) {
return deviceService.batchSort(scenicId, request);
}
@GetMapping("/config/{id}")
public ApiResponse<DeviceConfigEntity> getConfig(@PathVariable("id") Long id) {
return ApiResponse.success(deviceService.getConfig(id));
}
@PostMapping("/saveConfig/{configId}")
public ApiResponse saveConfig(@PathVariable("configId") Long configId, @RequestBody DeviceConfigEntity deviceConfigEntity) {
deviceService.saveConfig(configId, deviceConfigEntity);
return ApiResponse.success(null);
}
}

View File

@@ -1,390 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.device.dto.config.*;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.device.dto.device.*;
import com.ycwl.basic.integration.device.dto.status.DeviceStatusDTO;
import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService;
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
import com.ycwl.basic.integration.device.service.DeviceStatusIntegrationService;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 设备管理 V2 版本控制器 - 基于 zt-device 集成服务
*
* @author Claude Code
* @date 2025-09-01
*/
@Slf4j
@RestController
@RequestMapping("/api/device/v2")
@RequiredArgsConstructor
public class DeviceV2Controller {
private final DeviceIntegrationService deviceIntegrationService;
private final DeviceConfigIntegrationService deviceConfigIntegrationService;
private final DeviceStatusIntegrationService deviceStatusIntegrationService;
// ========== 设备基础 CRUD 操作 ==========
/**
* 设备V2核心信息分页列表
*/
@GetMapping("/")
public ApiResponse<PageResponse<DeviceV2DTO>> listDevices(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) String no,
@RequestParam(required = false) String type,
@RequestParam(required = false) Integer isActive,
@RequestParam(required = false) Long scenicId) {
log.info("分页查询设备核心信息列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
page, pageSize, name, no, type, isActive, scenicId);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<DeviceV2DTO> response = deviceIntegrationService.listDevices(page, pageSize, name, no, type, isActive, scenicId, null);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("分页查询设备核心信息列表失败", e);
return ApiResponse.fail("分页查询设备列表失败: " + e.getMessage());
}
}
/**
* 根据ID获取设备信息
*/
@GetMapping("/{id}")
public ApiResponse<DeviceV2DTO> getDevice(@PathVariable Long id) {
try {
DeviceV2DTO device = deviceIntegrationService.getDevice(id);
return ApiResponse.success(device);
} catch (Exception e) {
log.error("获取设备信息失败, id: {}", id, e);
return ApiResponse.fail("获取设备信息失败: " + e.getMessage());
}
}
/**
* 根据设备编号获取设备信息
*/
@GetMapping("/no/{no}")
public ApiResponse<DeviceV2DTO> getDeviceByNo(@PathVariable String no) {
try {
DeviceV2DTO device = deviceIntegrationService.getDeviceByNo(no);
return ApiResponse.success(device);
} catch (Exception e) {
log.error("根据设备编号获取设备信息失败, no: {}", no, e);
return ApiResponse.fail("根据设备编号获取设备信息失败: " + e.getMessage());
}
}
/**
* 根据设备ID获取设备在线状态
*/
@GetMapping("/{id}/status")
public ApiResponse<DeviceStatusDTO> getDeviceOnlineStatus(@PathVariable Long id) {
log.info("获取设备在线状态, deviceId: {}", id);
try {
// 首先获取设备信息以获得设备编号
DeviceV2DTO device = deviceIntegrationService.getDevice(id);
if (device == null) {
return ApiResponse.fail("设备不存在");
}
// 使用设备编号查询在线状态
DeviceStatusDTO onlineStatus = deviceStatusIntegrationService.getDeviceStatus(device.getNo());
return ApiResponse.success(onlineStatus);
} catch (Exception e) {
log.error("获取设备在线状态失败, deviceId: {}", id, e);
return ApiResponse.fail("获取设备在线状态失败: " + e.getMessage());
}
}
/**
* 创建设备
*/
@PostMapping("/")
public ApiResponse<DeviceV2DTO> createDevice(@Valid @RequestBody CreateDeviceRequest request) {
log.info("创建设备, name: {}, no: {}, type: {}, sort: {}",
request.getName(), request.getNo(), request.getType(), request.getSort());
try {
DeviceV2DTO device = deviceIntegrationService.createDevice(request);
return ApiResponse.success(device);
} catch (Exception e) {
log.error("创建设备失败", e);
return ApiResponse.fail("创建设备失败: " + e.getMessage());
}
}
/**
* 创建IPC摄像头设备(快捷方法)
*/
@PostMapping("/ipc")
public ApiResponse<DeviceV2DTO> createIpcDevice(@RequestBody Map<String, Object> request) {
String name = (String) request.get("name");
String deviceNo = (String) request.get("no");
Long scenicId = Long.valueOf(request.get("scenicId").toString());
Integer sort = request.get("sort") != null ? Integer.valueOf(request.get("sort").toString()) : null;
log.info("创建IPC摄像头设备, name: {}, no: {}, scenicId: {}, sort: {}", name, deviceNo, scenicId, sort);
try {
DeviceV2DTO device;
if (sort != null) {
device = deviceIntegrationService.createIpcDeviceWithSort(name, deviceNo, scenicId, sort);
} else {
device = deviceIntegrationService.createIpcDevice(name, deviceNo, scenicId);
}
return ApiResponse.success(device);
} catch (Exception e) {
log.error("创建IPC摄像头设备失败", e);
return ApiResponse.fail("创建IPC摄像头设备失败: " + e.getMessage());
}
}
/**
* 创建自定义设备(快捷方法)
*/
@PostMapping("/custom")
public ApiResponse<DeviceV2DTO> createCustomDevice(@RequestBody Map<String, Object> request) {
String name = (String) request.get("name");
String deviceNo = (String) request.get("no");
Long scenicId = Long.valueOf(request.get("scenicId").toString());
Integer sort = request.get("sort") != null ? Integer.valueOf(request.get("sort").toString()) : null;
log.info("创建自定义设备, name: {}, no: {}, scenicId: {}, sort: {}", name, deviceNo, scenicId, sort);
try {
DeviceV2DTO device;
if (sort != null) {
device = deviceIntegrationService.createCustomDeviceWithSort(name, deviceNo, scenicId, sort);
} else {
device = deviceIntegrationService.createCustomDevice(name, deviceNo, scenicId);
}
return ApiResponse.success(device);
} catch (Exception e) {
log.error("创建自定义设备失败", e);
return ApiResponse.fail("创建自定义设备失败: " + e.getMessage());
}
}
/**
* 更新设备信息
*/
@PutMapping("/{id}")
public ApiResponse<String> updateDevice(@PathVariable Long id, @Valid @RequestBody UpdateDeviceRequest request) {
log.info("更新设备信息, id: {}", id);
try {
deviceIntegrationService.updateDevice(id, request);
return ApiResponse.success("设备信息更新成功");
} catch (Exception e) {
log.error("更新设备信息失败, id: {}", id, e);
return ApiResponse.fail("更新设备信息失败: " + e.getMessage());
}
}
/**
* 更新设备排序
*/
@PutMapping("/{id}/sort")
public ApiResponse<String> updateDeviceSort(@PathVariable Long id, @RequestBody Map<String, Integer> request) {
Integer sort = request.get("sort");
log.info("更新设备排序, id: {}, sort: {}", id, sort);
try {
deviceIntegrationService.updateDeviceSort(id, sort);
return ApiResponse.success("设备排序更新成功");
} catch (Exception e) {
log.error("更新设备排序失败, id: {}, sort: {}", id, sort, e);
return ApiResponse.fail("更新设备排序失败: " + e.getMessage());
}
}
/**
* 启用设备
*/
@PutMapping("/{id}/enable")
public ApiResponse<String> enableDevice(@PathVariable Long id) {
log.info("启用设备, id: {}", id);
try {
deviceIntegrationService.enableDevice(id);
return ApiResponse.success("设备启用成功");
} catch (Exception e) {
log.error("启用设备失败, id: {}", id, e);
return ApiResponse.fail("启用设备失败: " + e.getMessage());
}
}
/**
* 禁用设备
*/
@PutMapping("/{id}/disable")
public ApiResponse<String> disableDevice(@PathVariable Long id) {
log.info("禁用设备, id: {}", id);
try {
deviceIntegrationService.disableDevice(id);
return ApiResponse.success("设备禁用成功");
} catch (Exception e) {
log.error("禁用设备失败, id: {}", id, e);
return ApiResponse.fail("禁用设备失败: " + e.getMessage());
}
}
/**
* 删除设备
*/
@DeleteMapping("/{id}")
public ApiResponse<String> deleteDevice(@PathVariable Long id) {
log.info("删除设备, id: {}", id);
try {
deviceIntegrationService.deleteDevice(id);
return ApiResponse.success("设备删除成功");
} catch (Exception e) {
log.error("删除设备失败, id: {}", id, e);
return ApiResponse.fail("删除设备失败: " + e.getMessage());
}
}
// ========== 设备配置管理操作 ==========
/**
* 获取设备配置列表
*/
@GetMapping("/{id}/config")
public ApiResponse<List<DeviceConfigV2DTO>> getDeviceConfigs(@PathVariable Long id) {
try {
List<DeviceConfigV2DTO> configs = deviceConfigIntegrationService.getDeviceConfigs(id);
return ApiResponse.success(configs);
} catch (Exception e) {
log.error("获取设备配置列表失败, deviceId: {}", id, e);
return ApiResponse.fail("获取设备配置列表失败: " + e.getMessage());
}
}
/**
* 根据配置键获取配置
*/
@GetMapping("/{id}/config/{configKey}")
public ApiResponse<DeviceConfigV2DTO> getDeviceConfigByKey(@PathVariable Long id,
@PathVariable String configKey) {
try {
DeviceConfigV2DTO config = deviceConfigIntegrationService.getDeviceConfigByKey(id, configKey);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("根据键获取设备配置失败, deviceId: {}, configKey: {}", id, configKey, e);
return ApiResponse.fail("根据键获取设备配置失败: " + e.getMessage());
}
}
/**
* 根据设备编号获取配置列表
*/
@GetMapping("/no/{no}/config")
public ApiResponse<List<DeviceConfigV2DTO>> getDeviceConfigsByNo(@PathVariable String no) {
log.info("根据设备编号获取配置列表, deviceNo: {}", no);
try {
List<DeviceConfigV2DTO> configs = deviceConfigIntegrationService.getDeviceConfigsByNo(no);
return ApiResponse.success(configs);
} catch (Exception e) {
log.error("根据设备编号获取配置列表失败, deviceNo: {}", no, e);
return ApiResponse.fail("根据设备编号获取配置列表失败: " + e.getMessage());
}
}
/**
* 创建设备配置
*/
@PostMapping("/{id}/config")
public ApiResponse<DeviceConfigV2DTO> createDeviceConfig(@PathVariable Long id,
@Valid @RequestBody CreateDeviceConfigRequest request) {
log.info("创建设备配置, deviceId: {}, configKey: {}", id, request.getConfigKey());
try {
DeviceConfigV2DTO config = deviceConfigIntegrationService.createDeviceConfig(id, request);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("创建设备配置失败, deviceId: {}, configKey: {}", id, request.getConfigKey(), e);
return ApiResponse.fail("创建设备配置失败: " + e.getMessage());
}
}
/**
* 批量创建/更新设备配置
*/
@PostMapping("/{id}/config/batch")
public ApiResponse<BatchUpdateResponse> batchUpdateDeviceConfig(@PathVariable Long id,
@Valid @RequestBody BatchDeviceConfigRequest request) {
log.info("批量更新设备配置, deviceId: {}, configs count: {}", id, request.getConfigs().size());
try {
BatchUpdateResponse result = deviceConfigIntegrationService.batchUpdateDeviceConfig(id, request);
return ApiResponse.success(result);
} catch (Exception e) {
log.error("批量更新设备配置失败, deviceId: {}", id, e);
return ApiResponse.fail("批量更新设备配置失败: " + e.getMessage());
}
}
/**
* 更新设备配置
*/
@PutMapping("/{id}/config/{configId}")
public ApiResponse<String> updateDeviceConfig(@PathVariable Long id, @PathVariable Long configId,
@Valid @RequestBody UpdateDeviceConfigRequest request) {
log.info("更新设备配置, deviceId: {}, configId: {}", id, configId);
try {
deviceConfigIntegrationService.updateDeviceConfig(id, configId, request);
return ApiResponse.success("设备配置更新成功");
} catch (Exception e) {
log.error("更新设备配置失败, deviceId: {}, configId: {}", id, configId, e);
return ApiResponse.fail("更新设备配置失败: " + e.getMessage());
}
}
/**
* 删除设备配置
*/
@DeleteMapping("/{id}/config/{configId}")
public ApiResponse<String> deleteDeviceConfig(@PathVariable Long id, @PathVariable Long configId) {
log.info("删除设备配置, deviceId: {}, configId: {}", id, configId);
try {
deviceConfigIntegrationService.deleteDeviceConfig(id, configId);
return ApiResponse.success("设备配置删除成功");
} catch (Exception e) {
log.error("删除设备配置失败, deviceId: {}, configId: {}", id, configId, e);
return ApiResponse.fail("删除设备配置失败: " + e.getMessage());
}
}
// ========== 景区设备管理操作 ==========
/**
* 获取景区所有设备列表
*/
@GetMapping("/scenic/{scenicId}")
public ApiResponse<PageResponse<DeviceV2DTO>> getScenicAllDevices(@PathVariable Long scenicId,
@RequestParam(required = false) String name,
@RequestParam(required = false) String type,
@RequestParam(required = false) String no,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("获取景区所有设备列表, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize);
try {
PageResponse<DeviceV2DTO> response = deviceIntegrationService.listDevices(page, pageSize, name, no, type, null, scenicId, null);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("获取景区所有设备列表失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("获取景区所有设备列表失败: " + e.getMessage());
}
}
}

View File

@@ -1,176 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.device.entity.common.DeviceVideoContinuityCache;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.device.req.VideoContinuityReportReq;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 设备视频连续性检查控制器
* 提供查询设备视频连续性检查结果的接口
*
* @author Claude Code
* @date 2025-09-01
*/
@Slf4j
@RestController
@RequestMapping("/api/device/video-continuity")
@RequiredArgsConstructor
public class DeviceVideoContinuityController {
private static final String REDIS_KEY_PREFIX = "device:video:continuity:";
private static final int CACHE_TTL_HOURS = 24; // 缓存24小时
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final DeviceRepository deviceRepository;
/**
* 查询设备最近的视频连续性检查结果
*
* @param deviceId 设备ID
* @return 检查结果
*/
@GetMapping("/{deviceId}")
public ApiResponse<DeviceVideoContinuityCache> getDeviceContinuityResult(@PathVariable Long deviceId) {
log.info("查询设备 {} 的视频连续性检查结果", deviceId);
try {
String redisKey = REDIS_KEY_PREFIX + deviceId;
String cacheJson = redisTemplate.opsForValue().get(redisKey);
if (cacheJson == null) {
log.warn("未找到设备 {} 的视频连续性检查结果", deviceId);
return ApiResponse.buildResponse(404, null, "未找到该设备的检查结果,可能设备未配置存储或尚未执行检查");
}
DeviceVideoContinuityCache cache = objectMapper.readValue(cacheJson, DeviceVideoContinuityCache.class);
return ApiResponse.success(cache);
} catch (Exception e) {
log.error("查询设备 {} 视频连续性检查结果失败", deviceId, e);
return ApiResponse.buildResponse(500, null, "查询失败: " + e.getMessage());
}
}
/**
* 手动触发设备视频连续性检查
* 注意:仅用于测试和紧急情况,正常情况下由定时任务自动执行
*
* @param deviceId 设备ID
* @return 检查结果
*/
@PostMapping("/{deviceId}/check")
public ApiResponse<DeviceVideoContinuityCache> manualCheck(@PathVariable Long deviceId) {
log.info("手动触发设备 {} 的视频连续性检查", deviceId);
return ApiResponse.success(null);
}
/**
* 删除设备的视频连续性检查缓存
* 用于清理过期或错误的缓存数据
*
* @param deviceId 设备ID
* @return 删除结果
*/
@DeleteMapping("/{deviceId}")
public ApiResponse<String> deleteContinuityCache(@PathVariable Long deviceId) {
log.info("删除设备 {} 的视频连续性检查缓存", deviceId);
try {
String redisKey = REDIS_KEY_PREFIX + deviceId;
Boolean deleted = redisTemplate.delete(redisKey);
if (deleted != null && deleted) {
return ApiResponse.success("缓存删除成功");
} else {
return ApiResponse.buildResponse(404, null, "未找到该设备的缓存数据");
}
} catch (Exception e) {
log.error("删除设备 {} 视频连续性检查缓存失败", deviceId, e);
return ApiResponse.buildResponse(500, null, "删除失败: " + e.getMessage());
}
}
/**
* 外部工具上报视频连续性检查结果
* 通过设备编号(deviceNo)上报检查结果,无需认证
*
* @param reportReq 上报请求
* @return 上报结果
*/
@PostMapping("/report")
@IgnoreToken
public ApiResponse<DeviceVideoContinuityCache> reportContinuityResult(
@Validated @RequestBody VideoContinuityReportReq reportReq) {
log.info("外部工具上报设备 {} 的视频连续性检查结果", reportReq.getDeviceNo());
try {
// 1. 根据设备编号查询设备ID
DeviceEntity device = deviceRepository.getDeviceByDeviceNo(reportReq.getDeviceNo());
if (device == null) {
log.warn("设备编号 {} 不存在", reportReq.getDeviceNo());
return ApiResponse.buildResponse(404, null, "设备不存在: " + reportReq.getDeviceNo());
}
Long deviceId = device.getId();
// 2. 构建缓存对象
DeviceVideoContinuityCache cache = new DeviceVideoContinuityCache();
cache.setDeviceId(deviceId);
cache.setCheckTime(new Date());
cache.setStartTime(reportReq.getStartTime());
cache.setEndTime(reportReq.getEndTime());
cache.setSupport(reportReq.getSupport());
cache.setContinuous(reportReq.getContinuous());
cache.setTotalVideos(reportReq.getTotalVideos());
cache.setTotalDurationMs(reportReq.getTotalDurationMs());
cache.setMaxAllowedGapMs(reportReq.getMaxAllowedGapMs() != null
? reportReq.getMaxAllowedGapMs() : 2000L);
cache.setGapCount(reportReq.getGaps() != null ? reportReq.getGaps().size() : 0);
// 3. 转换间隙信息
if (reportReq.getGaps() != null && !reportReq.getGaps().isEmpty()) {
List<DeviceVideoContinuityCache.GapInfo> gapInfos = reportReq.getGaps().stream()
.map(gap -> new DeviceVideoContinuityCache.GapInfo(
gap.getBeforeFileName(),
gap.getAfterFileName(),
gap.getGapMs(),
gap.getGapStartTime(),
gap.getGapEndTime()
))
.collect(Collectors.toList());
cache.setGaps(gapInfos);
}
// 4. 存储到Redis
String redisKey = REDIS_KEY_PREFIX + deviceId;
String cacheJson = objectMapper.writeValueAsString(cache);
redisTemplate.opsForValue().set(redisKey, cacheJson, CACHE_TTL_HOURS, TimeUnit.HOURS);
log.info("设备 {} (ID: {}) 视频连续性检查结果上报成功: continuous={}, videos={}, gaps={}",
reportReq.getDeviceNo(), deviceId, cache.getContinuous(),
cache.getTotalVideos(), cache.getGapCount());
return ApiResponse.success(cache);
} catch (Exception e) {
log.error("外部工具上报设备 {} 视频连续性检查结果失败", reportReq.getDeviceNo(), e);
return ApiResponse.buildResponse(500, null, "上报失败: " + e.getMessage());
}
}
}

View File

@@ -1,44 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.extraDevice.req.ExtraDevicePageQueryReq;
import com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO;
import com.ycwl.basic.service.pc.ExtraDeviceService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
/**
* 外部设备管理控制器
*/
@Slf4j
@RestController
@RequestMapping("/api/extra_device/v1")
@RequiredArgsConstructor
public class ExtraDeviceController {
private final ExtraDeviceService extraDeviceService;
/**
* 分页查询外部设备列表
*
* @param req 查询请求参数,包含scenicId(可选)、pageNum、pageSize
* @return 分页查询结果,包含设备ID、景区ID、景区名称、设备名称、标识、状态、心跳时间、在线状态
*/
@PostMapping("/page")
public ApiResponse<PageInfo<ExtraDeviceRespVO>> page(@RequestBody ExtraDevicePageQueryReq req) {
log.info("分页查询外部设备列表, scenicId: {}, pageNum: {}, pageSize: {}",
req.getScenicId(), req.getPageNum(), req.getPageSize());
PageInfo<ExtraDeviceRespVO> pageInfo = extraDeviceService.pageQuery(req);
log.info("外部设备列表查询完成, total: {}, pages: {}",
pageInfo.getTotal(), pageInfo.getPages());
return ApiResponse.success(pageInfo);
}
}

View File

@@ -6,6 +6,8 @@ import com.ycwl.basic.model.pc.face.req.FaceReqQuery;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.service.pc.FaceService;
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.*;
@@ -17,40 +19,41 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/face/v1")
// 用户人脸管理
@Api(tags = "用户人脸管理")
public class FaceController {
@Autowired
private FaceService faceService;
// 分页查询用户人脸
@ApiOperation("分页查询用户人脸")
@PostMapping("/page")
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(@RequestBody FaceReqQuery faceReqQuery) {
return faceService.pageQuery(faceReqQuery);
}
// 用户人脸列表查询
@ApiOperation("用户人脸列表查询")
@PostMapping("/list")
public ApiResponse<List<FaceRespVO>> list(@RequestBody FaceReqQuery faceReqQuery) {
return faceService.list(faceReqQuery);
}
// 用户人脸详情查询
@ApiOperation("用户人脸详情查询")
@GetMapping("/getDetail/{id}")
public ApiResponse<FaceRespVO> getDetail(@PathVariable("id") Long id) {
return faceService.getById(id);
}
// 添加用户人脸信息
@ApiOperation("添加用户人脸信息")
@PostMapping("/add")
public ApiResponse<Integer> add(@RequestBody FaceEntity face) {
return faceService.add(face);
}
// 删除用户人脸信息
@ApiOperation("删除用户人脸信息")
@PostMapping("/deleteById/{id}")
public ApiResponse<Integer> deleteById(@PathVariable Long id) {
return faceService.deleteById(id);
}
// 批量删除用户人脸
@ApiOperation("批量删除用户人脸")
@PostMapping("/deleteByIds")
public ApiResponse<Integer> deleteByIds(@RequestBody List<Long> ids) {
return faceService.deleteByIds(ids);
}
}

View File

@@ -1,5 +1,4 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.kafka.service.FaceProcessingKafkaService;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
@@ -7,6 +6,8 @@ import com.ycwl.basic.model.pc.faceSample.req.FaceSampleReqQuery;
import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
import com.ycwl.basic.service.pc.FaceSampleService;
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.*;
@@ -16,50 +17,28 @@ import java.util.List;
* @Author:longbinbin
* @Date:2024/12/2 16:33
*/
@Deprecated
@RestController
@RequestMapping("/api/faceSample/v1")
// 人脸样本管理
@Api(tags = "人脸样本管理")
public class FaceSampleController {
@Autowired
private FaceSampleService FaceSampleService;
@Autowired(required = false)
private FaceProcessingKafkaService faceProcessingKafkaService;
// 分页查询人脸样本
@ApiOperation("分页查询人脸样本")
@PostMapping("/page")
public ApiResponse<PageInfo<FaceSampleRespVO>> pageQuery(@RequestBody FaceSampleReqQuery FaceSampleReqQuery) {
return FaceSampleService.pageQuery(FaceSampleReqQuery);
}
// 人脸样本列表查询
@ApiOperation("人脸样本列表查询")
@PostMapping("/list")
public ApiResponse<List<FaceSampleRespVO>> list(@RequestBody FaceSampleReqQuery FaceSampleReqQuery) {
return FaceSampleService.list(FaceSampleReqQuery);
}
// 人脸样本详情查询
@ApiOperation("人脸样本详情查询")
@GetMapping("/getDetail/{id}")
public ApiResponse<FaceSampleRespVO> getDetail(@PathVariable("id") Long id) {
return FaceSampleService.getById(id);
}
/**
* 重试失败的人脸识别
* 用于手动重试状态为-1的人脸样本
*
* @param id 人脸样本ID
* @return 重试结果
*/
@PostMapping("/retry/{id}")
public ApiResponse<String> retryFaceRecognition(@PathVariable("id") Long id) {
if (faceProcessingKafkaService == null) {
return ApiResponse.fail("Kafka服务未启用,无法重试人脸识别");
}
boolean success = faceProcessingKafkaService.retryFaceRecognition(id);
if (success) {
return ApiResponse.success("人脸识别重试任务已提交");
} else {
return ApiResponse.fail("提交重试任务失败,请检查人脸样本状态");
}
}
}

View File

@@ -6,6 +6,8 @@ import com.ycwl.basic.model.pc.member.req.MemberReqQuery;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.service.pc.MemberService;
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.*;
@@ -17,37 +19,37 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/member/v1")
// 前台用户管理
@Api(tags = "前台用户管理")
public class MemberController {
@Autowired
private MemberService memberService;
// 前台用户分页查询
@ApiOperation("前台用户分页查询")
@PostMapping("/page")
public ApiResponse<PageInfo<MemberRespVO>> pageQuery(@RequestBody MemberReqQuery memberReqQuery) {
return memberService.pageQuery(memberReqQuery);
}
// 前台用户列表查询
@ApiOperation("前台用户列表查询")
@PostMapping("/list")
public ApiResponse<List<MemberRespVO>> list(@RequestBody MemberReqQuery memberReqQuery) {
return memberService.list(memberReqQuery);
}
// 前台用户详情查询
@ApiOperation("前台用户详情查询")
@GetMapping("/getDetail/{id}")
public ApiResponse<MemberRespVO> getDetail(@PathVariable("id") Long id) {
return memberService.getById(id);
}
// 前台用户删除
@ApiOperation("前台用户删除")
@DeleteMapping("/delete/{id}")
public ApiResponse<Integer> delete(@PathVariable("id") Long id) {
return memberService.deleteById(id);
}
// 前台用户新增
@ApiOperation("前台用户新增")
@PostMapping("/add")
public ApiResponse<Integer> add(@RequestBody MemberEntity member) {
return memberService.add(member);
}
// 前台用户修改
@ApiOperation("前台用户修改")
@PostMapping("/update")
public ApiResponse<Integer> update(@RequestBody MemberEntity member) {
return memberService.update(member);

View File

@@ -0,0 +1,45 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.pc.menu.entity.MenuEntity;
import com.ycwl.basic.service.pc.MenuService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
/**
* @Author:longbinbin
* @Date:2024/12/3 10:03
*/
@RestController
@RequestMapping("/api/menu/v1")
@Api(tags = "系统菜单管理")
public class MenuController {
private MenuService menuService;
@GetMapping(value = "/list/{type}")
@ApiOperation(value = " 菜单列表")
@IgnoreToken
public ApiResponse list(@PathVariable("type") Integer type) {
return menuService.list(type);
}
@PostMapping("/add")
@ApiOperation(value = "添加菜单")
public ApiResponse add(@RequestBody MenuEntity menu) {
return menuService.add(menu);
}
@PostMapping("/update")
@ApiOperation(value = "修改菜单")
public ApiResponse update(@RequestBody MenuEntity menu) {
return menuService.update(menu);
}
@GetMapping("/delete/{id}")
@ApiOperation(value = "删除菜单")
public ApiResponse delete(@PathVariable("id") Long id) {
return menuService.deleteById(id);
}
}

View File

@@ -1,60 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.message.dto.ChannelsResponse;
import com.ycwl.basic.integration.message.dto.MessageListData;
import com.ycwl.basic.integration.message.service.MessageIntegrationService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/api/message/v1")
@RequiredArgsConstructor
public class MessageController {
private final MessageIntegrationService messageService;
@GetMapping("/messages")
public ApiResponse<MessageListData> listMessages(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize,
@RequestParam(required = false) String channelId,
@RequestParam(required = false) String title,
@RequestParam(required = false) String content,
@RequestParam(required = false) String sendBiz,
@RequestParam(required = false) String sentAtStart,
@RequestParam(required = false) String sentAtEnd,
@RequestParam(required = false) String createdAtStart,
@RequestParam(required = false) String createdAtEnd
) {
log.debug("PC|消息列表查询 page={}, pageSize={}, channelId={}, title={}, sendBiz={}", page, pageSize, channelId, title, sendBiz);
if (pageSize > 100) {
pageSize = 100;
}
try {
MessageListData data = messageService.listMessages(page, pageSize, channelId, title, content, sendBiz,
sentAtStart, sentAtEnd, createdAtStart, createdAtEnd);
return ApiResponse.success(data);
} catch (Exception e) {
log.error("PC|消息列表查询失败", e);
return ApiResponse.fail("消息列表查询失败: " + e.getMessage());
}
}
@GetMapping("/channels")
public ApiResponse<ChannelsResponse> listChannels() {
log.debug("PC|获取消息通道列表");
try {
ChannelsResponse data = messageService.listChannels();
return ApiResponse.success(data);
} catch (Exception e) {
log.error("PC|获取消息通道列表失败", e);
return ApiResponse.fail("获取消息通道列表失败: " + e.getMessage());
}
}
}

View File

@@ -7,6 +7,8 @@ import com.ycwl.basic.model.pc.order.req.OrderReqQuery;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
import com.ycwl.basic.service.pc.OrderService;
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.*;
@@ -18,38 +20,38 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/order/v1")
// 订单管理
@Api(tags = "订单管理")
public class OrderController {
@Autowired
private OrderService orderService;
// 审核退款: 审核退款
@ApiOperation(value = "审核退款", notes = "审核退款")
@PostMapping("/auditRefundOrder")
public ApiResponse<?> auditRefundOrder(@RequestBody RefundOrderReq refundOrderReq) {
return orderService.auditRefundOrder(refundOrderReq);
}
// 分页查询订单
@ApiOperation("分页查询订单")
@PostMapping("page")
public ApiResponse<PageInfo<OrderRespVO>> pageQuery(@RequestBody OrderReqQuery query) {
return orderService.pageQuery(query);
}
// 订单列表查询
@ApiOperation("订单列表查询")
@PostMapping("list")
public ApiResponse<List<OrderRespVO>> list(@RequestBody OrderReqQuery query) {
return orderService.list(query);
}
// 订单详情查询
@ApiOperation("订单详情查询")
@GetMapping("detail/{id}")
public ApiResponse<OrderRespVO> detail(@PathVariable("id") Long orderId) {
return orderService.detail(orderId);
}
// 订单备注
@ApiOperation("订单备注")
@PostMapping("remark/{id}")
public ApiResponse<?> updateRemark(@PathVariable("id") Long orderId, @RequestBody OrderEntity query) {
orderService.remarkOrder(orderId, query);

View File

@@ -1,15 +1,13 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.mapper.AdminUserMapper;
import com.ycwl.basic.model.pc.adminUser.entity.LoginEntity;
import com.ycwl.basic.model.pc.permission.entity.PermissionEntity;
import com.ycwl.basic.model.pc.permission.req.PermissionSaveReq;
import com.ycwl.basic.model.pc.permission.resp.PermissionResp;
import com.ycwl.basic.model.pc.role.resp.RolePermissionResp;
import com.ycwl.basic.service.pc.PermissionService;
import com.ycwl.basic.service.pc.RoleService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -26,36 +24,22 @@ import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
@RestController
@RequestMapping("/api/permission/v1")
// 权限管理接口
@Api(tags = "权限管理接口")
public class PermissionController {
@Autowired
private PermissionService permissionService;
@Autowired
private AdminUserMapper adminUserMapper;
@Autowired
private RoleService roleService;
@GetMapping("/get/")
public ApiResponse<PermissionResp> getPermissionByUser() {
String userId = BaseContextHandler.getUserId();
if (MERCHANT.type.equals(BaseContextHandler.getRoleId())) {
PermissionEntity permission = permissionService.getPermissionByUserId(Long.parseLong(userId));
if (permission == null || StringUtils.isEmpty(permission.getPermString())) {
return ApiResponse.success(new PermissionResp(new ArrayList<>(), new ArrayList<>()));
}
return ApiResponse.success(new PermissionResp(Arrays.asList(StringUtils.split(permission.getPermString(), ",")), Arrays.asList(StringUtils.split(permission.getMenuString(), ","))));
} else {
// admin
LoginEntity login = adminUserMapper.getById(Long.parseLong(userId));
RolePermissionResp permissionByRoleId = roleService.getPermissionByRoleId(login.getRoleId());
if (permissionByRoleId == null) {
return ApiResponse.success(new PermissionResp(new ArrayList<>(), new ArrayList<>()));
}
return ApiResponse.success(new PermissionResp(Arrays.asList(StringUtils.split(permissionByRoleId.getPermStr(), ",")), Arrays.asList(StringUtils.split(permissionByRoleId.getMenuStr(), ","))));
PermissionEntity permission = permissionService.getPermissionByUserId(Long.parseLong(userId));
if (permission == null || StringUtils.isEmpty(permission.getPermString())) {
return ApiResponse.success(new PermissionResp(new ArrayList<>(), new ArrayList<>()));
}
return ApiResponse.success(new PermissionResp(Arrays.asList(StringUtils.split(permission.getPermString(), ",")), Arrays.asList(StringUtils.split(permission.getMenuString(), ","))));
}
// 根据用户ID查询权限信息
@ApiOperation("根据用户ID查询权限信息")
@GetMapping("/get/{userId}")
public ApiResponse<PermissionResp> getPermissionByUser(@PathVariable Long userId) {
PermissionEntity permission = permissionService.getPermissionByUserId(userId);
@@ -65,7 +49,7 @@ public class PermissionController {
return ApiResponse.success(new PermissionResp(Arrays.asList(StringUtils.split(permission.getPermString(), ",")), Arrays.asList(StringUtils.split(permission.getMenuString(), ","))));
}
// 保存或更新权限信息
@ApiOperation("保存或更新权限信息")
@PostMapping("/save/{userId}")
public ApiResponse saveOrUpdate(@PathVariable Long userId, @RequestBody PermissionSaveReq req) {
permissionService.saveOrUpdate(userId, StringUtils.join(req.getPermissions(), ","), StringUtils.join(req.getMenus(), ","));

View File

@@ -1,8 +1,13 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.biz.PriceBiz;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.price.entity.PriceConfigEntity;
import com.ycwl.basic.model.pc.price.req.PriceConfigListReq;
import com.ycwl.basic.model.pc.price.resp.GoodsListRespVO;
import com.ycwl.basic.model.pc.price.resp.SimpleGoodsRespVO;
import com.ycwl.basic.model.pc.price.resp.PriceConfigRespVO;
import com.ycwl.basic.repository.PriceRepository;
import com.ycwl.basic.service.pc.PriceConfigService;
import com.ycwl.basic.utils.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -14,13 +19,61 @@ import java.util.List;
public class PriceConfigController {
@Autowired
private PriceBiz priceBiz;
private PriceConfigService priceConfigService;
@Autowired
private PriceRepository priceRepository;
@GetMapping("/goodsList")
public ApiResponse<List<SimpleGoodsRespVO>> goodsList(
@RequestParam Long scenicId,
@RequestParam(required = false) String productType) {
return ApiResponse.success(priceBiz.listSimpleGoodsByScenic(scenicId, productType));
public ApiResponse<List<GoodsListRespVO>> goodsList(@RequestParam Long scenicId) {
return ApiResponse.success(priceConfigService.listGoodsByScenic(scenicId));
}
@PostMapping("/add")
public ApiResponse<PriceConfigEntity> addPriceConfig(@RequestBody PriceConfigEntity priceConfig) {
priceConfig.setId(null);
priceConfigService.save(priceConfig);
return ApiResponse.success(priceConfig);
}
@PostMapping("/update")
public ApiResponse<PriceConfigEntity> updatePriceConfig(@RequestBody PriceConfigEntity priceConfig) {
priceRepository.clearPriceCache(priceConfig.getId());
priceConfigService.updateById(priceConfig);
priceRepository.clearPriceCache(priceConfig.getId());
return ApiResponse.success(priceConfig);
}
@DeleteMapping("/delete/{id}")
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);
}
@GetMapping("/{id}")
public ApiResponse<PriceConfigRespVO> getPriceConfigById(@PathVariable Integer id) {
PriceConfigRespVO config = priceConfigService.findById(id);
priceConfigService.fillGoodsName(config);
return ApiResponse.success(config);
}
@GetMapping("/list")
public ApiResponse<PageInfo<PriceConfigRespVO>> list(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@ModelAttribute PriceConfigListReq req) {
PageHelper.startPage(pageNum, pageSize);
List<PriceConfigRespVO> result = priceConfigService.listByCondition(req);
priceConfigService.fillGoodsName(result);
PageInfo<PriceConfigRespVO> pageInfo = new PageInfo<>(result);
return ApiResponse.success(pageInfo);
}
}

View File

@@ -1,14 +1,10 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.mapper.PrintTaskMapper;
import com.ycwl.basic.model.pc.printer.entity.PrintTaskEntity;
import com.ycwl.basic.model.pc.printer.entity.PrinterEntity;
import com.ycwl.basic.model.pc.printer.req.PrintTaskReqQuery;
import com.ycwl.basic.model.pc.printer.req.ReprintRequest;
import com.ycwl.basic.service.printer.PrinterService;
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -26,97 +22,33 @@ public class PrinterController {
@Autowired
private PrinterService printerService;
@Autowired
private PrintTaskMapper printTaskMapper;
// 查询列表
@ApiOperation("查询列表")
@PostMapping("/list")
public ApiResponse<List<PrinterEntity>> list(@RequestBody PrinterEntity condition) {
return printerService.list(condition);
}
// 获取详情
@ApiOperation("获取详情")
@GetMapping("/get/{id}")
public ApiResponse<PrinterEntity> get(@PathVariable("id") Integer id) {
return printerService.get(id);
}
// 新增
@ApiOperation("新增")
@PostMapping("/add")
public ApiResponse<Integer> add(@RequestBody PrinterEntity entity) {
return printerService.add(entity);
}
// 更新
@ApiOperation("更新")
@PostMapping("/update")
public ApiResponse<Integer> update(@RequestBody PrinterEntity entity) {
return printerService.update(entity);
}
// 删除
@ApiOperation("删除")
@DeleteMapping("/delete/{id}")
public ApiResponse<Integer> delete(@PathVariable("id") Integer id) {
return printerService.delete(id);
}
// 分页查询打印任务
@PostMapping("/task/page")
public ApiResponse<PageInfo<PrintTaskEntity>> taskPage(@RequestBody PrintTaskReqQuery req) {
PageHelper.startPage(req.getPageNum(), req.getPageSize());
List<PrintTaskEntity> list = printTaskMapper.queryByCondition(req.getPrinterId(), req.getStatus());
PageInfo<PrintTaskEntity> pageInfo = new PageInfo<>(list);
return ApiResponse.success(pageInfo);
}
// 重新打印(将状态设置为0-未开始,并更新打印机名称)
@PostMapping("/task/reprint/{id}")
public ApiResponse<Integer> reprint(@PathVariable("id") Integer id, @RequestBody ReprintRequest request) {
int result = printerService.handleReprint(id, request);
return ApiResponse.success(result);
}
/**
* 查询待审核的打印任务
* @param printerId 打印机ID(可选)
* @return 待审核任务列表
*/
@GetMapping("/task/pending-review")
public ApiResponse<List<PrintTaskEntity>> getPendingReviewTasks(Integer printerId) {
List<PrintTaskEntity> tasks = printerService.getPendingReviewTasks(printerId);
return ApiResponse.success(tasks);
}
/**
* 更新待审核任务的URL(重新处理水印等)
* @param taskId 任务ID
* @param url 新的打印URL
* @return 操作结果
*/
@PostMapping("/task/{taskId}/url")
public ApiResponse<Boolean> updateTaskUrl(@PathVariable("taskId") Integer taskId, @RequestBody String url) {
boolean success = printerService.updatePendingReviewTaskUrl(taskId, url);
return ApiResponse.success(success);
}
/**
* 批准待审核任务,下发到打印队列
* @param taskIds 任务ID列表
* @return 成功数量
*/
@PostMapping("/task/approve")
public ApiResponse<Integer> approveTasks(@RequestBody List<Integer> taskIds) {
int count = printerService.approvePrintTasks(taskIds);
return ApiResponse.success(count);
}
/**
* 拒绝待审核任务
* @param taskIds 任务ID列表
* @return 成功数量
*/
@PostMapping("/task/reject")
public ApiResponse<Integer> rejectTasks(@RequestBody List<Integer> taskIds) {
int count = printerService.rejectPrintTasks(taskIds);
return ApiResponse.success(count);
}
}

View File

@@ -1,131 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.model.pc.printer.entity.PrinterEntity;
import com.ycwl.basic.model.pc.printer.req.PrinterPreferredSizeUpdateReq;
import com.ycwl.basic.model.pc.printer.req.PrinterStatusUpdateReq;
import com.ycwl.basic.model.pc.printer.req.PrinterUsePrinterUpdateReq;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiConst;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 打印机管理接口
*/
@RestController
@RequestMapping("/api/pc/printers/v1")
@RequiredArgsConstructor
public class PrinterManageController {
private final PrinterService printerService;
/**
* 打印机列表查询
*/
@GetMapping
public ApiResponse<List<PrinterEntity>> list(@RequestParam(value = "scenicId", required = false) Long scenicId,
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "name", required = false) String name) {
PrinterEntity condition = new PrinterEntity();
condition.setScenicId(scenicId);
condition.setStatus(status);
condition.setName(name);
return printerService.list(condition);
}
/**
* 打印机详情
*/
@GetMapping("/{id}")
public ApiResponse<PrinterEntity> detail(@PathVariable("id") Integer id) {
ApiResponse<PrinterEntity> response = printerService.get(id);
if (response.getData() == null) {
return ApiResponse.buildResponse(ApiConst.Code.CODE_NOT_EXIST, "打印机不存在");
}
return response;
}
/**
* 新增打印机
*/
@PostMapping
public ApiResponse<Integer> create(@RequestBody PrinterEntity request) {
request.setId(null);
return printerService.add(request);
}
/**
* 更新打印机信息
*/
@PutMapping("/{id}")
public ApiResponse<Integer> update(@PathVariable("id") Integer id, @RequestBody PrinterEntity request) {
request.setId(id);
return printerService.update(request);
}
/**
* 更新打印机状态
*/
@PatchMapping("/{id}/status")
public ApiResponse<Integer> updateStatus(@PathVariable("id") Integer id,
@RequestBody PrinterStatusUpdateReq req) {
if (req == null || req.getStatus() == null) {
return ApiResponse.buildResponse(ApiConst.Code.CODE_PARAM_ERROR, "状态不能为空");
}
PrinterEntity entity = new PrinterEntity();
entity.setId(id);
entity.setStatus(req.getStatus());
return printerService.update(entity);
}
/**
* 更新打印机首选尺寸
*/
@PatchMapping("/{id}/preferred-size")
public ApiResponse<Integer> updatePreferredSize(@PathVariable("id") Integer id,
@RequestBody PrinterPreferredSizeUpdateReq req) {
if (req == null || (req.getPreferW() == null && req.getPreferH() == null)) {
return ApiResponse.buildResponse(ApiConst.Code.CODE_PARAM_ERROR, "首选尺寸不能为空");
}
PrinterEntity entity = new PrinterEntity();
entity.setId(id);
entity.setPreferW(req.getPreferW());
entity.setPreferH(req.getPreferH());
return printerService.update(entity);
}
/**
* 更新当前使用的打印机
*/
@PatchMapping("/{id}/use-printer")
public ApiResponse<Integer> updateUsePrinter(@PathVariable("id") Integer id,
@RequestBody PrinterUsePrinterUpdateReq req) {
if (req == null) {
return ApiResponse.buildResponse(ApiConst.Code.CODE_PARAM_ERROR, "请求参数不能为空");
}
PrinterEntity entity = new PrinterEntity();
entity.setId(id);
entity.setUsePrinter(req.getUsePrinter());
return printerService.update(entity);
}
/**
* 删除打印机
*/
@DeleteMapping("/{id}")
public ApiResponse<Integer> delete(@PathVariable("id") Integer id) {
return printerService.delete(id);
}
}

View File

@@ -1,257 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.profitshare.dto.CalculateResultVO;
import com.ycwl.basic.integration.profitshare.dto.CalculateShareRequest;
import com.ycwl.basic.integration.profitshare.dto.ManualShareRequest;
import com.ycwl.basic.integration.profitshare.dto.TypesVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordDetailVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordVO;
import com.ycwl.basic.integration.profitshare.dto.rule.CreateRuleRequest;
import com.ycwl.basic.integration.profitshare.dto.rule.RuleVO;
import com.ycwl.basic.integration.profitshare.service.ProfitShareIntegrationService;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 分账管理 V2 版本控制器 - 基于 zt-profitshare 集成服务
*
* @author Claude Code
* @date 2025-01-11
*/
@Slf4j
@RestController
@RequestMapping("/api/profit-share/v2")
@RequiredArgsConstructor
public class ProfitShareV2Controller {
private final ProfitShareIntegrationService profitShareIntegrationService;
// ========== 分账规则管理 ==========
/**
* 创建分账规则
*/
@PostMapping("/rules")
public ApiResponse<RuleVO> createRule(@Valid @RequestBody CreateRuleRequest request) {
log.info("创建分账规则, scenicId: {}, ruleName: {}, ruleType: {}",
request.getScenicId(), request.getRuleName(), request.getRuleType());
try {
RuleVO rule = profitShareIntegrationService.createRule(request);
return ApiResponse.success(rule);
} catch (Exception e) {
log.error("创建分账规则失败", e);
return ApiResponse.fail("创建分账规则失败: " + e.getMessage());
}
}
/**
* 查询分账规则列表
*/
@GetMapping("/rules")
public ApiResponse<PageResponse<RuleVO>> listRules(
@RequestParam(required = false) Long scenicId,
@RequestParam(required = false) String status,
@RequestParam(required = false) String ruleType,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("查询分账规则列表, scenicId: {}, status: {}, ruleType: {}, page: {}, pageSize: {}",
scenicId, status, ruleType, page, pageSize);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<RuleVO> response = profitShareIntegrationService.listRules(scenicId, status, ruleType, page, pageSize);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("查询分账规则列表失败", e);
return ApiResponse.fail("查询分账规则列表失败: " + e.getMessage());
}
}
/**
* 获取分账规则详情
*/
@GetMapping("/rules/{id}")
public ApiResponse<RuleVO> getRule(@PathVariable Long id) {
log.info("获取分账规则详情, id: {}", id);
try {
RuleVO rule = profitShareIntegrationService.getRule(id);
return ApiResponse.success(rule);
} catch (Exception e) {
log.error("获取分账规则详情失败, id: {}", id, e);
return ApiResponse.fail("获取分账规则详情失败: " + e.getMessage());
}
}
/**
* 更新分账规则
*/
@PutMapping("/rules/{id}")
public ApiResponse<RuleVO> updateRule(@PathVariable Long id, @Valid @RequestBody CreateRuleRequest request) {
log.info("更新分账规则, id: {}", id);
try {
RuleVO rule = profitShareIntegrationService.updateRule(id, request);
return ApiResponse.success(rule);
} catch (Exception e) {
log.error("更新分账规则失败, id: {}", id, e);
return ApiResponse.fail("更新分账规则失败: " + e.getMessage());
}
}
/**
* 启用分账规则
*/
@PutMapping("/rules/{id}/enable")
public ApiResponse<String> enableRule(@PathVariable Long id) {
log.info("启用分账规则, id: {}", id);
try {
profitShareIntegrationService.enableRule(id);
return ApiResponse.success("规则已启用");
} catch (Exception e) {
log.error("启用分账规则失败, id: {}", id, e);
return ApiResponse.fail("启用分账规则失败: " + e.getMessage());
}
}
/**
* 禁用分账规则
*/
@PutMapping("/rules/{id}/disable")
public ApiResponse<String> disableRule(@PathVariable Long id) {
log.info("禁用分账规则, id: {}", id);
try {
profitShareIntegrationService.disableRule(id);
return ApiResponse.success("规则已禁用");
} catch (Exception e) {
log.error("禁用分账规则失败, id: {}", id, e);
return ApiResponse.fail("禁用分账规则失败: " + e.getMessage());
}
}
/**
* 删除分账规则
*/
@DeleteMapping("/rules/{id}")
public ApiResponse<String> deleteRule(@PathVariable Long id) {
log.info("删除分账规则, id: {}", id);
try {
profitShareIntegrationService.deleteRule(id);
return ApiResponse.success("规则已删除");
} catch (Exception e) {
log.error("删除分账规则失败, id: {}", id, e);
return ApiResponse.fail("删除分账规则失败: " + e.getMessage());
}
}
// ========== 分账记录查询 ==========
/**
* 查询景区分账记录
*/
@GetMapping("/records/scenic/{scenicId}")
public ApiResponse<PageResponse<RecordVO>> getRecordsByScenic(
@PathVariable Long scenicId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("查询景区分账记录, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<RecordVO> response = profitShareIntegrationService.getRecordsByScenic(scenicId, page, pageSize);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("查询景区分账记录失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("查询景区分账记录失败: " + e.getMessage());
}
}
/**
* 查询分账记录详情
*/
@GetMapping("/records/{id}")
public ApiResponse<RecordDetailVO> getRecordById(@PathVariable Long id) {
log.info("查询分账记录详情, id: {}", id);
try {
RecordDetailVO record = profitShareIntegrationService.getRecordById(id);
return ApiResponse.success(record);
} catch (Exception e) {
log.error("查询分账记录详情失败, id: {}", id, e);
return ApiResponse.fail("查询分账记录详情失败: " + e.getMessage());
}
}
/**
* 按订单ID查询分账记录
*/
@GetMapping("/records/order/{orderId}")
public ApiResponse<RecordDetailVO> getRecordByOrderId(@PathVariable String orderId) {
log.info("按订单ID查询分账记录, orderId: {}", orderId);
try {
RecordDetailVO record = profitShareIntegrationService.getRecordByOrderId(orderId);
return ApiResponse.success(record);
} catch (Exception e) {
log.error("按订单ID查询分账记录失败, orderId: {}", orderId, e);
return ApiResponse.fail("按订单ID查询分账记录失败: " + e.getMessage());
}
}
// ========== 手动分账与计算 ==========
/**
* 手动触发分账
*/
@PostMapping("/manual")
public ApiResponse<String> manualShare(@Valid @RequestBody ManualShareRequest request) {
log.info("手动触发分账, orderId: {}", request.getOrderId());
try {
profitShareIntegrationService.manualShare(request.getOrderId());
return ApiResponse.success("手动分账触发成功");
} catch (Exception e) {
log.error("手动触发分账失败, orderId: {}", request.getOrderId(), e);
return ApiResponse.fail("手动触发分账失败: " + e.getMessage());
}
}
/**
* 计算分账结果(不执行)
*/
@PostMapping("/calculate")
public ApiResponse<CalculateResultVO> calculateShare(@Valid @RequestBody CalculateShareRequest request) {
log.info("计算分账结果, scenicId: {}, totalAmount: {}", request.getScenicId(), request.getTotalAmount());
try {
CalculateResultVO result = profitShareIntegrationService.calculateShare(request);
return ApiResponse.success(result);
} catch (Exception e) {
log.error("计算分账结果失败", e);
return ApiResponse.fail("计算分账结果失败: " + e.getMessage());
}
}
// ========== 类型查询 ==========
/**
* 获取支持的类型列表
*/
@GetMapping("/types")
public ApiResponse<TypesVO> getSupportedTypes() {
log.info("获取支持的类型列表");
try {
TypesVO types = profitShareIntegrationService.getSupportedTypes();
return ApiResponse.success(types);
} catch (Exception e) {
log.error("获取支持的类型列表失败", e);
return ApiResponse.fail("获取支持的类型列表失败: " + e.getMessage());
}
}
}

View File

@@ -1,102 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.model.pc.project.entity.ProjectEntity;
import com.ycwl.basic.model.pc.project.req.ProjectReqQuery;
import com.ycwl.basic.model.pc.project.resp.ProjectRespVO;
import com.ycwl.basic.service.pc.ProjectService;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.WxMpUtil;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.File;
/**
* 景区项目管理控制器
*
* @Author: Claude
* @Date: 2025-01-15
*/
@RestController
@RequestMapping("/api/project/v1")
public class ProjectController {
@Autowired
private ProjectService projectService;
@Autowired
private ScenicRepository scenicRepository;
// 分页查询
@PostMapping("/page")
public ApiResponse page(@RequestBody ProjectReqQuery projectReqQuery) {
return ApiResponse.success(projectService.pageQuery(projectReqQuery));
}
// 列表查询
@PostMapping("/list")
public ApiResponse list(@RequestBody ProjectReqQuery projectReqQuery) {
return ApiResponse.success(projectService.list(projectReqQuery));
}
// 详情查询
@GetMapping("/getDetails/{id}")
public ApiResponse getDetails(@PathVariable("id") Long id) {
return ApiResponse.success(projectService.getById(id));
}
// 新增或修改
@PostMapping("/addOrUpdate")
public ApiResponse addOrUpdate(@RequestBody ProjectEntity project) {
return ApiResponse.success(projectService.addOrUpdate(project));
}
// 删除
@DeleteMapping("/delete/{id}")
public ApiResponse delete(@PathVariable("id") Long id) {
return ApiResponse.success(projectService.delete(id));
}
// 修改状态
@PutMapping("/updateStatus/{id}")
public ApiResponse updateStatus(@PathVariable("id") Long id) {
return ApiResponse.success(projectService.updateStatus(id));
}
// 根据项目ID下载小程序二维码
@GetMapping("/{id}/QRCode")
public ApiResponse<String> downloadQrCode(@PathVariable Long id) {
ProjectRespVO project = projectService.getById(id);
if (project == null) {
return ApiResponse.fail("项目不存在");
}
MpConfigEntity mpConfig = scenicRepository.getScenicMpConfig(project.getScenicId());
if (mpConfig == null) {
return ApiResponse.fail("小程序配置不存在");
}
String appId = mpConfig.getAppId();
String appSecret = mpConfig.getAppSecret();
String appState = mpConfig.getState();
String path = "pages/home/index?scenicId=" + project.getScenicId() + "&projectId=" + id;
String filePath = "qr_code_project_" + id + ".jpg";
IStorageAdapter adapter = StorageFactory.use();
if (adapter.isExists(filePath)) {
return ApiResponse.success(adapter.getUrl(filePath));
}
try {
WxMpUtil.generateWXAQRCode(appId, appSecret, appState, path, filePath);
File file = new File(filePath);
String s = adapter.uploadFile(null, file, filePath);
file.delete();
adapter.setAcl(StorageAcl.PUBLIC_READ, filePath);
return ApiResponse.success(s);
} catch (Exception e) {
return ApiResponse.fail("生成二维码失败");
}
}
}

View File

@@ -1,313 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.CreateQuestionnaireRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
import com.ycwl.basic.integration.questionnaire.service.QuestionnaireIntegrationService;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.WxMpUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.util.Map;
/**
* 问卷管理 V2 版本控制器 - 基于 zt-questionnaire 集成服务
*
* @author Claude Code
* @date 2025-09-05
*/
@Slf4j
@RestController
@RequestMapping("/api/questionnaire/v2")
@RequiredArgsConstructor
public class QuestionnaireV2Controller {
private final QuestionnaireIntegrationService questionnaireIntegrationService;
private final ScenicRepository scenicRepository;
// ========== 问卷管理 CRUD 操作 ==========
/**
* 分页查询问卷列表
*/
@GetMapping("/")
public ApiResponse<PageResponse<QuestionnaireResponse>> listQuestionnaires(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String name) {
log.info("分页查询问卷列表, page: {}, pageSize: {}, status: {}, name: {}",
page, pageSize, status, name);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<QuestionnaireResponse> response =
questionnaireIntegrationService.getQuestionnaireList(page, pageSize, name, status, null);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("分页查询问卷列表失败", e);
return ApiResponse.fail("分页查询问卷列表失败: " + e.getMessage());
}
}
/**
* 获取问卷详情
*/
@GetMapping("/{id}")
public ApiResponse<QuestionnaireResponse> getQuestionnaire(@PathVariable Long id) {
log.info("获取问卷详情, id: {}", id);
try {
QuestionnaireResponse questionnaire = questionnaireIntegrationService.getQuestionnaire(id);
return ApiResponse.success(questionnaire);
} catch (Exception e) {
log.error("获取问卷详情失败, id: {}", id, e);
return ApiResponse.fail("获取问卷详情失败: " + e.getMessage());
}
}
/**
* 创建问卷
*/
@PostMapping("/")
public ApiResponse<QuestionnaireResponse> createQuestionnaire(@Valid @RequestBody CreateQuestionnaireRequest request) {
log.info("创建问卷, name: {}, questions count: {}",
request.getName(), request.getQuestions() != null ? request.getQuestions().size() : 0);
try {
QuestionnaireResponse questionnaire = questionnaireIntegrationService.createQuestionnaire(request, "admin");
return ApiResponse.success(questionnaire);
} catch (Exception e) {
log.error("创建问卷失败", e);
return ApiResponse.fail("创建问卷失败: " + e.getMessage());
}
}
/**
* 更新问卷
*/
@PutMapping("/{id}")
public ApiResponse<QuestionnaireResponse> updateQuestionnaire(
@PathVariable Long id,
@Valid @RequestBody CreateQuestionnaireRequest request) {
log.info("更新问卷, id: {}", id);
try {
QuestionnaireResponse questionnaire = questionnaireIntegrationService.updateQuestionnaire(id, request, "admin");
return ApiResponse.success(questionnaire);
} catch (Exception e) {
log.error("更新问卷失败, id: {}", id, e);
return ApiResponse.fail("更新问卷失败: " + e.getMessage());
}
}
/**
* 更新问卷状态
*/
@PutMapping("/{id}/status")
public ApiResponse<String> updateQuestionnaireStatus(@PathVariable Long id, @RequestBody Map<String, Integer> request) {
Integer status = request.get("status");
log.info("更新问卷状态, id: {}, status: {}", id, status);
try {
// 根据状态调用不同的方法
if (status == 2) {
questionnaireIntegrationService.publishQuestionnaire(id, "admin");
} else if (status == 3) {
questionnaireIntegrationService.stopQuestionnaire(id, "admin");
}
return ApiResponse.success("问卷状态更新成功");
} catch (Exception e) {
log.error("更新问卷状态失败, id: {}, status: {}", id, status, e);
return ApiResponse.fail("更新问卷状态失败: " + e.getMessage());
}
}
/**
* 发布问卷
*/
@PutMapping("/{id}/publish")
public ApiResponse<String> publishQuestionnaire(@PathVariable Long id) {
log.info("发布问卷, id: {}", id);
try {
questionnaireIntegrationService.publishQuestionnaire(id, "admin");
return ApiResponse.success("问卷发布成功");
} catch (Exception e) {
log.error("发布问卷失败, id: {}", id, e);
return ApiResponse.fail("发布问卷失败: " + e.getMessage());
}
}
/**
* 停止问卷
*/
@PutMapping("/{id}/stop")
public ApiResponse<String> stopQuestionnaire(@PathVariable Long id) {
log.info("停止问卷, id: {}", id);
try {
questionnaireIntegrationService.stopQuestionnaire(id, "admin");
return ApiResponse.success("问卷停止成功");
} catch (Exception e) {
log.error("停止问卷失败, id: {}", id, e);
return ApiResponse.fail("停止问卷失败: " + e.getMessage());
}
}
/**
* 删除问卷
*/
@DeleteMapping("/{id}")
public ApiResponse<String> deleteQuestionnaire(@PathVariable Long id) {
log.info("删除问卷, id: {}", id);
try {
questionnaireIntegrationService.deleteQuestionnaire(id, "admin");
return ApiResponse.success("问卷删除成功");
} catch (Exception e) {
log.error("删除问卷失败, id: {}", id, e);
return ApiResponse.fail("删除问卷失败: " + e.getMessage());
}
}
// ========== 问卷答案查看操作 ==========
/**
* 分页查询问卷答案
*/
@GetMapping("/{id}/answers")
public ApiResponse<PageResponse<ResponseDetailResponse>> getQuestionnaireAnswers(
@PathVariable Long id,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String userId,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
log.info("分页查询问卷答案, questionnaireId: {}, page: {}, pageSize: {}, userId: {}",
id, page, pageSize, userId);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<ResponseDetailResponse> response =
questionnaireIntegrationService.getResponseList(page, pageSize, id, userId, startTime, endTime);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("分页查询问卷答案失败, questionnaireId: {}", id, e);
return ApiResponse.fail("分页查询问卷答案失败: " + e.getMessage());
}
}
/**
* 获取特定答案详情
*/
@GetMapping("/{id}/answers/{answerId}")
public ApiResponse<ResponseDetailResponse> getQuestionnaireAnswer(@PathVariable Long id, @PathVariable Long answerId) {
log.info("获取问卷答案详情, questionnaireId: {}, answerId: {}", id, answerId);
try {
ResponseDetailResponse answer = questionnaireIntegrationService.getResponseDetail(answerId);
return ApiResponse.success(answer);
} catch (Exception e) {
log.error("获取问卷答案详情失败, questionnaireId: {}, answerId: {}", id, answerId, e);
return ApiResponse.fail("获取问卷答案详情失败: " + e.getMessage());
}
}
/**
* 查询用户答题记录
*/
@GetMapping("/answers/user/{userId}")
public ApiResponse<PageResponse<ResponseDetailResponse>> getUserAnswers(
@PathVariable String userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long questionnaireId) {
log.info("查询用户答题记录, userId: {}, page: {}, pageSize: {}, questionnaireId: {}",
userId, page, pageSize, questionnaireId);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<ResponseDetailResponse> response =
questionnaireIntegrationService.getResponseList(page, pageSize, questionnaireId, userId, null, null);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("查询用户答题记录失败, userId: {}", userId, e);
return ApiResponse.fail("查询用户答题记录失败: " + e.getMessage());
}
}
// ========== 统计功能 ==========
/**
* 获取问卷统计信息
*/
@GetMapping("/{id}/statistics")
public ApiResponse<QuestionnaireStatistics> getQuestionnaireStatistics(@PathVariable Long id) {
log.info("获取问卷统计信息, id: {}", id);
try {
QuestionnaireStatistics statistics = questionnaireIntegrationService.getStatistics(id);
return ApiResponse.success(statistics);
} catch (Exception e) {
log.error("获取问卷统计信息失败, id: {}", id, e);
return ApiResponse.fail("获取问卷统计信息失败: " + e.getMessage());
}
}
/**
* 下载问卷小程序二维码
*/
@GetMapping("/{id}/QRCode")
public ApiResponse<String> downloadQrCode(@PathVariable Long id) {
log.info("下载问卷小程序二维码, id: {}", id);
try {
// 获取问卷详情
QuestionnaireResponse questionnaire = questionnaireIntegrationService.getQuestionnaire(id);
if (questionnaire == null) {
return ApiResponse.fail("问卷不存在");
}
MpConfigEntity mpConfig = scenicRepository.getScenicMpConfig(3930324797233434624L);
if (mpConfig == null) {
return ApiResponse.fail("小程序配置不存在");
}
String appId = mpConfig.getAppId();
String appSecret = mpConfig.getAppSecret();
String appState = mpConfig.getState();
String path = "pages/questionnaire/index?id=" + id;
String filePath = "qr_code_questionnaire_" + id + ".jpg";
IStorageAdapter adapter = StorageFactory.use();
if (adapter.isExists(filePath)) {
return ApiResponse.success(adapter.getUrl(filePath));
}
WxMpUtil.generateWXAQRCode(appId, appSecret, appState, path, filePath);
File file = new File(filePath);
String s = adapter.uploadFile(null, file, filePath);
file.delete();
adapter.setAcl(StorageAcl.PUBLIC_READ, filePath);
return ApiResponse.success(s);
} catch (Exception e) {
log.error("生成问卷二维码失败, id: {}", id, e);
return ApiResponse.fail("生成二维码失败: " + e.getMessage());
}
}
}

View File

@@ -7,6 +7,8 @@ import com.ycwl.basic.model.pc.order.req.OrderReqQuery;
import com.ycwl.basic.model.pc.order.resp.OrderRespVO;
import com.ycwl.basic.service.pc.OrderService;
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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -23,38 +25,38 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/refund/v1")
// 退款订单管理
@Api(tags = "退款订单管理")
public class RefundController {
@Autowired
private OrderService orderService;
// 审核退款: 审核退款
@ApiOperation(value = "审核退款", notes = "审核退款")
@PostMapping("/auditRefundOrder")
public ApiResponse<?> auditRefundOrder(@RequestBody RefundOrderReq refundOrderReq) {
return orderService.auditRefundOrder(refundOrderReq);
}
// 分页查询订单
@ApiOperation("分页查询订单")
@PostMapping("page")
public ApiResponse<PageInfo<OrderRespVO>> pageQuery(@RequestBody OrderReqQuery query) {
return orderService.refundPageQuery(query);
}
// 订单列表查询
@ApiOperation("订单列表查询")
@PostMapping("list")
public ApiResponse<List<OrderRespVO>> list(@RequestBody OrderReqQuery query) {
return orderService.list(query);
}
// 订单详情查询
@ApiOperation("订单详情查询")
@GetMapping("detail/{id}")
public ApiResponse<OrderRespVO> detail(@PathVariable("id") Long orderId) {
return orderService.detail(orderId);
}
// 订单备注
@ApiOperation("订单备注")
@PostMapping("remark/{id}")
public ApiResponse<?> updateRemark(@PathVariable("id") Long orderId, @RequestBody OrderEntity query) {
orderService.remarkOrder(orderId, query);

View File

@@ -1,196 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.render.dto.config.BatchRenderWorkerConfigRequest;
import com.ycwl.basic.integration.render.dto.config.RenderWorkerConfigV2DTO;
import com.ycwl.basic.integration.render.service.RenderWorkerConfigIntegrationService;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 渲染工作器配置管理 V2 版本控制器
* 基于 zt-render-worker 微服务标准接口实现
*
* @author Claude Code
* @date 2025-09-06
*/
@Slf4j
@RestController
@RequestMapping("/api/render/worker/config/v2")
@RequiredArgsConstructor
public class RenderWorkerConfigV2Controller {
private final RenderWorkerConfigIntegrationService configIntegrationService;
/**
* 获取工作器所有配置
*
* @param workerId 工作器ID
* @return 工作器配置列表
*/
@GetMapping("/{workerId}")
public ApiResponse<List<RenderWorkerConfigV2DTO>> getWorkerConfigs(@PathVariable Long workerId) {
log.info("获取渲染工作器配置列表, workerId: {}", workerId);
try {
List<RenderWorkerConfigV2DTO> configs = configIntegrationService.getWorkerConfigs(workerId);
return ApiResponse.success(configs);
} catch (Exception e) {
log.error("获取渲染工作器配置列表失败, workerId: {}", workerId, e);
return ApiResponse.fail("获取渲染工作器配置列表失败: " + e.getMessage());
}
}
/**
* 获取工作器平铺配置
*
* @param workerId 工作器ID
* @return 平铺配置Map
*/
@GetMapping("/{workerId}/flat")
public ApiResponse<Map<String, Object>> getWorkerFlatConfig(@PathVariable Long workerId) {
log.info("获取渲染工作器平铺配置, workerId: {}", workerId);
try {
Map<String, Object> flatConfig = configIntegrationService.getWorkerFlatConfig(workerId);
return ApiResponse.success(flatConfig);
} catch (Exception e) {
log.error("获取渲染工作器平铺配置失败, workerId: {}", workerId, e);
return ApiResponse.fail("获取渲染工作器平铺配置失败: " + e.getMessage());
}
}
/**
* 根据配置键获取特定配置
*
* @param workerId 工作器ID
* @param configKey 配置键
* @return 配置信息
*/
@GetMapping("/{workerId}/key/{configKey}")
public ApiResponse<RenderWorkerConfigV2DTO> getWorkerConfigByKey(@PathVariable Long workerId,
@PathVariable String configKey) {
log.info("根据配置键获取渲染工作器配置, workerId: {}, configKey: {}", workerId, configKey);
try {
RenderWorkerConfigV2DTO config = configIntegrationService.getWorkerConfigByKey(workerId, configKey);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("根据配置键获取渲染工作器配置失败, workerId: {}, configKey: {}", workerId, configKey, e);
return ApiResponse.fail("根据配置键获取渲染工作器配置失败: " + e.getMessage());
}
}
/**
* 创建工作器配置
*
* @param workerId 工作器ID
* @param config 配置信息
* @return 创建的配置信息
*/
@PostMapping("/{workerId}")
public ApiResponse<RenderWorkerConfigV2DTO> createWorkerConfig(@PathVariable Long workerId,
@Valid @RequestBody RenderWorkerConfigV2DTO config) {
log.info("创建渲染工作器配置, workerId: {}, configKey: {}", workerId, config.getConfigKey());
try {
RenderWorkerConfigV2DTO result = configIntegrationService.createWorkerConfig(workerId, config);
return ApiResponse.success(result);
} catch (Exception e) {
log.error("创建渲染工作器配置失败, workerId: {}", workerId, e);
return ApiResponse.fail("创建渲染工作器配置失败: " + e.getMessage());
}
}
/**
* 更新工作器配置
*
* @param workerId 工作器ID
* @param configId 配置ID
* @param updates 更新内容
* @return 操作结果
*/
@PutMapping("/{workerId}/{configId}")
public ApiResponse<Void> updateWorkerConfig(@PathVariable Long workerId,
@PathVariable Long configId,
@Valid @RequestBody Map<String, Object> updates) {
log.info("更新渲染工作器配置, workerId: {}, configId: {}", workerId, configId);
try {
configIntegrationService.updateWorkerConfig(workerId, configId, updates);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("更新渲染工作器配置失败, workerId: {}, configId: {}", workerId, configId, e);
return ApiResponse.fail("更新渲染工作器配置失败: " + e.getMessage());
}
}
/**
* 删除工作器配置
*
* @param workerId 工作器ID
* @param configId 配置ID
* @return 操作结果
*/
@DeleteMapping("/{workerId}/{configId}")
public ApiResponse<Void> deleteWorkerConfig(@PathVariable Long workerId,
@PathVariable Long configId) {
log.info("删除渲染工作器配置, workerId: {}, configId: {}", workerId, configId);
try {
configIntegrationService.deleteWorkerConfig(workerId, configId);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("删除渲染工作器配置失败, workerId: {}, configId: {}", workerId, configId, e);
return ApiResponse.fail("删除渲染工作器配置失败: " + e.getMessage());
}
}
/**
* 批量更新工作器配置
*
* @param workerId 工作器ID
* @param request 批量配置请求
* @return 操作结果
*/
@PostMapping("/{workerId}/batch")
public ApiResponse<Void> batchUpdateWorkerConfigs(@PathVariable Long workerId,
@Valid @RequestBody BatchRenderWorkerConfigRequest request) {
log.info("批量更新渲染工作器配置, workerId: {}, configCount: {}",
workerId, request.getConfigs() != null ? request.getConfigs().size() : 0);
try {
configIntegrationService.batchUpdateWorkerConfigs(workerId, request);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("批量更新渲染工作器配置失败, workerId: {}", workerId, e);
return ApiResponse.fail("批量更新渲染工作器配置失败: " + e.getMessage());
}
}
/**
* 批量平铺更新工作器配置
*
* @param workerId 工作器ID
* @param flatConfigs 平铺配置Map
* @return 操作结果
*/
@PostMapping("/{workerId}/flat-batch")
public ApiResponse<Void> batchFlatUpdateWorkerConfigs(@PathVariable Long workerId,
@Valid @RequestBody Map<String, Object> flatConfigs) {
log.info("批量平铺更新渲染工作器配置, workerId: {}, configCount: {}", workerId, flatConfigs.size());
try {
configIntegrationService.batchFlatUpdateWorkerConfigs(workerId, flatConfigs);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("批量平铺更新渲染工作器配置失败, workerId: {}", workerId, e);
return ApiResponse.fail("批量平铺更新渲染工作器配置失败: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,63 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.model.pc.renderWorker.entity.RenderWorkerEntity;
import com.ycwl.basic.model.pc.renderWorker.req.RenderWorkerReqQuery;
import com.ycwl.basic.service.pc.RenderWorkerService;
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.*;
/**
* @Author:longbinbin
* @Date:2024/12/3 14:59
*/
@RestController
@RequestMapping("/api/renderWorker/v1")
@Api(tags = "渲染机管理")
public class RenderWorkerController {
@Autowired
private RenderWorkerService renderWorkerService;
@ApiOperation("分页查询渲染机")
@PostMapping("/page")
public ApiResponse pageQuery(@RequestBody RenderWorkerReqQuery renderWorkerReqQuery){
return renderWorkerService.pageQuery(renderWorkerReqQuery);
}
@ApiOperation("渲染机列表查询")
@PostMapping("/list")
public ApiResponse list(@RequestBody RenderWorkerReqQuery renderWorkerReqQuery){
return renderWorkerService.list(renderWorkerReqQuery);
}
@ApiOperation("渲染机详情查询")
@GetMapping("/detail/{id}")
public ApiResponse detail(@PathVariable Long id){
return renderWorkerService.detail(id);
}
@ApiOperation("渲染机新增")
@PostMapping("/add")
public ApiResponse add(@RequestBody RenderWorkerEntity renderWorker){
return renderWorkerService.add(renderWorker);
}
@ApiOperation("渲染机删除")
@DeleteMapping("/delete/{id}")
public ApiResponse deleteById(@PathVariable Long id){
return renderWorkerService.deleteById(id);
}
@ApiOperation("渲染机修改")
@PostMapping("/update")
public ApiResponse update(@RequestBody RenderWorkerEntity renderWorker){
return renderWorkerService.update(renderWorker);
}
@ApiOperation("渲染机修改状态")
@PutMapping("/updateStatus/{id}")
public ApiResponse updateStatus(@PathVariable Long id) {
return renderWorkerService.updateStatus(id);
}
}

View File

@@ -1,175 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.dto.RenderWorkerWithStatusDTO;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.render.dto.worker.CreateRenderWorkerRequest;
import com.ycwl.basic.integration.render.dto.worker.RenderWorkerV2DTO;
import com.ycwl.basic.integration.render.dto.worker.UpdateRenderWorkerRequest;
import com.ycwl.basic.integration.render.service.RenderWorkerIntegrationService;
import com.ycwl.basic.model.task.req.ClientStatusReqVo;
import com.ycwl.basic.repository.RenderWorkerRepository;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* 渲染工作器管理 V2 版本控制器
* 基于 zt-render-worker 微服务标准接口实现
*
* @author Claude Code
* @date 2025-09-06
*/
@Slf4j
@RestController
@RequestMapping("/api/render/worker/v2")
@RequiredArgsConstructor
public class RenderWorkerV2Controller {
private final RenderWorkerIntegrationService renderWorkerIntegrationService;
private final RenderWorkerRepository renderWorkerRepository;
/**
* 分页查询渲染工作器列表(带保活信息)
*
* @param page 页码,从1开始
* @param pageSize 每页大小,默认10,最大100
* @param isEnabled 是否启用(0-禁用,1-启用)
* @param name 工作器名称(模糊搜索)
* @return 分页查询结果(包含保活信息)
*/
@GetMapping
public ApiResponse<PageResponse<RenderWorkerWithStatusDTO>> listWorkers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Integer isEnabled,
@RequestParam(required = false) String name) {
log.debug("分页查询渲染工作器列表, page: {}, pageSize: {}, isEnabled: {}, name: {}",
page, pageSize, isEnabled, name);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
// 获取基础工作器列表
PageResponse<RenderWorkerV2DTO> basicResult = renderWorkerIntegrationService.listWorkers(
page, pageSize, isEnabled, name);
// 转换为带保活信息的DTO列表
List<RenderWorkerWithStatusDTO> workersWithStatus = new ArrayList<>();
for (RenderWorkerV2DTO worker : basicResult.getList()) {
RenderWorkerWithStatusDTO workerWithStatus = new RenderWorkerWithStatusDTO();
// 复制基础信息
BeanUtils.copyProperties(worker, workerWithStatus);
// 查询保活信息
ClientStatusReqVo hostStatus = renderWorkerRepository.getWorkerHostStatus(worker.getId());
workerWithStatus.setHostStatus(hostStatus);
workerWithStatus.setIsOnline(hostStatus != null);
workersWithStatus.add(workerWithStatus);
}
// 构建带保活信息的分页响应
PageResponse<RenderWorkerWithStatusDTO> result = new PageResponse<>();
result.setList(workersWithStatus);
result.setTotal(basicResult.getTotal());
result.setPage(basicResult.getPage());
result.setPageSize(basicResult.getPageSize());
return ApiResponse.success(result);
} catch (Exception e) {
log.error("分页查询渲染工作器列表失败", e);
return ApiResponse.fail("分页查询渲染工作器列表失败: " + e.getMessage());
}
}
/**
* 根据ID获取渲染工作器详情
*
* @param id 工作器ID
* @return 工作器详情
*/
@GetMapping("/{id}")
public ApiResponse<RenderWorkerV2DTO> getWorker(@PathVariable Long id) {
log.debug("获取渲染工作器详情, id: {}", id);
try {
RenderWorkerV2DTO worker = renderWorkerIntegrationService.getWorker(id);
return ApiResponse.success(worker);
} catch (Exception e) {
log.error("获取渲染工作器详情失败, id: {}", id, e);
return ApiResponse.fail("获取渲染工作器详情失败: " + e.getMessage());
}
}
/**
* 创建渲染工作器
*
* @param request 创建请求
* @return 创建的工作器信息
*/
@PostMapping
public ApiResponse<RenderWorkerV2DTO> createWorker(@Valid @RequestBody CreateRenderWorkerRequest request) {
log.debug("创建渲染工作器, name: {}, key: {}, isActive: {}",
request.getName(), request.getKey(), request.getIsActive());
try {
RenderWorkerV2DTO worker = renderWorkerIntegrationService.createWorker(request);
return ApiResponse.success(worker);
} catch (Exception e) {
log.error("创建渲染工作器失败", e);
return ApiResponse.fail("创建渲染工作器失败: " + e.getMessage());
}
}
/**
* 更新渲染工作器
*
* @param id 工作器ID
* @param request 更新请求
* @return 操作结果
*/
@PutMapping("/{id}")
public ApiResponse<Void> updateWorker(@PathVariable Long id,
@Valid @RequestBody UpdateRenderWorkerRequest request) {
log.debug("更新渲染工作器, id: {}, name: {}, isActive: {}",
id, request.getName(), request.getIsActive());
try {
renderWorkerIntegrationService.updateWorker(id, request);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("更新渲染工作器失败, id: {}", id, e);
return ApiResponse.fail("更新渲染工作器失败: " + e.getMessage());
}
}
/**
* 删除渲染工作器
*
* @param id 工作器ID
* @return 操作结果
*/
@DeleteMapping("/{id}")
public ApiResponse<Void> deleteWorker(@PathVariable Long id) {
log.debug("删除渲染工作器, id: {}", id);
try {
renderWorkerIntegrationService.deleteWorker(id);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("删除渲染工作器失败, id: {}", id, e);
return ApiResponse.fail("删除渲染工作器失败: " + e.getMessage());
}
}
}

View File

@@ -2,62 +2,56 @@ package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.pc.permission.resp.PermissionResp;
import com.ycwl.basic.model.pc.role.req.AddOrUpdateRoleReqVO;
import com.ycwl.basic.model.pc.role.req.RoleListReqVO;
import com.ycwl.basic.model.pc.role.resp.RoleListRespVO;
import com.ycwl.basic.model.pc.role.resp.RolePermissionResp;
import com.ycwl.basic.service.pc.RoleService;
import com.ycwl.basic.utils.ApiResponse;
import org.apache.commons.lang3.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/api/role/v1")
// 系统角色管理
@Api(tags = "系统角色管理")
public class RoleController {
@Autowired
RoleService roleService;
@PostMapping(value = "/page")
// 角色列表分页查询
@ApiOperation(value = "角色列表分页查询")
@IgnoreToken
public ApiResponse<PageInfo<RoleListRespVO>> page(@RequestBody RoleListReqVO roleListReqVO) {
return roleService.pageQuery(roleListReqVO);
}
@PostMapping(value = "/list")
// 角色列表
@ApiOperation(value = "角色列表")
@IgnoreToken
public ApiResponse<List<RoleListRespVO>> list(@RequestBody RoleListReqVO roleListReqVO) {
return roleService.list(roleListReqVO);
}
@GetMapping("/{roleId}/permission")
// 角色权限列表
public ApiResponse<PermissionResp> getPermissionByRoleId(@PathVariable("roleId") Long roleId) {
RolePermissionResp permission = roleService.getPermissionByRoleId(roleId);
if (permission == null) {
return ApiResponse.fail("角色不存在");
}
return ApiResponse.success(new PermissionResp(Arrays.asList(StringUtils.split(permission.getPermStr(), ",")), Arrays.asList(StringUtils.split(permission.getMenuStr(), ","))));
}
@PostMapping(value = "/addOrUpdate")
// 添加或更新角色
@ApiOperation(value = "添加或更新角色")
@IgnoreToken
public ApiResponse addOrUpdate(@RequestBody AddOrUpdateRoleReqVO addOrUpdateRoleReqVO) {
return roleService.addOrUpdate(addOrUpdateRoleReqVO);
}
@GetMapping(value = "/delete/{id}")
// 删除
@ApiOperation(value = "删除")
@IgnoreToken
public ApiResponse delete(@PathVariable("id") String id) {
return roleService.delete(id);
}
@GetMapping(value = "/updateReturnMenu/{id}")
// 编辑回显该角色当前菜单
@ApiOperation(value = "编辑回显该角色当前菜单")
@IgnoreToken
public ApiResponse updateReturnMenu(@PathVariable("id") String id) {
return roleService.updateReturnMenu(id);
}
@@ -65,7 +59,8 @@ public class RoleController {
@GetMapping(value = "/updateStatus/{id}")
// 更改角色类型状态
@ApiOperation(value = "更改角色类型状态")
//@IgnoreToken
public ApiResponse updateStatus(@PathVariable("id") String id) {
return roleService.updateStatus(id);
}

View File

@@ -39,13 +39,6 @@ public class ScenicAccountController {
return result > 0 ? ApiResponse.success("更新成功") : ApiResponse.fail("更新失败");
}
// 激活/停用景区账号
@PostMapping("/updateActiveStatus/{id}")
public ApiResponse updateActiveStatus(@PathVariable Long id) {
int result = service.updateActiveStatus(id);
return result > 0 ? ApiResponse.success("操作成功") : ApiResponse.fail("操作失败");
}
// 更新景区账号
@PostMapping("/update")
public ApiResponse updateScenicAccount(@RequestBody ScenicAccountEntity entity) {

View File

@@ -1,12 +1,13 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
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.repository.ScenicRepository;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.service.mobile.AppScenicService;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.service.pc.ScenicAccountService;
@@ -16,20 +17,18 @@ import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.WxMpUtil;
import org.apache.commons.lang3.Strings;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.util.Collections;
import java.util.List;
import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN;
import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
/**
@@ -38,7 +37,7 @@ import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT;
*/
@RestController
@RequestMapping("/api/scenic/v1")
// 景区管理
@Api(tags = "景区管理")
public class ScenicController {
@Autowired
@@ -53,7 +52,69 @@ public class ScenicController {
@Autowired
private ScenicAccountService accountService;
// 根据景区ID下载小程序二维码
@ApiOperation("分页查询景区")
@PostMapping("/page")
public ApiResponse<PageInfo<ScenicRespVO>> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery) {
return scenicService.pageQuery(scenicReqQuery);
}
@ApiOperation("查询景区列表")
@PostMapping("/list")
public ApiResponse<List<ScenicRespVO>> list(@RequestBody ScenicReqQuery scenicReqQuery) {
return scenicService.list(scenicReqQuery);
}
@ApiOperation("查询景区详情")
@GetMapping("/getDetail/{id}")
public ApiResponse<ScenicRespVO> getDetail(@PathVariable Long id) {
return scenicService.getById(id);
}
@ApiOperation("新增景区")
@PostMapping("/add")
public ApiResponse<Boolean> add(@RequestBody ScenicAddOrUpdateReq scenicAddReq) {
return scenicService.add(scenicAddReq);
}
@ApiOperation("删除景区")
@GetMapping("/delete/{id}")
public ApiResponse<Boolean> delete(@PathVariable Long id) {
return scenicService.deleteById(id);
}
@ApiOperation("修改景区")
@PostMapping("/update")
public ApiResponse<Boolean> update(@RequestBody ScenicAddOrUpdateReq scenicAddReq) {
return scenicService.update(scenicAddReq);
}
@ApiOperation("修改景区状态")
@GetMapping("/updateStatus/{id}")
public ApiResponse<Boolean> updateStatus(@PathVariable Long id) {
return scenicService.updateStatus(id);
}
@ApiOperation("新增景区配置")
@PostMapping("/addConfig")
public ApiResponse<Boolean> addConfig(@RequestBody ScenicConfigEntity scenicConfig) {
return scenicService.addConfig(scenicConfig);
}
@ApiOperation("修改景区配置")
@PostMapping("/updateConfig")
public ApiResponse<Boolean> updateConfig(@RequestBody ScenicConfigEntity scenicConfig) {
return scenicService.updateConfigById(scenicConfig);
}
@ApiOperation("查询景区配置")
@GetMapping("/config/{id}")
public ApiResponse<ScenicConfigEntity> getConfig(@PathVariable("id") Long id) {
return ApiResponse.success(scenicService.getConfig(id));
}
@PostMapping("/saveConfig/{id}")
public ApiResponse saveConfig(@PathVariable("id") Long id, @RequestBody ScenicConfigEntity config) {
scenicService.saveConfig(id, config);
return ApiResponse.success(null);
}
@PostMapping("/saveConfig/undefined")
public ApiResponse saveConfig(@RequestBody ScenicConfigEntity config) {
scenicService.addConfig(config);
return ApiResponse.success(null);
}
@ApiOperation("根据景区ID下载小程序二维码")
@GetMapping("/{id}/QRCode")
public ApiResponse<String> downloadQrCode(@PathVariable Long id) {
MpConfigEntity mpConfig = scenicRepository.getScenicMpConfig(id);
@@ -108,19 +169,19 @@ public class ScenicController {
}
@GetMapping("/myScenicList")
public ApiResponse<List<ScenicV2DTO>> myScenicList() {
List<ScenicV2DTO> list = Collections.emptyList();
if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
public ApiResponse<List<ScenicRespVO>> myScenicList() {
List<ScenicRespVO> list = Collections.emptyList();
if (StringUtils.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) {
String userId = BaseContextHandler.getUserId();
ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId));
if (account == null || account.getScenicId().isEmpty()) {
return ApiResponse.fail("景区账号未绑定景区");
}
list = account.getScenicId().stream().map(id -> scenicRepository.getScenicBasic(id)).toList();
} else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) {
ScenicReqQuery query = new ScenicReqQuery();
query.setPageSize(1000);
list = scenicRepository.list(query);
list = account.getScenicId().stream().map(id -> {
return appScenicService.getDetails(id).getData();
}).toList();
} else {
list = scenicService.list(new ScenicReqQuery()).getData();
}
return ApiResponse.success(list);
}

View File

@@ -1,264 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.scenic.dto.config.BatchConfigRequest;
import com.ycwl.basic.integration.scenic.dto.config.BatchUpdateResponse;
import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest;
import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO;
import com.ycwl.basic.integration.scenic.dto.config.UpdateConfigRequest;
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
import com.ycwl.basic.utils.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* @Author:longbinbin
* @Date:2024/12/26
* 景区管理 V2 版本控制器 - 基于 zt-scenic 集成服务
*/
@Slf4j
@RestController
@RequestMapping("/api/scenic/v2")
@RequiredArgsConstructor
public class ScenicV2Controller {
private final ScenicIntegrationService scenicIntegrationService;
private final ScenicConfigIntegrationService scenicConfigIntegrationService;
// ========== 景区基础 CRUD 操作 ==========
/**
* 景区V2核心信息分页列表
*/
@GetMapping("/")
public ApiResponse<PageResponse<ScenicV2DTO>> listScenics(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String name) {
log.info("分页查询景区核心信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
// 参数验证:限制pageSize最大值为100
if (pageSize > 100) {
pageSize = 100;
}
try {
PageResponse<ScenicV2DTO> response = scenicIntegrationService.listScenics(page, pageSize, status, name, null);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("分页查询景区核心信息列表失败", e);
return ApiResponse.fail("分页查询景区列表失败: " + e.getMessage());
}
}
/**
* 查询单个景区详情
*/
@GetMapping("/{scenicId}")
public ApiResponse<ScenicV2DTO> getScenic(@PathVariable Long scenicId) {
log.info("查询景区详情, scenicId: {}", scenicId);
try {
ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId);
return ApiResponse.success(scenic);
} catch (Exception e) {
log.error("查询景区详情失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("查询景区详情失败: " + e.getMessage());
}
}
/**
* 查询景区列表(支持筛选和分页)- 高级筛选
*/
@PostMapping("/filter")
public ApiResponse<ScenicFilterPageResponse> filterScenics(@RequestBody @Valid ScenicFilterRequest request) {
log.info("高级筛选景区列表, 筛选条件数: {}, 页码: {}, 页大小: {}",
request.getFilters().size(), request.getPage(), request.getPageSize());
try {
ScenicFilterPageResponse response = scenicIntegrationService.filterScenics(request);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("高级筛选景区列表失败", e);
return ApiResponse.fail("高级筛选景区列表失败: " + e.getMessage());
}
}
/**
* 新增景区
*/
@PostMapping("/create")
public ApiResponse<ScenicV2DTO> createScenic(@RequestBody @Valid CreateScenicRequest request) {
log.info("新增景区, name: {}, mpId: {}", request.getName(), request.getMpId());
try {
ScenicV2DTO scenic = scenicIntegrationService.createScenic(request);
return ApiResponse.success(scenic);
} catch (Exception e) {
log.error("新增景区失败, name: {}", request.getName(), e);
return ApiResponse.fail("新增景区失败: " + e.getMessage());
}
}
/**
* 修改景区
*/
@PutMapping("/{scenicId}")
public ApiResponse<ScenicV2DTO> updateScenic(@PathVariable Long scenicId,
@RequestBody @Valid UpdateScenicRequest request) {
log.info("修改景区, scenicId: {}", scenicId);
try {
ScenicV2DTO scenic = scenicIntegrationService.updateScenic(scenicId, request);
return ApiResponse.success(scenic);
} catch (Exception e) {
log.error("修改景区失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("修改景区失败: " + e.getMessage());
}
}
/**
* 删除景区
*/
@DeleteMapping("/{scenicId}")
public ApiResponse<Void> deleteScenic(@PathVariable Long scenicId) {
log.info("删除景区, scenicId: {}", scenicId);
try {
scenicIntegrationService.deleteScenic(scenicId);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("删除景区失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("删除景区失败: " + e.getMessage());
}
}
/**
* 景区列表查询(默认1000条)
* 只支持根据状态筛选
*/
@GetMapping("/list")
public ApiResponse<PageResponse<ScenicV2DTO>> listScenicsByStatus(@RequestParam(required = false) Integer status) {
log.info("查询景区列表, status: {}", status);
try {
// 默认查询1000条数据,第1页
PageResponse<ScenicV2DTO> scenics = scenicIntegrationService.listScenics(1, 1000, status, null, null);
return ApiResponse.success(scenics);
} catch (Exception e) {
log.error("查询景区列表失败, status: {}", status, e);
return ApiResponse.fail("查询景区列表失败: " + e.getMessage());
}
}
// ========== 景区配置管理 ==========
/**
* 获取景区配置列表
*/
@GetMapping("/{scenicId}/config")
public ApiResponse<List<ScenicConfigV2DTO>> listConfigs(@PathVariable Long scenicId) {
log.info("获取景区配置列表, scenicId: {}", scenicId);
try {
List<ScenicConfigV2DTO> configs = scenicConfigIntegrationService.listConfigs(scenicId);
return ApiResponse.success(configs);
} catch (Exception e) {
log.error("获取景区配置列表失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("获取景区配置列表失败: " + e.getMessage());
}
}
/**
* 根据配置键获取配置
*/
@GetMapping("/{scenicId}/config/{configKey}")
public ApiResponse<ScenicConfigV2DTO> getConfigByKey(@PathVariable Long scenicId,
@PathVariable String configKey) {
log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey);
try {
ScenicConfigV2DTO config = scenicConfigIntegrationService.getConfigByKey(scenicId, configKey);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("根据键获取景区配置失败, scenicId: {}, configKey: {}", scenicId, configKey, e);
return ApiResponse.fail("获取配置失败: " + e.getMessage());
}
}
/**
* 创建景区配置
*/
@PostMapping("/{scenicId}/config")
public ApiResponse<ScenicConfigV2DTO> createConfig(@PathVariable Long scenicId,
@RequestBody @Valid CreateConfigRequest request) {
log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey());
try {
ScenicConfigV2DTO config = scenicConfigIntegrationService.createConfig(scenicId, request);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("创建景区配置失败, scenicId: {}, configKey: {}", scenicId, request.getConfigKey(), e);
return ApiResponse.fail("创建配置失败: " + e.getMessage());
}
}
/**
* 更新景区配置
*/
@PutMapping("/{scenicId}/config/{configId}")
public ApiResponse<ScenicConfigV2DTO> updateConfig(@PathVariable Long scenicId,
@PathVariable String configId,
@RequestBody @Valid UpdateConfigRequest request) {
log.info("更新景区配置, scenicId: {}, configId: {}", scenicId, configId);
try {
ScenicConfigV2DTO config = scenicConfigIntegrationService.updateConfig(scenicId, configId, request);
return ApiResponse.success(config);
} catch (Exception e) {
log.error("更新景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e);
return ApiResponse.fail("更新配置失败: " + e.getMessage());
}
}
/**
* 删除景区配置
*/
@DeleteMapping("/{scenicId}/config/{configId}")
public ApiResponse<Void> deleteConfig(@PathVariable Long scenicId, @PathVariable String configId) {
log.info("删除景区配置, scenicId: {}, configId: {}", scenicId, configId);
try {
scenicConfigIntegrationService.deleteConfig(scenicId, configId);
return ApiResponse.success(null);
} catch (Exception e) {
log.error("删除景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e);
return ApiResponse.fail("删除配置失败: " + e.getMessage());
}
}
/**
* 批量更新景区配置
*/
@PutMapping("/{scenicId}/config/batch")
public ApiResponse<BatchUpdateResponse> batchUpdateConfigs(@PathVariable Long scenicId,
@RequestBody @Valid BatchConfigRequest request) {
log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size());
try {
BatchUpdateResponse response = scenicConfigIntegrationService.batchUpdateConfigs(scenicId, request);
return ApiResponse.success(response);
} catch (Exception e) {
log.error("批量更新景区配置失败, scenicId: {}", scenicId, e);
return ApiResponse.fail("批量更新配置失败: " + e.getMessage());
}
}
}

View File

@@ -4,37 +4,34 @@ import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.printer.req.CreateVirtualOrderRequest;
import com.ycwl.basic.service.pc.SourceService;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @Author:longbinbin
* @Date:2024/12/3 15:45
*/
@RestController
@RequestMapping("/api/source/v1")
// 视频源管理
@Api(tags = "视频源管理")
public class SourceController {
@Autowired
private SourceService sourceService;
@Autowired
private PrinterService printerService;
// 分页查询视频源
@Deprecated
@ApiOperation("分页查询视频源")
@PostMapping("/page")
public ApiResponse pageQuery(@RequestBody SourceReqQuery sourceReqQuery) {
return sourceService.pageQuery(sourceReqQuery);
}
@Deprecated
// 查询视频源列表
@ApiOperation("查询视频源列表")
@PostMapping("/list")
public ApiResponse list(@RequestBody SourceReqQuery sourceReqQuery) {
return sourceService.list(sourceReqQuery);
@@ -45,34 +42,11 @@ public class SourceController {
return sourceService.cutVideo(id);
}
@Deprecated
// 删除视频源
@ApiOperation("删除视频源")
@DeleteMapping("/delete/{id}")
public ApiResponse deleteById(@PathVariable Long id) {
return sourceService.deleteById(id);
}
/**
* 创建虚拟用户0元订单
* 用于后台直接从source创建订单,不需要真实用户
*
* @param request 请求参数
* @return 订单信息
*/
@PostMapping("/createVirtualOrder")
public ApiResponse<Map<String, Object>> createVirtualOrder(@RequestBody CreateVirtualOrderRequest request) {
try {
Map<String, Object> result = printerService.createVirtualOrder(
request.getSourceId(),
request.getScenicId(),
request.getPrinterId(),
request.getNeedEnhance(),
request.getPrintImgUrl()
);
return ApiResponse.success(result);
} catch (Exception e) {
return ApiResponse.fail(e.getMessage());
}
}
}

View File

@@ -1,57 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.pc.statistics.resp.OrderStatisticsResp;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.service.pc.StatisticsService;
import com.ycwl.basic.utils.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
/**
* @Author:longbinbin
* @Date:2024/12/12 14:30
*/
@RestController
@RequestMapping("/api/statistics/v1")
public class StatisticsController {
@Autowired
private StatisticsService statisticsService;
@Autowired
private AppStatisticsService appStatisticsService;
/**
* 智能获取扫码访问人数统计数据(自动选择粒度)
* @param query 查询参数(包含景区ID、开始时间、结束时间;如果scenicId为空则统计全部景区,否则统计指定景区)
* @return 统计数据(超过7天返回日期级别,否则返回小时级别)
*/
@PostMapping("/scanCodeMemberChart")
public ApiResponse<List<HashMap<String, String>>> getScanCodeMemberChart(@RequestBody CommonQueryReq query) {
return ApiResponse.success(statisticsService.getScanCodeMemberChartAuto(query));
}
@PostMapping("/one")
public ApiResponse getStatisticsOne(@RequestBody CommonQueryReq query) {
return appStatisticsService.oneStatistics(query);
}
@PostMapping("/two")
public ApiResponse getStatisticsTwo(@RequestBody CommonQueryReq query) {
return appStatisticsService.twoStatistics(query);
}
/**
* 获取订单统计数据(包含订单数量和金额、推送订单数量、现场订单数量)
* @param query 查询参数(包含景区ID、开始时间、结束时间;如果scenicId为空则统计全部景区,否则统计指定景区)
* @return 统计数据(totalOrderCount: 总订单数量, totalOrderAmount: 总订单金额, pushOrderCount: 推送订单数量, sceneOrderCount: 现场订单数量)
*/
@PostMapping("/orderStatistics")
public ApiResponse<OrderStatisticsResp> getOrderStatistics(@RequestBody CommonQueryReq query) {
return ApiResponse.success(statisticsService.getOrderStatistics(query));
}
}

View File

@@ -4,6 +4,8 @@ import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.task.req.TaskReqQuery;
import com.ycwl.basic.service.pc.TaskService;
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.*;
@@ -13,38 +15,39 @@ import org.springframework.web.bind.annotation.*;
*/
@RestController
@RequestMapping("/api/task/v1")
// 任务列表管理
@Deprecated
@Api(tags = "任务列表管理")
public class TaskController {
@Autowired
private TaskService taskService;
// 分页查询任务列表
@ApiOperation("分页查询任务列表")
@PostMapping("/page")
public ApiResponse pageQuery(@RequestBody TaskReqQuery taskReqQuery) {
return taskService.pageQuery(taskReqQuery);
}
// 查询任务列表
@ApiOperation("查询任务列表")
@PostMapping("/list")
public ApiResponse list(@RequestBody TaskReqQuery taskReqQuery) {
return taskService.list(taskReqQuery);
}
// 查询任务详情
@ApiOperation("查询任务详情")
@GetMapping("/getDetail/{id}")
public ApiResponse getById(@PathVariable Long id) {
return taskService.getById(id);
}
// 删除任务
@ApiOperation("删除任务")
@DeleteMapping("/delete/{id}")
public ApiResponse deleteById(@PathVariable Long id) {
return taskService.deleteById(id);
}
// 修改任务
@ApiOperation("修改任务")
@PostMapping("/update")
public ApiResponse update(@RequestBody TaskEntity taskEntity) {
return taskService.update(taskEntity);
}
// 修改任务状态
@ApiOperation("修改任务状态")
@PostMapping("/updateStatus")
public ApiResponse updateStatus(@RequestParam Long id, @RequestParam Integer status) {
return taskService.updateStatus(id,status);

View File

@@ -9,6 +9,8 @@ import com.ycwl.basic.model.pc.template.req.TemplateSortRequest;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.service.pc.TemplateService;
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.*;
@@ -20,60 +22,54 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api/template/v1")
// 模板管理
@Api(tags = "模板管理")
public class TemplateController {
@Autowired
private TemplateService templateService;
// 分页查询模板
@ApiOperation("分页查询模板")
@PostMapping("/page")
public ApiResponse<PageInfo<TemplateRespVO>> pageQuery(@RequestBody TemplateReqQuery templateReqQuery) {
return templateService.pageQuery(templateReqQuery);
}
// 查询模板列表
@ApiOperation("查询模板列表")
@PostMapping("/list")
public ApiResponse<List<TemplateRespVO>> list(@RequestBody TemplateReqQuery templateReqQuery) {
return templateService.list(templateReqQuery);
}
// 查询模板详情
@ApiOperation("查询模板详情")
@GetMapping("getDetail/{id}")
public ApiResponse<TemplateRespVO> getById(@PathVariable Long id) {
return templateService.getById(id);
}
// 添加模板
@ApiOperation("添加模板")
@PostMapping("/add")
public ApiResponse<Boolean> add(@RequestBody TemplateEntity template) {
return templateService.add(template);
}
// 删除模板
@ApiOperation("删除模板")
@DeleteMapping("/delete/{id}")
public ApiResponse<Integer> deleteById(@PathVariable Long id) {
return templateService.deleteById(id);
}
// 修改模板
@ApiOperation("修改模板")
@PostMapping("/update")
public ApiResponse<Boolean> update(@RequestBody TemplateEntity template) {
return templateService.update(template);
}
// 修改模板状态
@ApiOperation("修改模板状态")
@PostMapping("/updateStatus/{id}")
public ApiResponse<Boolean> updateStatus(@PathVariable("id") Long id) {
return templateService.updateStatus(id);
}
// 排序模板
@ApiOperation("排序模板")
@PostMapping("/sort")
public ApiResponse<Boolean> sortTemplate(@RequestBody TemplateSortRequest request) {
return templateService.sortTemplate(request.getTemplateId(), request.getAfterTemplateId());
}
// 修改模板排序值
@PostMapping("/updateSort/{id}")
public ApiResponse<Boolean> updateSort(@PathVariable("id") Long id, @RequestParam Integer sort) {
return templateService.updateSort(id, sort);
}
@GetMapping("/config/{id}")
public ApiResponse<TemplateConfigEntity> getConfig(@PathVariable("id") Long id) {
return ApiResponse.success(templateService.getConfig(id));

View File

@@ -6,6 +6,8 @@ import com.ycwl.basic.model.pc.video.req.VideoReqQuery;
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
import com.ycwl.basic.service.pc.VideoService;
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.*;
@@ -18,38 +20,27 @@ import java.util.List;
@RestController
@RequestMapping("/api/video/v1")
// 视频成片管理
@Deprecated
@Api(tags = "视频成片管理")
public class VideoController {
@Autowired
private VideoService videoService;
// 分页查询成片
@ApiOperation("分页查询成片")
@PostMapping("/page")
public ApiResponse<PageInfo<VideoRespVO>> pageQuery(@RequestBody VideoReqQuery videoReqQuery) {
return videoService.pageQuery(videoReqQuery);
}
// 查询成片列表
@ApiOperation("查询成片列表")
@PostMapping("/list")
public ApiResponse<List<VideoRespVO>> list(@RequestBody VideoReqQuery videoReqQuery) {
return videoService.list(videoReqQuery);
}
// 查询成片详情
@ApiOperation("查询成片详情")
@GetMapping("/getDetail/{id}")
public ApiResponse<VideoRespVO> getById(@PathVariable Long id) {
return videoService.getById(id);
}
/**
* 查询视频是否被购买
*
* @param videoId 视频ID
* @return 是否已购买
*/
@GetMapping("/checkBuyStatus")
public ApiResponse<Boolean> checkBuyStatus(@RequestParam("videoId") Long videoId) {
Boolean isBuy = videoService.checkVideoBuyStatus(videoId);
return ApiResponse.success(isBuy);
}
}

View File

@@ -1,122 +0,0 @@
package com.ycwl.basic.controller.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeEventTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeSceneTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeSendLogEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeTemplateConfigEntity;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeEventTemplatePageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeEventTemplateSaveReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeSceneTemplatePageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeSceneTemplateSaveReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeSendLogPageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeTemplateConfigPageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeTemplateConfigSaveReq;
import com.ycwl.basic.service.pc.WechatSubscribeNotifyAdminService;
import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 微信小程序订阅消息:配置管理(管理后台)
*
* @Author: System
* @Date: 2025/12/31
*/
@Slf4j
@RestController
@RequestMapping("/api/wechatSubscribeNotify/v1")
@RequiredArgsConstructor
public class WechatSubscribeNotifyAdminController {
private final WechatSubscribeNotifyAdminService adminService;
// ========================= 模板配置 =========================
@PostMapping("/templateConfig/page")
public ApiResponse<PageInfo<WechatSubscribeTemplateConfigEntity>> pageTemplateConfig(
@RequestBody WechatSubscribeTemplateConfigPageReq req) {
return adminService.pageTemplateConfig(req);
}
@GetMapping("/templateConfig/detail/{id}")
public ApiResponse<WechatSubscribeTemplateConfigEntity> getTemplateConfig(@PathVariable("id") Long id) {
return adminService.getTemplateConfig(id);
}
@PostMapping("/templateConfig/save")
public ApiResponse<Boolean> saveTemplateConfig(@RequestBody WechatSubscribeTemplateConfigSaveReq req) {
return adminService.saveTemplateConfig(req);
}
@DeleteMapping("/templateConfig/delete/{id}")
public ApiResponse<Boolean> deleteTemplateConfig(@PathVariable("id") Long id) {
return adminService.deleteTemplateConfig(id);
}
// ========================= 场景映射 =========================
@PostMapping("/sceneTemplate/page")
public ApiResponse<PageInfo<WechatSubscribeSceneTemplateEntity>> pageSceneTemplate(
@RequestBody WechatSubscribeSceneTemplatePageReq req) {
return adminService.pageSceneTemplate(req);
}
@GetMapping("/sceneTemplate/detail/{id}")
public ApiResponse<WechatSubscribeSceneTemplateEntity> getSceneTemplate(@PathVariable("id") Long id) {
return adminService.getSceneTemplate(id);
}
@PostMapping("/sceneTemplate/save")
public ApiResponse<Boolean> saveSceneTemplate(@RequestBody WechatSubscribeSceneTemplateSaveReq req) {
return adminService.saveSceneTemplate(req);
}
@DeleteMapping("/sceneTemplate/delete/{id}")
public ApiResponse<Boolean> deleteSceneTemplate(@PathVariable("id") Long id) {
return adminService.deleteSceneTemplate(id);
}
// ========================= 事件映射 =========================
@PostMapping("/eventTemplate/page")
public ApiResponse<PageInfo<WechatSubscribeEventTemplateEntity>> pageEventTemplate(
@RequestBody WechatSubscribeEventTemplatePageReq req) {
return adminService.pageEventTemplate(req);
}
@GetMapping("/eventTemplate/detail/{id}")
public ApiResponse<WechatSubscribeEventTemplateEntity> getEventTemplate(@PathVariable("id") Long id) {
return adminService.getEventTemplate(id);
}
@PostMapping("/eventTemplate/save")
public ApiResponse<Boolean> saveEventTemplate(@RequestBody WechatSubscribeEventTemplateSaveReq req) {
return adminService.saveEventTemplate(req);
}
@DeleteMapping("/eventTemplate/delete/{id}")
public ApiResponse<Boolean> deleteEventTemplate(@PathVariable("id") Long id) {
return adminService.deleteEventTemplate(id);
}
// ========================= 发送日志 =========================
@PostMapping("/sendLog/page")
public ApiResponse<PageInfo<WechatSubscribeSendLogEntity>> pageSendLog(@RequestBody WechatSubscribeSendLogPageReq req) {
return adminService.pageSendLog(req);
}
@GetMapping("/sendLog/detail/{id}")
public ApiResponse<WechatSubscribeSendLogEntity> getSendLog(@PathVariable("id") Long id) {
return adminService.getSendLog(id);
}
}

View File

@@ -7,13 +7,14 @@ import com.ycwl.basic.model.printer.resp.PrintTaskResp;
import com.ycwl.basic.model.printer.resp.TaskSyncResp;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiResponse;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@IgnoreToken
// 打印机对接接口
@Api(tags = "打印机对接接口")
@RestController
@RequestMapping("/printer/v1")
public class PrinterTaskController {

View File

@@ -1,194 +0,0 @@
package com.ycwl.basic.controller.printer;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.WxMpUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.List;
@IgnoreToken
// 打印机大屏对接接口
@RestController
@RequestMapping("/printer/v1/tv")
@RequiredArgsConstructor
public class PrinterTvController {
private final DeviceRepository deviceRepository;
private final ScenicRepository scenicRepository;
private final FaceRepository faceRepository;
private final FaceService pcFaceService;
private final SourceMapper sourceMapper;
/**
* 获取景区列表
*
* @return 景区列表
*/
@GetMapping("/scenic/list")
public ApiResponse<List<ScenicV2DTO>> getScenicList() {
ScenicReqQuery query = new ScenicReqQuery();
query.setStatus("1"); // 只查询启用状态的景区
query.setPageNum(1);
query.setPageSize(1000);
return ApiResponse.success(scenicRepository.list(query));
}
/**
* 根据景区ID查询设备列表
*
* @param scenicId 景区ID
* @return 设备列表
*/
@GetMapping("/device/list")
public ApiResponse<List<DeviceV2DTO>> getDeviceListByScenicId(@RequestParam Long scenicId) {
List<DeviceV2DTO> result = deviceRepository.getAllDeviceByScenicId(scenicId);
return ApiResponse.success(result);
}
@GetMapping("/{sampleId}/qrcode")
public void getQrcode(@PathVariable("sampleId") Long sampleId, HttpServletResponse response) throws Exception {
File qrcode = new File("qrcode_"+sampleId+".jpg");
FaceSampleEntity faceSample = faceRepository.getFaceSample(sampleId);
if (faceSample == null) {
response.setStatus(404);
return;
}
String targetPath = "pages/printer/from_sample";
DeviceV2DTO device = deviceRepository.getDeviceBasic(faceSample.getDeviceId());
if (device.getType().equals("AI_CAM")) {
// AI_CAM,需要修改path
targetPath = "pages/ai-cam/from_sample";
}
try {
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(faceSample.getScenicId());
WxMpUtil.generateUnlimitedWXAQRCode(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), targetPath, sampleId.toString(), qrcode);
// 设置响应头
response.setContentType("image/jpeg");
response.setHeader("Content-Disposition", "inline; filename=\"" + qrcode.getName() + "\"");
// 将二维码文件写入响应输出流
try (FileInputStream fis = new FileInputStream(qrcode);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
} finally {
// 删除临时文件
if (qrcode.exists()) {
qrcode.delete();
}
}
}
/**
* 获取人脸绑定二维码
* 生成小程序二维码,用于绑定人脸到用户账号
*
* @param faceId 人脸ID
* @param response HTTP响应
*/
@GetMapping("/face/{faceId}/qrcode")
public void getFaceQrcode(@PathVariable("faceId") Long faceId, HttpServletResponse response) throws Exception {
String url = pcFaceService.bindWxaCode(faceId);
response.sendRedirect(url);
}
/**
* 根据人脸ID查询图像素材
*
* @param faceId 人脸ID
* @param type 素材类型(默认为2-图片)
* @return 匹配的source记录
*/
@GetMapping("/{faceId}/source")
public ApiResponse<List<SourceEntity>> getSourceByFaceId(@PathVariable Long faceId, @RequestParam(name = "type", required = false, defaultValue = "2") Integer type) {
List<SourceEntity> sources = sourceMapper.listSourceByFaceRelation(faceId, type);
return ApiResponse.success(sources);
}
/**
* 打印机大屏人脸识别
* 上传照片,在景区人脸库中搜索匹配的人脸样本,返回识别结果
*
* 使用 USER_FACE_DB_NAME+scenicId 对人脸进行去重检测:
* - 如果已存在相同人脸(打印机大屏用户,memberId=0),则返回已存在的 faceId
* - 否则创建新的人脸记录并添加到人脸库
*
* @param file 人脸照片文件
* @param scenicId 景区ID
* @return 人脸识别结果
*/
@PostMapping("/{scenicId}/faceRecognize")
public ApiResponse<FaceRecognizeResp> faceRecognize(
@RequestParam("file") MultipartFile file,
@PathVariable Long scenicId) {
// 复用 faceUpload 方法的去重逻辑
// memberId=0L 表示打印机大屏用户,scene="tv" 为试点场景:仅执行识别/补救/落库/建关系
FaceRecognizeResp resp = pcFaceService.faceUpload(file, scenicId, 0L, "tv");
return ApiResponse.success(resp);
}
/**
* 通过人脸样本ID重定向到人脸图片URL
*
* @param faceSampleId 人脸样本ID
* @param response HTTP响应
*/
@GetMapping("/faceSample/{faceSampleId}/url")
public void redirectToFaceSampleUrl(@PathVariable Long faceSampleId, HttpServletResponse response) throws Exception {
FaceSampleEntity faceSample = faceRepository.getFaceSample(faceSampleId);
if (faceSample == null || faceSample.getFaceUrl() == null) {
response.setStatus(404);
return;
}
response.sendRedirect(faceSample.getFaceUrl());
}
/**
* 通过人脸ID重定向到人脸图片URL
*
* @param faceId 人脸ID
* @param response HTTP响应
*/
@GetMapping("/face/{faceId}/url")
public void redirectToFaceUrl(@PathVariable Long faceId, HttpServletResponse response) throws Exception {
FaceEntity face = faceRepository.getFace(faceId);
if (face == null || face.getFaceUrl() == null) {
response.setStatus(404);
return;
}
response.sendRedirect(face.getFaceUrl());
}
}

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