Browse Source

增加小程序订阅消息接口

glx
glx 2 weeks ago
parent
commit
3b73dc2135
  1. 1
      src/main/java/com/youlai/boot/codegen/freemarker/MyBatisPlusGenerator.java
  2. 3
      src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java
  3. 63
      src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java
  4. 17
      src/main/java/com/youlai/boot/mini/mapper/MiniUserSubscribeMapper.java
  5. 74
      src/main/java/com/youlai/boot/mini/model/entity/MiniUserSubscribe.java
  6. 46
      src/main/java/com/youlai/boot/mini/model/enums/SubscribeStatusEnum.java
  7. 26
      src/main/java/com/youlai/boot/mini/model/form/WxSubscribeAuthForm.java
  8. 33
      src/main/java/com/youlai/boot/mini/model/form/WxSubscribeSendForm.java
  9. 43
      src/main/java/com/youlai/boot/mini/model/vo/UserUploadMediaVO.java
  10. 16
      src/main/java/com/youlai/boot/mini/model/vo/WxApiResponse.java
  11. 6
      src/main/java/com/youlai/boot/mini/service/AiGenerationService.java
  12. 14
      src/main/java/com/youlai/boot/mini/service/WxSubscribeService.java
  13. 125
      src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java
  14. 147
      src/main/java/com/youlai/boot/mini/service/impl/WxSubscribeServiceImpl.java
  15. 9
      src/main/resources/application-dev.yml
  16. 16
      src/main/resources/mapper/mini/MiniUserSubscribeMapper.xml

1
src/main/java/com/youlai/boot/codegen/freemarker/MyBatisPlusGenerator.java

