Compare commits

...

16 Commits
master ... glx

  1. 2
      .gitignore
  2. 2
      src/main/java/com/youlai/boot/YouLaiBootApplication.java
  3. 3
      src/main/java/com/youlai/boot/codegen/freemarker/MyBatisPlusGenerator.java
  4. 5
      src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java
  5. 200
      src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java
  6. 2
      src/main/java/com/youlai/boot/mini/controller/MiniUserController.java
  7. 28
      src/main/java/com/youlai/boot/mini/job/AiTaskTimeoutJob.java
  8. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniAiGenerationTaskMapper.java
  9. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniAiTaskMediaMapper.java
  10. 17
      src/main/java/com/youlai/boot/mini/mapper/MiniUserSubscribeMapper.java
  11. 31
      src/main/java/com/youlai/boot/mini/model/dto/GenerateResponse.java
  12. 48
      src/main/java/com/youlai/boot/mini/model/dto/GenerationData.java
  13. 26
      src/main/java/com/youlai/boot/mini/model/dto/ImageData.java
  14. 94
      src/main/java/com/youlai/boot/mini/model/entity/MiniAiGenerationTask.java
  15. 102
      src/main/java/com/youlai/boot/mini/model/entity/MiniAiTaskMedia.java
  16. 74
      src/main/java/com/youlai/boot/mini/model/entity/MiniUserSubscribe.java
  17. 46
      src/main/java/com/youlai/boot/mini/model/enums/SubscribeStatusEnum.java
  18. 25
      src/main/java/com/youlai/boot/mini/model/form/AiCallbackImage.java
  19. 35
      src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java
  20. 57
      src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java
  21. 35
      src/main/java/com/youlai/boot/mini/model/form/AiSingleImageCallbackForm.java
  22. 57
      src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java
  23. 23
      src/main/java/com/youlai/boot/mini/model/form/AiTaskVisibilityForm.java
  24. 30
      src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java
  25. 19
      src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java
  26. 34
      src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java
  27. 26
      src/main/java/com/youlai/boot/mini/model/form/WxSubscribeAuthForm.java
  28. 33
      src/main/java/com/youlai/boot/mini/model/form/WxSubscribeSendForm.java
  29. 21
      src/main/java/com/youlai/boot/mini/model/query/AiTaskMediaQuery.java
  30. 15
      src/main/java/com/youlai/boot/mini/model/query/DiscoveryPublicWorkQuery.java
  31. 42
      src/main/java/com/youlai/boot/mini/model/vo/AiGenerationTaskVO.java
  32. 70
      src/main/java/com/youlai/boot/mini/model/vo/AiVideoCallbackVO.java
  33. 38
      src/main/java/com/youlai/boot/mini/model/vo/DiscoveryPublicWorkVO.java
  34. 39
      src/main/java/com/youlai/boot/mini/model/vo/MiniAiTaskMediaVO.java
  35. 48
      src/main/java/com/youlai/boot/mini/model/vo/UserUploadMediaVO.java
  36. 21
      src/main/java/com/youlai/boot/mini/model/vo/VideoCallbackContent.java
  37. 25
      src/main/java/com/youlai/boot/mini/model/vo/VideoCallbackUsage.java
  38. 16
      src/main/java/com/youlai/boot/mini/model/vo/WxApiResponse.java
  39. 56
      src/main/java/com/youlai/boot/mini/service/AiGenerationService.java
  40. 14
      src/main/java/com/youlai/boot/mini/service/WxSubscribeService.java
  41. 1286
      src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java
  42. 1
      src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java
  43. 148
      src/main/java/com/youlai/boot/mini/service/impl/WxSubscribeServiceImpl.java
  44. 41
      src/main/resources/application-dev.yml
  45. 9
      src/main/resources/mapper/mini/MiniAiGenerationTaskMapper.xml
  46. 9
      src/main/resources/mapper/mini/MiniAiTaskMediaMapper.xml
  47. 15
      src/main/resources/mapper/mini/MiniUserSubscribeMapper.xml

2
.gitignore

