|
|
@@ -1,6 +1,5 @@
|
|
|
package shop.alien.storeplatform.service.impl;
|
|
|
|
|
|
-import com.alibaba.excel.util.StringUtils;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
@@ -10,15 +9,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RBucket;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
import org.springframework.util.MultiValueMap;
|
|
|
import shop.alien.entity.store.LifeDiscountCoupon;
|
|
|
-import shop.alien.entity.store.LifeUser;
|
|
|
import shop.alien.entity.store.StoreImg;
|
|
|
-import shop.alien.entity.store.StoreUser;
|
|
|
import shop.alien.entity.storePlatform.StoreOperationalActivity;
|
|
|
import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
|
|
|
import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
|
|
|
@@ -33,6 +32,7 @@ import java.util.ArrayList;
|
|
|
import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
|
/**
|
|
|
@@ -60,12 +60,32 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
|
|
|
private final AlienAIFeign alienAIFeign;
|
|
|
|
|
|
+ private final RedissonClient redissonClient;
|
|
|
+
|
|
|
@Value("${ai.aiAccount}")
|
|
|
private String aiAccount;
|
|
|
|
|
|
@Value("${ai.aiPassword}")
|
|
|
private String aiPassword;
|
|
|
|
|
|
+ @Value("${ai.token-timeout:3}")
|
|
|
+ private Integer aiTokenTimeout;
|
|
|
+
|
|
|
+ // AI平台token Redis key
|
|
|
+ private static final String AI_TOKEN_KEY = "ai:platform:token";
|
|
|
+
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ public static final ThreadLocal<String> failureReasonHolder = new ThreadLocal<>();
|
|
|
+
|
|
|
+ // AI参数模板
|
|
|
+ String tpl = "活动名称:%s\n"
|
|
|
+ + "活动时间:%s - %s,格式化时间为yyyy-mm-dd类型\n"
|
|
|
+ + "用户可参与次数:%s\n"
|
|
|
+ + "活动规则:%s\n"
|
|
|
+ + "优惠券发放数量:%s\n"
|
|
|
+ + "图片描述:%s";
|
|
|
+
|
|
|
@Override
|
|
|
public int createActivity(StoreOperationalActivityDTO dto) {
|
|
|
log.info("OperationalActivityServiceImpl.createActivity: dto={}", dto);
|
|
|
@@ -80,38 +100,44 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
if (activity.getStatus() == null) {
|
|
|
activity.setStatus(1);
|
|
|
}
|
|
|
- Integer result = activityMapper.insert(activity);
|
|
|
- if (result > 0) {
|
|
|
- // 使用用户描述让AI生成图片。
|
|
|
- if (dto.getUploadImgType()==2) {
|
|
|
- try {
|
|
|
- // 先调用登录接口获取access_token
|
|
|
- MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
|
|
- formData.add("username", aiAccount);
|
|
|
- formData.add("password", aiPassword);
|
|
|
- JsonNode loginResponse = alienAIFeign.login(formData);
|
|
|
-
|
|
|
- String accessToken = null;
|
|
|
- if (loginResponse != null && loginResponse.has("data")) {
|
|
|
- JsonNode data = loginResponse.get("data");
|
|
|
- if (data.has("access_token")) {
|
|
|
- accessToken = data.get("access_token").asText();
|
|
|
+ Integer result =0;
|
|
|
+ try {
|
|
|
+ String accessToken = getToken();
|
|
|
+ if (accessToken == null || accessToken.isEmpty()) {
|
|
|
+ log.error("获取AI服务access_token失败,无法生成促销图片");
|
|
|
+ } else {
|
|
|
+ // AI登录成功
|
|
|
+ // 先调用AI进行运营名称和图片的审核,同步调用
|
|
|
+ String authorization = "Bearer " + accessToken;
|
|
|
+ JsonNode audioResponse = alienAIFeign.multiModelAudit(authorization, dto.getAuditParam());
|
|
|
+ // 如果审核失败,不进行文字生成海报图片,提前短路。
|
|
|
+ if (audioResponse.has("data")) {
|
|
|
+ String taskId = audioResponse.get("data").get("task_id").asText();
|
|
|
+ JsonNode audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
|
|
|
+ String status=audioResResponse.get("data").get("status").asText();
|
|
|
+ for (int i = 0; i < 60; i++) { // AI审核接口速度还在优化中 todo
|
|
|
+ if (status.equals("completed")||status.equals("failed")) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
|
|
|
+ status = audioResResponse.get("data").get("status").asText();
|
|
|
+ Thread.sleep(1000);
|
|
|
+ }
|
|
|
+ String auditRes = audioResResponse.get("data").get("audit_result").asText();
|
|
|
+ if (!status.equals("failed")&&auditRes != null && auditRes.equals("compliant")) {
|
|
|
+ activity.setStatus(8);
|
|
|
+ result = activityMapper.insert(activity);
|
|
|
+ } else {
|
|
|
+ String failureReason = audioResResponse.get("data").get("failure_reason").asText();
|
|
|
+ failureReasonHolder.set(failureReason);
|
|
|
+ // 审核不成功 不生成任何记录,返回原因
|
|
|
+ return 2;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (accessToken == null || accessToken.isEmpty()) {
|
|
|
- log.error("获取AI服务access_token失败,无法生成促销图片");
|
|
|
- } else {
|
|
|
- ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
|
|
|
+ if (dto.getUploadImgType() == 2) {
|
|
|
+ // 格式化输入AI参数
|
|
|
ObjectNode requestBody = objectMapper.createObjectNode();
|
|
|
-
|
|
|
- String tpl = "活动名称:%s\n"
|
|
|
- + "活动时间:%s - %s\n"
|
|
|
- + "用户可参与次数:%s\n"
|
|
|
- + "活动规则:%s\n"
|
|
|
- + "优惠券发放数量:%s\n"
|
|
|
- + "图片描述:%s";
|
|
|
-
|
|
|
String filled = String.format(
|
|
|
tpl,
|
|
|
dto.getActivityName(),
|
|
|
@@ -123,12 +149,10 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
dto.getImgDescribe()
|
|
|
);
|
|
|
requestBody.put("text", filled);
|
|
|
- // 调用接口,传递Bearer token
|
|
|
- String authorization = "Bearer " + accessToken;
|
|
|
- JsonNode response = alienAIFeign.generatePromotionImage(authorization, requestBody);
|
|
|
+ JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
|
|
|
// 解析响应
|
|
|
- if (response.has("data")) {
|
|
|
- JsonNode data = response.get("data");
|
|
|
+ if (imgResponse.has("data")) {
|
|
|
+ JsonNode data = imgResponse.get("data");
|
|
|
// 提取横向图(banner_image)的图片URL
|
|
|
if (data.has("banner_image")) {
|
|
|
JsonNode bannerImage = data.get("banner_image");
|
|
|
@@ -147,9 +171,10 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("调用AI服务生成促销图片失败", e);
|
|
|
}
|
|
|
+ } catch (Exception e) {
|
|
|
+ // AI调用失败,也可以添加数据
|
|
|
+ log.error("调用AI服务生成促销图片失败", e);
|
|
|
}
|
|
|
dto.getActivityTitleImg().setBusinessId(activity.getId());
|
|
|
dto.getActivityTitleImg().setImgType(26);
|
|
|
@@ -158,19 +183,49 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
dto.getActivityDetailImg().setBusinessId(activity.getId());
|
|
|
dto.getActivityDetailImg().setImgType(27);
|
|
|
imgMapper.insert(dto.getActivityDetailImg());
|
|
|
- }
|
|
|
-
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 登录AI平台,获取token
|
|
|
+ */
|
|
|
+ private String getToken() {
|
|
|
+ // 1. 先从缓存获取
|
|
|
+ RBucket<String> tokenBucket = redissonClient.getBucket(AI_TOKEN_KEY);
|
|
|
+ String cachedToken = tokenBucket.get();
|
|
|
+ if (cachedToken != null && !cachedToken.isEmpty()) {
|
|
|
+ log.debug("从缓存获取 AI token");
|
|
|
+ return cachedToken;
|
|
|
+ }
|
|
|
+ // 2. 缓存未命中,调用登录接口
|
|
|
+ MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
|
|
+ formData.add("username", aiAccount);
|
|
|
+ formData.add("password", aiPassword);
|
|
|
+ JsonNode loginResponse = alienAIFeign.login(formData);
|
|
|
+ String accessToken = null;
|
|
|
+ if (loginResponse != null && loginResponse.has("data")) {
|
|
|
+ JsonNode data = loginResponse.get("data");
|
|
|
+ if (data.has("access_token")) {
|
|
|
+ accessToken = data.get("access_token").asText();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 3. 如果获取成功,存入缓存
|
|
|
+ if (accessToken != null && !accessToken.isEmpty()) {
|
|
|
+ tokenBucket.set(accessToken, aiTokenTimeout, TimeUnit.SECONDS);
|
|
|
+ } else {
|
|
|
+ log.error("获取 AI token 失败");
|
|
|
+ }
|
|
|
+ return accessToken;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public int updateActivity(StoreOperationalActivityDTO dto) {
|
|
|
log.info("OperationalActivityServiceImpl.updateActivity: dto={}", dto);
|
|
|
-
|
|
|
+
|
|
|
if (dto.getId() == null) {
|
|
|
throw new IllegalArgumentException("活动ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
StoreOperationalActivity activity = new StoreOperationalActivity();
|
|
|
BeanUtils.copyProperties(dto, activity);
|
|
|
Integer result = activityMapper.updateById(activity);
|
|
|
@@ -185,7 +240,6 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
imgMapper.update(null, wrapper);
|
|
|
|
|
|
|
|
|
-
|
|
|
// 插入新图片
|
|
|
StoreImg activityTitleImg = new StoreImg();
|
|
|
activityTitleImg.setStoreId(dto.getStoreId());
|
|
|
@@ -210,11 +264,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
@Override
|
|
|
public int deleteActivity(Integer id) {
|
|
|
log.info("OperationalActivityServiceImpl.deleteActivity: id={}", id);
|
|
|
-
|
|
|
+
|
|
|
if (id == null) {
|
|
|
throw new IllegalArgumentException("活动ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 逻辑删除
|
|
|
return activityMapper.deleteById(id);
|
|
|
}
|
|
|
@@ -222,11 +276,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
@Override
|
|
|
public StoreOperationalActivityVO queryActivityById(Integer id) {
|
|
|
log.info("OperationalActivityServiceImpl.getActivityById: id={}", id);
|
|
|
-
|
|
|
+
|
|
|
if (id == null) {
|
|
|
throw new IllegalArgumentException("活动ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
StoreOperationalActivity activity = activityMapper.selectById(id);
|
|
|
|
|
|
if (activity == null) {
|
|
|
@@ -252,7 +306,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
} else if (activity.getStatus() == 7) {
|
|
|
vo.setStatusName("已结束");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置优惠券名称(判空处理)
|
|
|
if (activity.getCouponId() != null) {
|
|
|
LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
|
|
|
@@ -286,19 +340,19 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
|
|
|
@Override
|
|
|
public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
|
|
|
- log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
|
|
|
+ log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
|
|
|
storeId, status, activityName, pageNum, pageSize);
|
|
|
-
|
|
|
+
|
|
|
LambdaQueryWrapper<StoreOperationalActivity> wrapper = new LambdaQueryWrapper<>();
|
|
|
wrapper.eq(storeId != null, StoreOperationalActivity::getStoreId, storeId);
|
|
|
wrapper.like(activityName != null && activityName != "", StoreOperationalActivity::getActivityName, activityName);
|
|
|
wrapper.eq(status != null, StoreOperationalActivity::getStatus, status);
|
|
|
|
|
|
IPage<StoreOperationalActivity> list = activityMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
|
|
|
-
|
|
|
+
|
|
|
// 将list复制到vo
|
|
|
List<StoreOperationalActivityVO> voRecords = new ArrayList<>();
|
|
|
-
|
|
|
+
|
|
|
for (StoreOperationalActivity activity : list.getRecords()) {
|
|
|
// 创建实体类
|
|
|
StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
|
|
|
@@ -324,11 +378,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
|
|
|
voRecords.add(vo);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 创建分页结果对象
|
|
|
Page<StoreOperationalActivityVO> voList = new Page<>(list.getCurrent(), list.getSize(), list.getTotal());
|
|
|
voList.setRecords(voRecords);
|
|
|
-
|
|
|
+
|
|
|
return voList;
|
|
|
}
|
|
|
|
|
|
@@ -338,7 +392,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
|
|
|
Date now = new Date();
|
|
|
System.out.println("当前时间: " + sdf.format(now));
|
|
|
-
|
|
|
+
|
|
|
// 获取当天零点零分零秒时间
|
|
|
Calendar todayStart = Calendar.getInstance();
|
|
|
todayStart.setTime(now);
|
|
|
@@ -348,7 +402,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
todayStart.set(Calendar.MILLISECOND, 0);
|
|
|
Date todayZero = todayStart.getTime();
|
|
|
System.out.println("当天零点时间: " + sdf.format(todayZero));
|
|
|
-
|
|
|
+
|
|
|
// now + 1天(零点)
|
|
|
Calendar calendarPlus = Calendar.getInstance();
|
|
|
calendarPlus.setTime(now);
|
|
|
@@ -365,15 +419,15 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
|
|
|
@Override
|
|
|
public int updateActivityStatus(Integer id, Integer status) {
|
|
|
log.info("OperationalActivityServiceImpl.updateActivityStatus: id={}, status={}", id, status);
|
|
|
-
|
|
|
+
|
|
|
if (id == null) {
|
|
|
throw new IllegalArgumentException("活动ID不能为空");
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
StoreOperationalActivity activity = new StoreOperationalActivity();
|
|
|
activity.setId(id);
|
|
|
activity.setStatus(status);
|
|
|
-
|
|
|
+
|
|
|
return activityMapper.updateById(activity);
|
|
|
}
|
|
|
}
|