@ -101,6 +101,7 @@ public class MyBatisPlusGenerator {
,new TableConfig("mini_sign_record", IdType.AUTO, "mini") ,new TableConfig("mini_sign_record", IdType.AUTO, "mini")
,new TableConfig("mini_ai_generation_task", IdType.AUTO, "mini") ,new TableConfig("mini_ai_generation_task", IdType.AUTO, "mini")
,new TableConfig("mini_ai_task_media", IdType.AUTO, "mini") ,new TableConfig("mini_ai_task_media", IdType.AUTO, "mini")
,new TableConfig("mini_user_subscribe", IdType.AUTO, "mini")
// ,new TableConfig("mini_stray_animal", IdType.AUTO, "mini") // ,new TableConfig("mini_stray_animal", IdType.AUTO, "mini")
// ,new TableConfig("mini_stray_animal", IdType.INPUT, "minitest") // ,new TableConfig("mini_stray_animal", IdType.INPUT, "minitest")

3
src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java

@ -34,7 +34,8 @@ public enum LogModuleEnum implements IBaseEnum<Integer> {
POINT_RULE(102, "积分规则"), POINT_RULE(102, "积分规则"),
SIGN_RECORD(103, "签到记录"), SIGN_RECORD(103, "签到记录"),
AI_TASK_MEDIA(104, "AI任务媒体"), AI_TASK_MEDIA(104, "AI任务媒体"),
AI_GENERATION_TASK(105, "AI生成任务"); AI_GENERATION_TASK(105, "AI生成任务"),
USER_SUBSCRIBE(106, "用户订阅模板消息");
@EnumValue @EnumValue
private final Integer value; private final Integer value;

63
src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java

@ -6,14 +6,15 @@ import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result; import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils; import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm; import com.youlai.boot.mini.model.form.*;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.form.AiVideoGenerateForm;
import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.youlai.boot.mini.service.AiGenerationService; import com.youlai.boot.mini.service.AiGenerationService;
import com.youlai.boot.mini.service.WxSubscribeService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -22,8 +23,9 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
@Tag(name = "AI生成图片视频相关接口") @Tag(name = "AI生成相关接口")
@RestController @RestController
@RequestMapping("/api/v1/mini/ai/generation") @RequestMapping("/api/v1/mini/ai/generation")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -31,24 +33,21 @@ import java.util.List;
public class AiGenerationController { public class AiGenerationController {
private final AiGenerationService aiGenerationService; private final AiGenerationService aiGenerationService;
private final WxSubscribeService wxSubscribeService;
@Operation(summary = "上传AI生成参考文件", operationId = "AiReferenceSaveFile") @Operation(summary = "上传AI生成参考文件")
@PostMapping(value = "/upload-reference", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/upload/reference", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit @RepeatSubmit
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.INSERT) @Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.INSERT)
public Result<List<String>> uploadReferenceFile( public Result<List<String>> uploadReferenceFile(
@RequestPart(name = "images", required = false) List<MultipartFile> images, @RequestParam(name = "images", required = false) List<MultipartFile> images,
@RequestPart(name = "videos", required = false) List<MultipartFile> videos @RequestParam(name = "videos", required = false) List<MultipartFile> videos
) { ) {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
List<String> urlList = aiGenerationService.uploadReferenceFile(images, videos, userId); List<String> urlList = aiGenerationService.uploadReferenceFile(images, videos, userId);
return Result.success(urlList); return Result.success(urlList);
} }
//TODO: 查询用户最近上传的图片视频接口
//TODO:用户删除上次的图片视频接口
@Operation(summary = "提交单图生成任务") @Operation(summary = "提交单图生成任务")
@PostMapping("/generate-single-image") @PostMapping("/generate-single-image")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT) @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
@ -100,5 +99,39 @@ public class AiGenerationController {
return Result.success(success); return Result.success(success);
} }
//TODO: 后续在回调中 增加用户小程序订阅通知 @Operation(summary = "上报微信订阅消息授权状态")
@PostMapping("/subscribe/auth/report")
@Log(module = LogModuleEnum.USER_SUBSCRIBE, value = ActionTypeEnum.UPDATE)
public Result<Void> reportSubscribeAuth(@Valid @RequestBody WxSubscribeAuthForm form) {
form.setUserId(SecurityUtils.getUserId());
wxSubscribeService.reportAuth(form);
return Result.success();
}
@Operation(summary = "发送微信订阅消息")
@PostMapping("/subscribe/send")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<Boolean> sendSubscribeMessage(@Valid @RequestBody WxSubscribeSendForm form) {
form.setUserId(SecurityUtils.getUserId());
Boolean result = wxSubscribeService.sendMessage(form);
return Result.success(result);
}
@Operation(summary = "查询当前用户最近上传的图片视频")
@GetMapping("/my/recent-upload")
public Result<List<UserUploadMediaVO>> getMyRecentUpload() {
Long userId = SecurityUtils.getUserId();
List<UserUploadMediaVO> voList = aiGenerationService.getRecentUploadVO(userId);
return Result.success(voList);
}
@Operation(summary = "删除用户上传的图片/视频")
@DeleteMapping("/my/upload/delete")
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.DELETE)
public Result<Boolean> deleteMyUpload(@RequestParam String uuid) {
Long userId = SecurityUtils.getUserId();
boolean success = aiGenerationService.deleteUploadMedia(userId, uuid);
return Result.success(success);
}
} }

17
src/main/java/com/youlai/boot/mini/mapper/MiniUserSubscribeMapper.java

@ -0,0 +1,17 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserSubscribe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* 用户订阅状态表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserSubscribeMapper extends BaseMapper<MiniUserSubscribe> {
String getOpenidByUserId(Long aLong);
}

74
src/main/java/com/youlai/boot/mini/model/entity/MiniUserSubscribe.java