@ -17,3 +17,5 @@ docker/*/data/
docker/minio/config docker/minio/config
docker/xxljob/logs docker/xxljob/logs
application-youlai.yml application-youlai.yml
.claude
CLAUDE.md

2
src/main/java/com/youlai/boot/YouLaiBootApplication.java

@ -3,6 +3,7 @@ package com.youlai.boot;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/** /**
* 应用启动类 * 应用启动类
@ -12,6 +13,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
*/ */
@SpringBootApplication @SpringBootApplication
@MapperScan("com.youlai.boot.**.mapper") @MapperScan("com.youlai.boot.**.mapper")
@EnableScheduling
public class YouLaiBootApplication { public class YouLaiBootApplication {
public static void main(String[] args) { public static void main(String[] args) {

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

@ -99,6 +99,9 @@ public class MyBatisPlusGenerator {
,new TableConfig("mini_point_record", IdType.AUTO, "mini") ,new TableConfig("mini_point_record", IdType.AUTO, "mini")
,new TableConfig("mini_point_rule", IdType.AUTO, "mini") ,new TableConfig("mini_point_rule", IdType.AUTO, "mini")
,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_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")

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

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

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

@ -0,0 +1,200 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.*;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.youlai.boot.mini.service.AiGenerationService;
import com.youlai.boot.mini.service.WxSubscribeService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import org.springframework.security.access.prepost.PreAuthorize;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.mini.model.query.AiTaskMediaQuery;
import com.youlai.boot.mini.model.query.DiscoveryPublicWorkQuery;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.vo.AiGenerationTaskVO;
import com.youlai.boot.mini.model.vo.DiscoveryPublicWorkVO;
import cn.hutool.core.util.StrUtil;
import java.util.List;
import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
@Tag(name = "AI生成相关接口")
@RestController
@RequestMapping("/api/v1/mini/ai/generation")
@RequiredArgsConstructor
@Valid
public class AiGenerationController {
private final AiGenerationService aiGenerationService;
private final WxSubscribeService wxSubscribeService;
@Operation(summary = "上传AI生成参考文件")
@PostMapping(value = "/upload/reference", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.INSERT)
public Result<List<String>> uploadReferenceFile(
@RequestParam(name = "images", required = false) List<MultipartFile> images,
@RequestParam(name = "videos", required = false) List<MultipartFile> videos
) {
Long userId = SecurityUtils.getUserId();
List<String> urlList = aiGenerationService.uploadReferenceFile(images, videos, userId);
return Result.success(urlList);
}
@Operation(summary = "提交单图生成任务")
@PostMapping("/generate-single-image")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateSingleImage(@Valid @RequestBody AiSingleImageGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateImage(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "单图任务回调接口")
@PostMapping("/single-image/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> singleImageTaskCallback(@Valid @RequestBody AiSingleImageCallbackForm form) {
boolean success = aiGenerationService.handleTaskCallback(form);
return Result.success(success);
}
@Operation(summary = "提交四宫格漫画生成任务")
@PostMapping("/generate-four-panel")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateFourPanelImage(@Valid @RequestBody AiFourPanelGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateFourPanel(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "四宫格漫画任务回调接口")
@PostMapping("/four-panel/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> fourPanelTaskCallback(@Valid @RequestBody AiFourPanelCallbackForm form) {
boolean success = aiGenerationService.handleFourPanelCallback(form);
return Result.success(success);
}
@Operation(summary = "提交视频生成任务")
@PostMapping("/generate-video")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateVideo(@Valid @RequestBody AiVideoGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateVideo(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "视频任务回调接口")
@PostMapping("/video/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> videoTaskCallback(@Valid @RequestBody AiVideoCallbackVO vo) {
boolean success = aiGenerationService.handleVideoTaskCallback(vo);
return Result.success(success);
}
@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();
}
@Hidden
@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 = "删除用户上传的图片/视频")
@PostMapping("/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);
}
@Operation(summary = "查询我的AI生成任务历史")
@GetMapping("/my/history")
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.LIST)
public Result<IPage<AiGenerationTaskVO>> getMyAiGenerateHistory(@Valid AiTaskMediaQuery query) {
Long userId = SecurityUtils.getUserId();
IPage<AiGenerationTaskVO> result = aiGenerationService.getMyAiGenerateHistory(query, userId);
return Result.success(result);
}
@Operation(summary = "设置微信订阅消息跳转版本")
@PostMapping("/subscribe/mini-program-state/set")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> setMiniProgramState(
@Schema(description = "跳转版本:developer=开发版 trial=体验版 formal=正式版,为空时删除配置")
@Pattern(regexp = "^(developer|trial|formal|)$", message = "参数只能是developer/trial/formal或为空")
@RequestParam(required = false) String miniProgramState
) {
aiGenerationService.setMiniProgramState(miniProgramState);
return Result.success();
}
@Operation(summary = "更新任务可见范围")
@PostMapping("/my/task/visibility")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> updateTaskVisibility(@Valid @RequestBody AiTaskVisibilityForm form) {
Long userId = SecurityUtils.getUserId();
aiGenerationService.updateTaskVisibility(userId, form);
return Result.success();
}
@Operation(summary = "查询公开作品发现页")
@GetMapping("/discovery/feed")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.LIST)
public Result<IPage<DiscoveryPublicWorkVO>> getPublicDiscoveryFeed(@Valid DiscoveryPublicWorkQuery query) {
IPage<DiscoveryPublicWorkVO> result = aiGenerationService.getPublicDiscoveryFeed(query);
return Result.success(result);
}
@Operation(summary = "手动同步AI任务状态")
@PostMapping("/task/{taskUuid}/sync-status")
@RepeatSubmit(expire = 60)
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> syncTaskStatus(@PathVariable String taskUuid) {
Long userId = SecurityUtils.getUserId();
MiniAiGenerationTask task = aiGenerationService.getTaskByUuid(taskUuid);
if (task == null) {
return Result.failed("任务不存在");
}
if (!task.getMiniUserId().equals(userId)) {
return Result.failed("无权操作该任务");
}
aiGenerationService.syncTaskStatus(taskUuid);
return Result.success();
}
}

2
src/main/java/com/youlai/boot/mini/controller/MiniUserController.java

@ -46,7 +46,7 @@ public class MiniUserController {
@Operation(summary = "修改当前登录用户基本信息") @Operation(summary = "修改当前登录用户基本信息")
@PostMapping(value = "/updateInfo") @PostMapping(value = "/updateInfo")
@Log(module = LogModuleEnum.USER, value = ActionTypeEnum.UPDATE) @Log(module = LogModuleEnum.USER, value = ActionTypeEnum.UPDATE)
public Result<Void> updateCurrentUserInfo(@Valid MiniUserUpdateForm form) { public Result<Void> updateCurrentUserInfo(@Valid @RequestBody MiniUserUpdateForm form) {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
miniUserService.updateCurrentUserInfo(userId, form); miniUserService.updateCurrentUserInfo(userId, form);
return Result.success(); return Result.success();

28
src/main/java/com/youlai/boot/mini/job/AiTaskTimeoutJob.java

@ -0,0 +1,28 @@
package com.youlai.boot.mini.job;
import com.youlai.boot.mini.service.AiGenerationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class AiTaskTimeoutJob {
private final AiGenerationService aiGenerationService;
/**
* 每10分钟扫描一次超时的AI生成任务标记为超时并退还积分
*/
@Scheduled(cron = "0 */10 * * * ?")
public void execute() {
log.info("开始扫描超时AI生成任务");
try {
aiGenerationService.handleTimeoutTasks();
} catch (Exception e) {
log.error("扫描超时AI生成任务异常", e);
}
}
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniAiGenerationTaskMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* AI生成任务统一表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAiGenerationTaskMapper extends BaseMapper<MiniAiGenerationTask> {
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniAiTaskMediaMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* AI任务媒体文件表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAiTaskMediaMapper extends BaseMapper<MiniAiTaskMedia> {
}

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);
}

31
src/main/java/com/youlai/boot/mini/model/dto/GenerateResponse.java

@ -0,0 +1,31 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
/**
* AI生成接口响应DTO
*
* @author youlai
*/
@Data
public class GenerateResponse {
/**
* 响应消息
*/
private String msg;
/**
* 响应码0表示成功
*/
private Integer code;
/**
* 生成结果数据
*/
private GenerationData data;
/**
* 请求ID
*/
private String request_id;
}

48
src/main/java/com/youlai/boot/mini/model/dto/GenerationData.java

@ -0,0 +1,48 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
import java.util.List;
/**
* AI生成结果数据DTO
*
* @author youlai
*/
@Data
public class GenerationData {
/**
* 使用的模型ID
*/
private String model;
/**
* 生成的图片列表
*/
private List<ImageData> data;
/**
* 错误信息无错误则为null
*/
private Object error;
/**
* Token使用统计
*/
private Object usage;
/**
* 创建时间戳
*/
private Long created_at;
/**
* 使用的工具
*/
private String tool;
/**
* 创建时间戳
*/
private Long created;
}

26
src/main/java/com/youlai/boot/mini/model/dto/ImageData.java

@ -0,0 +1,26 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
/**
* 生成的图片数据DTO
*
* @author youlai
*/
@Data
public class ImageData {
/**
* 图片URL地址
*/
private String url;
/**
* Base64编码的图片JSON
*/
private String b64_json;
/**
* 图片尺寸格式如 1664x2496
*/
private String size;
}

94
src/main/java/com/youlai/boot/mini/model/entity/MiniAiGenerationTask.java

@ -0,0 +1,94 @@
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_ai_generation_task")
@Schema(description = "AI生成任务统一表")
public class MiniAiGenerationTask implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "笔记ID")
private Long id;
@TableField("uuid")
@Schema(description = "uuid唯一标识,前后端用这个进行数据交互")
private String uuid;
@TableField("refund_uuid")
@Schema(description = "退积分唯一uuid")
private String refundUuid;
@TableField("mini_user_id")
@Schema(description = "作者用户ID")
private Long miniUserId;
@TableField("type")
@Schema(description = "生成类型:img_single, img_grid_4, video")
private String type;
@TableField("generate_params")
@Schema(description = "生成参数JSON,不同类型参数不同")
private String generateParams;
@TableField("video_task_uuid")
@Schema(description = "调用视频接口时返回")
private String videoTaskUuid;
@TableField("status")
@Schema(description = "任务状态:0=生成中 1=成功 2=失败 3=超时 4=已取消")
private Integer status;
@TableField("visibility")
@Schema(description = "可见范围:public-公开,private-仅自己可见,friends-好友可见")
private String visibility;
@TableField("points_consumed")
@Schema(description = "消耗积分")
private Integer pointsConsumed;
@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;
}

102
src/main/java/com/youlai/boot/mini/model/entity/MiniAiTaskMedia.java

@ -0,0 +1,102 @@
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_ai_task_media")
@Schema(description = "AI任务媒体文件表")
public class MiniAiTaskMedia implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "")
private Long id;
@TableField("uuid")
@Schema(description = "uuid唯一标识,前后端用这个进行数据交互")
private String uuid;
@TableField("mini_user_id")
@Schema(description = "用户id")
private Long miniUserId;
@TableField("task_id")
@Schema(description = "AI生成任务id")
private Long taskId;
@TableField("file_source")
@Schema(description = "文件来源:user_upload(用户上传), ai_generated(AI生成)")
private String fileSource;
@TableField("media_type")
@Schema(description = "媒体类型,image-图片,video-视频")
private String mediaType;
@TableField("source_url")
@Schema(description = "资源URL")
private String sourceUrl;
@TableField("storage_key")
@Schema(description = "对象存储中的key")
private String storageKey;
@TableField("thumbnail_url")
@Schema(description = "缩略图URL(视频需要)")
private String thumbnailUrl;
@TableField("width")
@Schema(description = "宽度(像素)")
private Integer width;
@TableField("height")
@Schema(description = "高度(像素)")
private Integer height;
@TableField("duration")
@Schema(description = "时长(秒,视频用)")
private Integer duration;
@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;
}

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;
}
}

25
src/main/java/com/youlai/boot/mini/model/form/AiCallbackImage.java

@ -0,0 +1,25 @@
package com.youlai.boot.mini.model.form;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI回调图片通用结构
*
* @author youlai
*/
@Data
@Schema(description = "AI回调图片通用结构")
public class AiCallbackImage {
@Schema(description = "图片URL地址")
private String url;
@JsonProperty("b64_json")
@Schema(description = "Base64编码的图片JSON")
private String b64Json;
@Schema(description = "图片尺寸,格式如 2048x2048")
private String size;
}

35
src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java

@ -0,0 +1,35 @@
package com.youlai.boot.mini.model.form;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
/**
* 四宫格漫画任务回调请求表单
*
* @author youlai
*/
@Data
@Schema(description = "四宫格漫画任务回调请求表单")
public class AiFourPanelCallbackForm {
@NotBlank(message = "任务UUID不能为空")
@Schema(description = "任务唯一标识UUID", requiredMode = Schema.RequiredMode.REQUIRED)
private String uuid;
@Schema(description = "任务状态:succeeded=成功,failed=失败")
private String status;
@Schema(description = "生成的图片列表")
private List<AiCallbackImage> result;
@JsonProperty("created_at")
@Schema(description = "创建时间戳")
private Long createdAt;
@JsonProperty("updated_at")
@Schema(description = "更新时间戳")
private Long updatedAt;
}

57
src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java

@ -0,0 +1,57 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* AI四宫格漫画生成请求表单
*
* @author youlai
*/
@Data
@Schema(description = "AI四宫格漫画生成请求表单")
public class AiFourPanelGenerateForm {
@Schema(description = "参考图片URL列表,可选")
private List<String> referenceImage;
@Schema(description = "使用的模型ID", defaultValue = "doubao-seedream-5-0-260128")
private String model;
@Schema(description = "画风:1=治愈系水彩,2=Q版卡通,3=日式漫画,4=吉卜力风,5=3D毛绒,6=搞笑夸张", defaultValue = "1")
private Integer style;
@Schema(description = "比例:1=1:1,2=2:3,3=3:4,4=4:3,5=5:4", defaultValue = "1")
private Integer ratio;
@Schema(description = "图片尺寸:1=2K,2=4K,3=8K", defaultValue = "1")
private Integer imgSize;
@Schema(description = "图片格式:1=png,2=jpeg", defaultValue = "1")
private Integer imgType;
@Schema(description = "宠物物种:如cat、dog")
private String species;
@Schema(description = "宠物品种:如British Shorthair、Corgi")
private String breed;
@Schema(description = "宠物毛色:如blue-gray、orange")
private String color;
@Schema(description = "眼睛颜色:如amber、blue")
private String eyeColor;
@Schema(description = "体型:如round chubby、slim and elegant")
private String bodyType;
@Schema(description = "特殊特征:如flat face、thick coat", defaultValue = "")
private String distinctiveFeatures;
@Schema(description = "故事梗概:AI会据此生成四格漫画脚本")
private String description;
@Schema(description = "可见范围: public公开 private仅自己", defaultValue = "private")
private String visibility;
}

35
src/main/java/com/youlai/boot/mini/model/form/AiSingleImageCallbackForm.java

@ -0,0 +1,35 @@
package com.youlai.boot.mini.model.form;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
/**
* 单图生成任务回调请求表单
*
* @author youlai
*/
@Data
@Schema(description = "单图生成任务回调请求表单")
public class AiSingleImageCallbackForm {
@NotBlank(message = "任务UUID不能为空")
@Schema(description = "任务唯一标识UUID", requiredMode = Schema.RequiredMode.REQUIRED)
private String uuid;
@Schema(description = "任务状态:succeeded=成功,failed=失败")
private String status;
@Schema(description = "生成的图片列表")
private List<AiCallbackImage> result;
@JsonProperty("created_at")
@Schema(description = "创建时间戳")
private Long createdAt;
@JsonProperty("updated_at")
@Schema(description = "更新时间戳")
private Long updatedAt;
}

57
src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java

@ -0,0 +1,57 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* AI单图漫画生成请求表单
*
* @author youlai
*/
@Data
@Schema(description = "AI单图漫画生成请求表单")
public class AiSingleImageGenerateForm {
@Schema(description = "参考图片URL列表,可选")
private List<String> imageUrl;
@Schema(description = "使用的模型ID", defaultValue = "doubao-seedream-5-0-260128")
private String model;
@Schema(description = "画风:1=治愈系水彩,2=Q版卡通,3=日式漫画,4=吉卜力风,5=3D毛绒,6=搞笑夸张", defaultValue = "1")
private Integer style;
@Schema(description = "比例:1=1:1,2=2:3,3=3:4,4=4:3,5=5:4", defaultValue = "1")
private Integer ratio;
@Schema(description = "图片尺寸:1=2K,2=4K,3=8K", defaultValue = "1")
private Integer imgSize;
@Schema(description = "图片格式:1=png,2=jpeg", defaultValue = "1")
private Integer imgType;
@Schema(description = "宠物物种:如cat、dog")
private String species;
@Schema(description = "宠物品种:如British Shorthair、Corgi")
private String breed;
@Schema(description = "宠物毛色:如blue-gray、orange")
private String color;
@Schema(description = "眼睛颜色:如amber、blue")
private String eyeColor;
@Schema(description = "体型:如round chubby、slim and elegant")
private String bodyType;
@Schema(description = "特殊特征:如flat face、thick coat", defaultValue = "")
private String distinctiveFeatures;
@Schema(description = "场景描述:用户自定义生成场景")
private String description;
@Schema(description = "可见范围: public公开 private仅自己", defaultValue = "private")
private String visibility;
}

23
src/main/java/com/youlai/boot/mini/model/form/AiTaskVisibilityForm.java

@ -0,0 +1,23 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "更新任务可见范围表单")
public class AiTaskVisibilityForm {
@NotBlank(message = "任务UUID不能为空")
@Schema(description = "任务UUID")
private String uuid;
@NotBlank(message = "可见范围不能为空")
@Pattern(regexp = "^(public|private|friends)$", message = "可见范围只能是public/private/friends")
@Schema(description = "可见范围:public-公开,private-仅自己可见")
private String visibility;
}

30
src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java

@ -0,0 +1,30 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* AI视频生成请求表单
*
* @author youlai
*/
@Data
@Schema(description = "AI视频生成请求表单")
public class AiVideoGenerateForm {
@Schema(description = "使用的模型ID", defaultValue = "doubao-seedance-2-0-260128")
private String model;
@Schema(description = "内容数组,包含text、image_url、video_url、audio_url类型")
private List<VideoContentItem> content;
@Schema(description = "分辨率: 480p, 720p, 1080p, 2K", defaultValue = "720p")
private String resolution = "720p";
@Schema(description = "视频时长,单位秒,最大15秒", defaultValue = "5")
private Integer duration = 5;
@Schema(description = "可见范围: public公开 private仅自己", defaultValue = "private")
private String visibility;
}

19
src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java

@ -0,0 +1,19 @@
package com.youlai.boot.mini.model.form;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 媒体URL通用结构
*
* @author youlai
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "媒体URL通用结构")
public class MediaUrl {
@Schema(description = "资源URL地址")
private String url;
}

34
src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java

@ -0,0 +1,34 @@
package com.youlai.boot.mini.model.form;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 视频生成内容项
*
* @author youlai
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "视频生成内容项")
public class VideoContentItem {
@Schema(description = "内容类型:text/image_url/video_url/audio_url", allowableValues = {"text", "image_url", "video_url", "audio_url"})
private String type;
@Schema(description = "文本内容,type=text时必填")
private String text;
@Schema(description = "图片URL结构,type=image_url时必填")
private MediaUrl image_url;
@Schema(description = "视频URL结构,type=video_url时必填")
private MediaUrl video_url;
@Schema(description = "音频URL结构,type=audio_url时必填")
private MediaUrl audio_url;
@Schema(description = "内容角色标识,比如reference_image/reference_video/reference_audio")
private String role;
}

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;
@NotNull(message = "模板参数不能为空")
@Schema(description = "模板参数,key是模板字段名,value是对应的值", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, String> templateParams;
}