@ -0,0 +1,74 @@
package com.youlai.boot.mini.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("mini_user_subscribe")
@Schema(description = "用户订阅状态表")
public class MiniUserSubscribe implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "")
private Long id;
@TableField("uuid")
@Schema(description = "uuid唯一标识,前后端用这个进行数据交互")
private String uuid;
@TableField("user_id")
@Schema(description = "用户id")
private Long userId;
@TableField("template_id")
@Schema(description = "订阅消息模板ID")
private String templateId;
@TableField("status")
@Schema(description = "授权状态:0=拒绝 1=同意一次 2=总是同意")
private Integer status;
@TableField("create_time")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField("create_timestamp")
@Schema(description = "创建时间毫秒级时间戳")
private Long createTimestamp;
@TableField("create_by")
@Schema(description = "创建人ID")
private Long createBy;
@TableField("update_time")
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@TableField("update_timestamp")
@Schema(description = "更新时间毫秒级时间戳")
private Long updateTimestamp;
@TableField("update_by")
@Schema(description = "修改人ID")
private Long updateBy;
@TableField("is_deleted")
@Schema(description = "逻辑删除标识(0-未删除 1-已删除)")
private Boolean deleted;
}

46
src/main/java/com/youlai/boot/mini/model/enums/SubscribeStatusEnum.java

@ -0,0 +1,46 @@
package com.youlai.boot.mini.model.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "微信订阅消息授权状态")
public enum SubscribeStatusEnum {
REJECT(0, "拒绝授权"),
ONCE(1, "同意一次"),
ALWAYS(2, "总是同意");
private final Integer value;
private final String desc;
SubscribeStatusEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
@JsonValue
public Integer getValue() {
return value;
}
public String getDesc() {
return desc;
}
@JsonCreator
public static SubscribeStatusEnum from(Integer value) {
if (value == null) return null;
for (SubscribeStatusEnum e : values()) {
if (e.value.equals(value)) {
return e;
}
}
return null;
}
public static boolean contains(Integer value) {
return from(value) != null;
}
}

26
src/main/java/com/youlai/boot/mini/model/form/WxSubscribeAuthForm.java