21
src/main/java/com/youlai/boot/mini/model/query/AiTaskMediaQuery.java

@ -0,0 +1,21 @@
package com.youlai.boot.mini.model.query;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* 用户AI生成任务历史查询参数
*/
@Getter
@Setter
@Schema(description = "用户AI生成任务历史查询参数")
public class AiTaskMediaQuery extends BaseQuery {
@Schema(description = "任务状态:0=生成中 1=成功 2=失败 3=超时 4=已取消,不传查全部")
private Integer status;
@Schema(description = "生成类型:img_single(单图), img_grid_4(四宫格), video(视频),不传查全部")
private String type;
}

15
src/main/java/com/youlai/boot/mini/model/query/DiscoveryPublicWorkQuery.java

@ -0,0 +1,15 @@
package com.youlai.boot.mini.model.query;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "发现页公开作品查询参数")
public class DiscoveryPublicWorkQuery extends BaseQuery {
@Schema(description = "生成类型:img_single(单图), img_grid_4(四宫格), video(视频),不传查全部")
private String type;
}

42
src/main/java/com/youlai/boot/mini/model/vo/AiGenerationTaskVO.java

@ -0,0 +1,42 @@
package com.youlai.boot.mini.model.vo;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.List;
/**
* AI生成任务列表VO
*/
@Getter
@Setter
@Schema(description = "AI生成任务列表VO")
public class AiGenerationTaskVO {
// @Hidden
// @Schema(description = "任务ID",hidden = true)
// private Long id;
@Schema(description = "任务唯一标识")
private String uuid;
@Schema(description = "生成类型:img_single(单图), img_grid_4(四宫格), video(视频)")
private String type;
@Schema(description = "任务状态:0=生成中 1=成功 2=失败 3=超时 4=已取消")
private Integer status;
@Schema(description = "消耗积分")
private Integer pointsConsumed;
@Schema(description = "可见范围:public(公开) / private(仅自己)")
private String visibility;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "生成内容列表")
private List<MiniAiTaskMediaVO> generateContent;
}

70
src/main/java/com/youlai/boot/mini/model/vo/AiVideoCallbackVO.java

@ -0,0 +1,70 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI视频生成任务回调请求
*
* @author youlai
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "AI视频生成任务回调请求")
public class AiVideoCallbackVO {
@Schema(description = "第三方视频任务唯一ID")
private String id;
@Schema(description = "使用的模型ID")
private String model;
@Schema(description = "任务状态:queued排队中/running处理中/succeeded成功/failed失败")
private String status;
@JsonProperty("created_at")
@Schema(description = "创建时间戳")
private Long createdAt;
@JsonProperty("updated_at")
@Schema(description = "更新时间戳")
private Long updatedAt;
@JsonProperty("service_tier")
@Schema(description = "服务等级")
private String serviceTier;
@JsonProperty("execution_expires_after")
@Schema(description = "执行过期时间")
private Long executionExpiresAfter;
@JsonProperty("generate_audio")
@Schema(description = "是否生成音频")
private Boolean generateAudio;
@Schema(description = "是否为草稿")
private Boolean draft;
@Schema(description = "生成结果,status=succeeded时返回")
private VideoCallbackContent content;
@Schema(description = "资源消耗统计")
private VideoCallbackUsage usage;
@Schema(description = "随机种子")
private Long seed;
@Schema(description = "分辨率,比如720p")
private String resolution;
@Schema(description = "比例,比如16:9")
private String ratio;
@Schema(description = "视频时长,单位秒")
private Integer duration;
@Schema(description = "帧率")
private Integer framespersecond;
}