@ -0,0 +1,26 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 上报用户订阅授权请求
*/
@Data
@Schema(description = "上报用户订阅授权请求")
public class WxSubscribeAuthForm {
@NotBlank(message = "用户ID不能为空")
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long userId;
@NotBlank(message = "模板ID不能为空")
@Schema(description = "订阅消息模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String templateId;
@NotNull(message = "授权状态不能为空")
@Schema(description = "授权状态:0=拒绝 1=同意一次 2=总是同意", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer status;
}

33
src/main/java/com/youlai/boot/mini/model/form/WxSubscribeSendForm.java

@ -0,0 +1,33 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
/**
* 发送订阅消息请求
*/
@Data
@Schema(description = "发送订阅消息请求")
public class WxSubscribeSendForm {
@NotBlank(message = "用户ID不能为空")
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long userId;
@NotBlank(message = "模板ID不能为空")
@Schema(description = "订阅消息模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String templateId;
@Schema(description = "跳转页面,例:/pages/result/index?id=123")
private String page;
@Schema(description = "跳转版本:developer=开发版 trial=体验版 formal=正式版,默认formal")
private String miniProgramState = "developer";
@NotNull(message = "模板参数不能为空")
@Schema(description = "模板参数,key是模板字段名,value是对应的值", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, String> templateParams;
}

43
src/main/java/com/youlai/boot/mini/model/vo/UserUploadMediaVO.java

@ -0,0 +1,43 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 用户上传媒体返回VO
*
* @author youlai
*/
@Data
@Schema(description = "用户上传媒体返回VO")
public class UserUploadMediaVO {
@Schema(description = "媒体ID")
private Long id;
@Schema(description = "媒体类型:image-图片,video-视频")
private String mediaType;
@Schema(description = "媒体访问地址")
private String sourceUrl;
@Schema(description = "缩略图地址(视频才有)")
private String thumbnailUrl;
@Schema(description = "视频时长(秒,视频才有)")
private Integer duration;
@Schema(description = "图片宽度")
private Integer width;
@Schema(description = "图片高度")
private Integer height;
@Schema(description = "上传时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

16
src/main/java/com/youlai/boot/mini/model/vo/WxApiResponse.java

@ -0,0 +1,16 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 微信接口通用返回
*/
@Data
public class WxApiResponse {
@JsonProperty("errcode")
private Integer errcode;
@JsonProperty("errmsg")
private String errmsg;
}

6
src/main/java/com/youlai/boot/mini/service/AiGenerationService.java

@ -1,5 +1,6 @@
package com.youlai.boot.mini.service; package com.youlai.boot.mini.service;
import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm;
import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm; import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm; import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
@ -7,6 +8,7 @@ import com.youlai.boot.mini.model.form.AiVideoGenerateForm;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask; import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm; import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@ -29,4 +31,8 @@ public interface AiGenerationService {
MiniAiGenerationTask getTaskByUuid(String uuid); MiniAiGenerationTask getTaskByUuid(String uuid);
List<UserUploadMediaVO> getRecentUploadVO(Long userId);
boolean deleteUploadMedia(Long userId, String uuid);
} }

14
src/main/java/com/youlai/boot/mini/service/WxSubscribeService.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.service;
import com.youlai.boot.mini.model.form.WxSubscribeAuthForm;
import com.youlai.boot.mini.model.form.WxSubscribeSendForm;
/**
* 微信订阅消息服务
*/
public interface WxSubscribeService {
void reportAuth(WxSubscribeAuthForm form);
Boolean sendMessage(WxSubscribeSendForm form);
}

125
src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java

@ -1,11 +1,10 @@
package com.youlai.boot.mini.service.impl; package com.youlai.boot.mini.service.impl;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONException; import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
@ -16,7 +15,6 @@ import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.util.FileUtils; import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils; import com.youlai.boot.common.util.JavaVCUtils;
import com.youlai.boot.common.util.RandomNumberUtils; import com.youlai.boot.common.util.RandomNumberUtils;
import com.youlai.boot.file.model.FileInfo;
import com.youlai.boot.file.service.FileService; import com.youlai.boot.file.service.FileService;
import com.youlai.boot.file.service.impl.AliyunFileService; import com.youlai.boot.file.service.impl.AliyunFileService;
import com.youlai.boot.mini.mapper.MiniAiGenerationTaskMapper; import com.youlai.boot.mini.mapper.MiniAiGenerationTaskMapper;
@ -24,18 +22,15 @@ import com.youlai.boot.mini.mapper.MiniAiTaskMediaMapper;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask; import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.entity.MiniAiTaskMedia; import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; import com.youlai.boot.mini.model.form.*;
import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.form.AiVideoGenerateForm;
import com.youlai.boot.mini.model.form.MiniDeductPointForm;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.youlai.boot.mini.model.form.AiCallbackImage; import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm;
import com.youlai.boot.mini.service.AiGenerationService; import com.youlai.boot.mini.service.AiGenerationService;
import com.youlai.boot.mini.service.MiniPointRecordService; import com.youlai.boot.mini.service.MiniPointRecordService;
import com.youlai.boot.mini.service.WxSubscribeService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -46,7 +41,11 @@ import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.concurrent.CompletableFuture;
@Slf4j @Slf4j
@Service @Service
@ -58,6 +57,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
private final FileService fileService; private final FileService fileService;
private final AliyunFileService aliyunFileService; private final AliyunFileService aliyunFileService;
private final MiniPointRecordService pointRecordService; private final MiniPointRecordService pointRecordService;
private final WxSubscribeService wxSubscribeService;
//OSS存储目录配置 //OSS存储目录配置
private static final String OSS_IMAGE_DIR = "ai/image/"; private static final String OSS_IMAGE_DIR = "ai/image/";
@ -91,6 +91,9 @@ public class AiGenerationServiceImpl implements AiGenerationService {
@Value("${ai.default.video-model:doubao-seedance-2-0-260128}") @Value("${ai.default.video-model:doubao-seedance-2-0-260128}")
private String aiDefaultVideoModel; private String aiDefaultVideoModel;
@Value("${subscribe.template}")
private String subscribeTemplate;
//AI单图生成积分规则编码 //AI单图生成积分规则编码
private static final String AI_GENERATE_SINGLE_IMAGE_RULE = "AI_GENERATE_SINGLE_IMAGE"; private static final String AI_GENERATE_SINGLE_IMAGE_RULE = "AI_GENERATE_SINGLE_IMAGE";
//AI四宫格生成积分规则编码 //AI四宫格生成积分规则编码
@ -537,6 +540,24 @@ public class AiGenerationServiceImpl implements AiGenerationService {
.set(MiniAiTaskMedia::getUpdateTime, new Date()) .set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis()); .set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper); aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
if (StrUtil.isNotBlank(subscribeTemplate)) {
try {
WxSubscribeSendForm sendForm = new WxSubscribeSendForm();
sendForm.setUserId(task.getMiniUserId());
sendForm.setTemplateId(subscribeTemplate);
sendForm.setPage("pages/index/index");
sendForm.setTemplateParams(Map.of(
"thing1", "您的AI视频作品已完成",
"time2", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
"phrase3", "点击查看状态"
));
wxSubscribeService.sendMessage(sendForm);
} catch (Exception e) {
log.error("视频任务{}发送订阅消息失败", task.getId(), e);
}
}
} }
success = true; success = true;
@ -627,6 +648,25 @@ public class AiGenerationServiceImpl implements AiGenerationService {
.set(MiniAiTaskMedia::getUpdateTime, new Date()) .set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis()); .set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper); aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
if (StrUtil.isNotBlank(subscribeTemplate)) {
try {
WxSubscribeSendForm sendForm = new WxSubscribeSendForm();
sendForm.setUserId(task.getMiniUserId());
sendForm.setTemplateId(subscribeTemplate);
sendForm.setPage("pages/index/index");
// 根据实际模板字段调整参数
sendForm.setTemplateParams(Map.of(
"thing1", "您的AI绘画作品已完成",
"time2", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
"phrase3", "点击查看状态"
));
wxSubscribeService.sendMessage(sendForm);
} catch (Exception e) {
log.error("单图任务{}发送订阅消息失败", task.getId(), e);
}
}
} }
log.info("四宫格任务{}回调处理完成,状态:{}", taskUuid, status); log.info("四宫格任务{}回调处理完成,状态:{}", taskUuid, status);
@ -711,6 +751,25 @@ public class AiGenerationServiceImpl implements AiGenerationService {
.set(MiniAiTaskMedia::getUpdateTime, new Date()) .set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis()); .set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper); aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
if (StrUtil.isNotBlank(subscribeTemplate)) {
try {
WxSubscribeSendForm sendForm = new WxSubscribeSendForm();
sendForm.setUserId(task.getMiniUserId());
sendForm.setTemplateId(subscribeTemplate);
sendForm.setPage("pages/index/index");
// 根据实际模板字段调整参数
sendForm.setTemplateParams(Map.of(
"thing1", "您的AI绘画作品已完成",
"time2", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
"phrase3", "点击查看状态"
));
wxSubscribeService.sendMessage(sendForm);
} catch (Exception e) {
log.error("单图任务{}发送订阅消息失败", task.getId(), e);
}
}
} }
// 更新任务状态 // 更新任务状态
@ -793,4 +852,48 @@ public class AiGenerationServiceImpl implements AiGenerationService {
} }
@Override
public List<UserUploadMediaVO> getRecentUploadVO(Long userId) {
List<MiniAiTaskMedia> mediaList = aiTaskMediaMapper.selectList(new LambdaQueryWrapper<MiniAiTaskMedia>()
.eq(MiniAiTaskMedia::getMiniUserId, userId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.orderByDesc(MiniAiTaskMedia::getCreateTime)
.last("limit 20"));
if (mediaList == null || mediaList.isEmpty()) {
return Collections.emptyList();
}
return mediaList.stream()
.map(this::convertToVO)
.toList();
}
private UserUploadMediaVO convertToVO(MiniAiTaskMedia media) {
UserUploadMediaVO vo = new UserUploadMediaVO();
BeanUtil.copyProperties(media, vo);
return vo;
}
@Override
public boolean deleteUploadMedia(Long userId, String uuid) {
// 校验是否是当前用户的上传媒体
MiniAiTaskMedia media = aiTaskMediaMapper.selectOne(new LambdaQueryWrapper<MiniAiTaskMedia>()
.eq(MiniAiTaskMedia::getUuid, uuid)
.eq(MiniAiTaskMedia::getMiniUserId, userId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.last("limit 1"));
if (media == null) {
log.warn("用户{}删除媒体失败,媒体{}不存在或无权限", userId, uuid);
return false;
}
// 软删除
media.setDeleted(true);
media.setUpdateTime(new Date());
media.setUpdateTimestamp(System.currentTimeMillis());
return aiTaskMediaMapper.updateById(media) > 0;
}
} }