38
src/main/java/com/youlai/boot/mini/model/vo/DiscoveryPublicWorkVO.java

@ -0,0 +1,38 @@
package com.youlai.boot.mini.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@Schema(description = "发现页公开作品VO")
public class DiscoveryPublicWorkVO {
@Schema(description = "任务唯一标识")
private String uuid;
@Schema(description = "生成类型:img_single(单图), img_grid_4(四宫格), video(视频)")
private String type;
@Schema(description = "消耗积分")
private Integer pointsConsumed;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "生成内容列表")
private List<MiniAiTaskMediaVO> generateContent;
@Schema(description = "创作者用户UUID")
private String userUuid;
@Schema(description = "创作者昵称")
private String nickname;
@Schema(description = "创作者头像")
private String avatar;
}

39
src/main/java/com/youlai/boot/mini/model/vo/MiniAiTaskMediaVO.java

@ -0,0 +1,39 @@
package com.youlai.boot.mini.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* AI生成媒体内容VO
*/
@Getter
@Setter
@Schema(description = "AI生成媒体内容VO")
public class MiniAiTaskMediaVO {
@Schema(description = "唯一标识")
private String uuid;
@Schema(description = "媒体类型,image-图片,video-视频")
private String mediaType;
@Schema(description = "资源URL")
private String sourceUrl;
@Schema(description = "缩略图URL(视频需要)")
private String thumbnailUrl;
@Schema(description = "宽度(像素)")
private Integer width;
@Schema(description = "高度(像素)")
private Integer height;
@Schema(description = "时长(秒,视频用)")
private Integer duration;
@Schema(description = "创建时间")
private Date createTime;
}

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

@ -0,0 +1,48 @@
package com.youlai.boot.mini.model.vo;
import com.baomidou.mybatisplus.annotation.TableField;
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 = "媒体UUID")
private String uuid;
@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;
@TableField("create_timestamp")
@Schema(description = "创建时间毫秒级时间戳")
private Long createTimestamp;
}

21
src/main/java/com/youlai/boot/mini/model/vo/VideoCallbackContent.java

@ -0,0 +1,21 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI视频生成回调结果内容
*
* @author youlai
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "AI视频生成回调结果内容")
public class VideoCallbackContent {
@JsonProperty("video_url")
@Schema(description = "生成的视频地址")
private String videoUrl;
}

25
src/main/java/com/youlai/boot/mini/model/vo/VideoCallbackUsage.java

@ -0,0 +1,25 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 视频生成资源消耗统计
*
* @author youlai
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(description = "视频生成资源消耗统计")
public class VideoCallbackUsage {
@JsonProperty("completion_tokens")
@Schema(description = "完成token数")
private Integer completionTokens;
@JsonProperty("total_tokens")
@Schema(description = "总token数")
private Integer totalTokens;
}

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;
}

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

@ -0,0 +1,56 @@
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.AiSingleImageCallbackForm;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.form.AiVideoGenerateForm;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm;
import com.youlai.boot.mini.model.form.AiTaskVisibilityForm;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.mini.model.query.AiTaskMediaQuery;
import com.youlai.boot.mini.model.query.DiscoveryPublicWorkQuery;
import com.youlai.boot.mini.model.vo.AiGenerationTaskVO;
import com.youlai.boot.mini.model.vo.DiscoveryPublicWorkVO;
import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface AiGenerationService {
List<String> uploadReferenceFile(List<MultipartFile> images, List<MultipartFile> videos, Long userId);
String createAndGenerateImage(AiSingleImageGenerateForm form, Long userId);
String createAndGenerateFourPanel(AiFourPanelGenerateForm form, Long userId);
boolean handleTaskCallback(AiSingleImageCallbackForm form);
String createAndGenerateVideo(AiVideoGenerateForm form, Long userId);
boolean handleVideoTaskCallback(AiVideoCallbackVO vo);
boolean handleFourPanelCallback(AiFourPanelCallbackForm form);
MiniAiGenerationTask getTaskByUuid(String uuid);
List<UserUploadMediaVO> getRecentUploadVO(Long userId);
boolean deleteUploadMedia(Long userId, String uuid);
IPage<AiGenerationTaskVO> getMyAiGenerateHistory(AiTaskMediaQuery query, Long userId);
void setMiniProgramState(String miniProgramState);
void handleTimeoutTasks();
IPage<DiscoveryPublicWorkVO> getPublicDiscoveryFeed(DiscoveryPublicWorkQuery query);
void updateTaskVisibility(Long userId, AiTaskVisibilityForm form);
void syncTaskStatus(String taskUuid);
}

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);
}

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