147
src/main/java/com/youlai/boot/mini/service/impl/WxSubscribeServiceImpl.java

@ -0,0 +1,147 @@
package com.youlai.boot.mini.service.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.mini.model.enums.SubscribeStatusEnum;
import com.youlai.boot.mini.mapper.MiniUserSubscribeMapper;
import com.youlai.boot.mini.model.entity.MiniUserSubscribe;
import com.youlai.boot.mini.model.form.WxSubscribeAuthForm;
import com.youlai.boot.mini.model.form.WxSubscribeSendForm;
import com.youlai.boot.mini.model.vo.WxApiResponse;
import com.youlai.boot.mini.service.WxSubscribeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
/**
* 微信订阅消息服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WxSubscribeServiceImpl implements WxSubscribeService {
private final WxMaService wxMaService;
private final MiniUserSubscribeMapper userSubscribeMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void reportAuth(WxSubscribeAuthForm form) {
try {
// 查询用户openid
String openid = userSubscribeMapper.getOpenidByUserId(form.getUserId());
if (StrUtil.isBlank(openid)) {
throw new MsgException("用户未绑定微信");
}
long currentTime = System.currentTimeMillis();
// 查询是否已有授权记录
MiniUserSubscribe exist = userSubscribeMapper.selectOne(new LambdaQueryWrapper<MiniUserSubscribe>()
.eq(MiniUserSubscribe::getUserId, form.getUserId())
.eq(MiniUserSubscribe::getTemplateId, form.getTemplateId())
.eq(MiniUserSubscribe::getDeleted, 0)
.last("limit 1"));
if (exist != null) {
exist.setStatus(form.getStatus());
exist.setUpdateTime(new Date());
exist.setUpdateTimestamp(currentTime);
userSubscribeMapper.updateById(exist);
} else {
MiniUserSubscribe subscribe = new MiniUserSubscribe();
subscribe.setUserId(form.getUserId());
subscribe.setTemplateId(form.getTemplateId());
subscribe.setStatus(form.getStatus());
subscribe.setCreateTime(new Date());
subscribe.setCreateTimestamp(currentTime);
subscribe.setUpdateTime(new Date());
subscribe.setUpdateTimestamp(currentTime);
subscribe.setDeleted(false);
userSubscribeMapper.insert(subscribe);
}
log.info("用户{}订阅授权上报成功,模板ID:{},状态:{}", form.getUserId(), form.getTemplateId(), form.getStatus());
} catch (Exception e) {
log.error("上报用户订阅授权失败,请求参数:{}", JSONUtil.toJsonStr(form), e);
throw new MsgException("上报授权状态失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean sendMessage(WxSubscribeSendForm form) {
log.info("开始发送订阅消息,请求参数:{}", JSONUtil.toJsonStr(form));
try {
// 1. 查询用户openid
String openid = userSubscribeMapper.getOpenidByUserId(form.getUserId());
if (StrUtil.isBlank(openid)) {
log.error("用户{}未绑定微信openid,无法发送订阅消息", form.getUserId());
return false;
}
// 2. 构建微信订阅消息参数
List<Map<String, String>> dataList = new ArrayList<>();
for (Map.Entry<String, String> entry : form.getTemplateParams().entrySet()) {
Map<String, String> dataMap = new HashMap<>();
dataMap.put("name", entry.getKey());
dataMap.put("value", entry.getValue());
dataList.add(dataMap);
}
Map<String, Object> messageMap = new HashMap<>();
messageMap.put("touser", openid);
messageMap.put("template_id", form.getTemplateId());
messageMap.put("page", form.getPage());
messageMap.put("miniprogram_state", form.getMiniProgramState());
messageMap.put("lang", "zh_CN");
messageMap.put("data", dataList);
// 3. 调用微信接口发送
String responseStr = wxMaService.post("https://api.weixin.qq.com/cgi-bin/message/subscribe/send", JSONUtil.toJsonStr(messageMap));
WxApiResponse response = JSONUtil.toBean(responseStr, WxApiResponse.class);
log.info("微信订阅消息发送结果,用户:{},返回:{}", form.getUserId(), JSONUtil.toJsonStr(response));
// 4. 根据返回结果更新用户订阅状态
MiniUserSubscribe subscribe = userSubscribeMapper.selectOne(new LambdaQueryWrapper<MiniUserSubscribe>()
.eq(MiniUserSubscribe::getUserId, form.getUserId())
.eq(MiniUserSubscribe::getTemplateId, form.getTemplateId())
.eq(MiniUserSubscribe::getDeleted, 0)
.last("limit 1")
);
if (response.getErrcode() == 0) {
log.info("订阅消息发送成功,用户:{},模板ID:{}", form.getUserId(), form.getTemplateId());
// 如果是同意一次的状态,发送后更新为未授权
if (subscribe != null && SubscribeStatusEnum.ONCE.getValue().equals(subscribe.getStatus())) {
subscribe.setStatus(SubscribeStatusEnum.REJECT.getValue());
subscribe.setUpdateTime(new Date());
userSubscribeMapper.updateById(subscribe);
}
return true;
} else if (response.getErrcode() == 43101) {
// 用户未订阅,更新本地状态为拒绝
log.warn("用户{}未订阅该模板消息,更新本地状态为拒绝", form.getUserId());
if (subscribe != null) {
subscribe.setStatus(SubscribeStatusEnum.REJECT.getValue());
subscribe.setUpdateTime(new Date());
userSubscribeMapper.updateById(subscribe);
}
return false;
} else {
log.error("订阅消息发送失败,错误码:{},错误信息:{}", response.getErrcode(), response.getErrmsg());
return false;
}
} catch (Exception e) {
log.error("发送订阅消息异常,请求参数:{}", JSONUtil.toJsonStr(form), e);
return false;
}
}
}

9
src/main/resources/application-dev.yml

@ -226,8 +226,8 @@ captcha:
# 微信小程序配置 # 微信小程序配置
wx: wx:
miniapp: miniapp:
appid: Your_AppId appid: wx56425c3301f5c6df
secret: Your_AppSecret secret: 7c4060199f49b0ab872b06b97cef2ac4
# AIGeneration 配置 # AIGeneration 配置
ai: ai:
@ -244,3 +244,8 @@ ai:
image-model: doubao-seedream-5-0-260128 image-model: doubao-seedream-5-0-260128
video-model: doubao-seedance-2-0-260128 video-model: doubao-seedance-2-0-260128
# 订阅模板配置
subscribe:
template: "7m5Vu4gaCo2zY6hzID4yqEv94y1guuOGxdPOJYV_xHE"

16
src/main/resources/mapper/mini/MiniUserSubscribeMapper.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.youlai.boot.mini.mapper.MiniUserSubscribeMapper">
<select id="getOpenidByUserId" resultType="java.lang.String">
SELECT openid
FROM sys_user_social
WHERE user_id = #{userId}
AND is_deleted = 0
LIMIT 1
</select>
</mapper>
Loading…
Cancel
Save