File diff suppressed because it is too large

1
src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java

@ -132,6 +132,7 @@ public class MiniUserServiceImpl implements MiniUserService {
log.warn("删除用户旧头像失败,userId={}, oldAvatar={}", userId, oldAvatar, e); log.warn("删除用户旧头像失败,userId={}, oldAvatar={}", userId, oldAvatar, e);
} }
} }
sysUserService.update(updateWrapper);
} catch (Exception e) { } catch (Exception e) {
log.error("user avatar upload failed", e); log.error("user avatar upload failed", e);
throw new MsgException("头像上传失败"); throw new MsgException("头像上传失败");

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

@ -0,0 +1,148 @@
package com.youlai.boot.mini.service.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.IdUtil;
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.setUuid(IdUtil.fastSimpleUUID());
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. 构建微信订阅消息参数(新版小程序订阅消息格式)
Map<String, Map<String, String>> dataMap = new HashMap<>();
for (Map.Entry<String, String> entry : form.getTemplateParams().entrySet()) {
Map<String, String> valueMap = new HashMap<>();
valueMap.put("value", entry.getValue());
dataMap.put(entry.getKey(), valueMap);
}
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", dataMap);
// 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;
}
}
}

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

@ -91,6 +91,10 @@ security:
- /api/v1/mini/public/** - /api/v1/mini/public/**
- /api/v1/mini/homePage/listByBounds - /api/v1/mini/homePage/listByBounds
- /healthcheck - /healthcheck
- /api/v1/mini/ai/generation/single-image/task/callback # AIGeneration 单图任务回调
- /api/v1/mini/ai/generation/video/task/callback # AIGeneration 视频任务回调
- /api/v1/mini/ai/generation/four-panel/task/callback # AIGeneration 四宫格任务回调
- /api/v1/mini/ai/generation/subscribe/send # AIGeneration 发送订阅消息
# 非安全端点路径,完全绕过 Spring Security 的过滤器 # 非安全端点路径,完全绕过 Spring Security 的过滤器
unsecured-urls: unsecured-urls:
- ${springdoc.swagger-ui.path} - ${springdoc.swagger-ui.path}
@ -223,5 +227,38 @@ captcha:
# 微信小程序配置 # 微信小程序配置
wx: wx:
miniapp: miniapp:
appid: Your_AppId appid: wx56425c3301f5c6df
secret: Your_AppSecret secret: 7c4060199f49b0ab872b06b97cef2ac4
# AIGeneration 配置
ai:
generate:
single-image-server-url: http://127.0.0.1:8001/api/v1/photo-to-comic
four-panel-server-url: http://127.0.0.1:8001/api/v1/four-panel-comic
video-server-url: http://127.0.0.1:8001/api/v1/video/submit
callback:
single-image-callback-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/single-image/task/callback
four-panel-callback-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/four-panel/task/callback
#需要内网穿透工具 由火山方舟 api 回调,ngrok http 30101 替换为内网穿透工具地址
video-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/video/task/callback
default:
image-model: doubao-seedream-5-0-260128
video-model: doubao-seedance-2-0-260128
task:
# AI任务超时时间 单位分钟,默认300分钟
timeout-minutes: 300
# AI任务状态查询接口
status:
single-image-url: http://127.0.0.1:8001/api/v1/photo-to-comic/status/
four-panel-url: http://127.0.0.1:8001/api/v1/four-panel-comic/status/
video-url: http://127.0.0.1:8001/api/v1/video/status/
# 手动同步最小间隔(分钟),任务创建后至少等待此时间才能手动同步
sync-min-interval-minutes: 30
# 订阅模板配置
subscribe:
template: "7m5Vu4gaCo2zY6hzID4yqEv94y1guuOGxdPOJYV_xHE"
# developer=开发版 trial=体验版 formal=正式版
miniProgramState: "developer"

9
src/main/resources/mapper/mini/MiniAiGenerationTaskMapper.xml

@ -0,0 +1,9 @@
<?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.MiniAiGenerationTaskMapper">
</mapper>

9
src/main/resources/mapper/mini/MiniAiTaskMediaMapper.xml

@ -0,0 +1,9 @@
<?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.MiniAiTaskMediaMapper">
</mapper>

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

@ -0,0 +1,15 @@
<?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}
LIMIT 1
</select>
</mapper>
Loading…
Cancel
Save