AI模块重构

This commit is contained in:
ql
2025-05-26 16:47:20 +08:00
parent e106cc7bbb
commit caeb23719f
676 changed files with 16047 additions and 35958 deletions
@@ -1,5 +1,8 @@
package com.hula.utils;
import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
@@ -7,6 +10,139 @@ import java.util.Date;
* @author nyh
*/
public class DateUtil extends org.apache.commons.lang3.time.DateUtils {
/**
* 时区 - 默认
*/
public static final String TIME_ZONE_DEFAULT = "GMT+8";
/**
* 秒转换成毫秒
*/
public static final long SECOND_MILLIS = 1000;
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
/**
* 将 LocalDateTime 转换成 Date
*
* @param date LocalDateTime
* @return LocalDateTime
*/
public static Date of(LocalDateTime date) {
if (date == null) {
return null;
}
// 将此日期时间与时区相结合以创建 ZonedDateTime
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
Instant instant = zonedDateTime.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return Date.from(instant);
}
/**
* 将 Date 转换成 LocalDateTime
*
* @param date Date
* @return LocalDateTime
*/
public static LocalDateTime of(Date date) {
if (date == null) {
return null;
}
// 转为时间戳
Instant instant = date.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
public static Date addTime(Duration duration) {
return new Date(System.currentTimeMillis() + duration.toMillis());
}
public static boolean isExpired(LocalDateTime time) {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(time);
}
/**
* 创建指定时间
*
* @param year 年
* @param mouth 月
* @param day 日
* @return 指定时间
*/
public static Date buildTime(int year, int mouth, int day) {
return buildTime(year, mouth, day, 0, 0, 0);
}
/**
* 创建指定时间
*
* @param year 年
* @param mouth 月
* @param day 日
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @return 指定时间
*/
public static Date buildTime(int year, int mouth, int day,
int hour, int minute, int second) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, mouth - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
return calendar.getTime();
}
public static Date max(Date a, Date b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.compareTo(b) > 0 ? a : b;
}
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.isAfter(b) ? a : b;
}
/**
* 是否今天
*
* @param date 日期
* @return 是否
*/
public static boolean isToday(LocalDateTime date) {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
}
/**
* 是否昨天
*
* @param date 日期
* @return 是否
*/
public static boolean isYesterday(LocalDateTime date) {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
}
public static Long getEndTimeByToday() {
Calendar instance = Calendar.getInstance();
Date now = new Date();
+136
View File
@@ -148,6 +148,142 @@
</exclusion>
</exclusions>
</dependency>
<!-- Spring AI Model 模型接入 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- 通义千问 -->
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>${spring-ai.version}.1</version>
</dependency>
<dependency>
<!-- 文心一言 -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- 智谱 GLM -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-minimax-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
<dependency>
<!-- Qdranthttps://qdrant.tech/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qdrant-store</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- Redishttps://redis.io/docs/latest/develop/get-started/vector-database/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- Milvushttps://milvus.io/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<!-- 解决和 logback 的日志冲突 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- Tika:负责内容的解析 -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-cloud-function-context</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
<exclusion>
<artifactId>spring-cloud-function-core</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-anno</artifactId>
<version>${easy-trans.version}</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
<version>${mybatis-plus-join.version}</version>
</dependency>
<!-- TinyFlowAI 工作流 -->
<dependency>
<groupId>dev.tinyflow</groupId>
<artifactId>tinyflow-java-core</artifactId>
<version>${tinyflow.version}</version>
<exclusions>
<exclusion>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
</exclusion>
<exclusion>
<groupId>com.agentsflex</groupId>
<artifactId>agents-flex-store-elasticsearch</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
<!-- 解决和 logback 的日志冲突 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
@@ -1,56 +0,0 @@
package com.hula.ai.client.enums;
import lombok.Getter;
/**
* 聊天内容类型枚举类
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum ChatContentEnum {
/**
* TEXT
*/
TEXT("text", "文字"),
IMAGE("image", "图片"),
VOICE("voice", "音频"),
VIDEO("video", "视频"),
FILE("file", "文件"),
;
/**
* 值
*/
private final String value;
/**
* 标签
*/
private final String label;
ChatContentEnum(final String value, final String label) {
this.label = label;
this.value = value;
}
public static ChatContentEnum getEnum(String value) {
for (ChatContentEnum chatModelEnum : ChatContentEnum.values()) {
if (value.equals(chatModelEnum.value)) {
return chatModelEnum;
}
}
return null;
}
}
@@ -1,65 +0,0 @@
package com.hula.ai.client.enums;
import lombok.Getter;
/**
* 聊天大模型枚举类
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
* 得其道 乾乾
*/
@Getter
public enum ChatModelEnum {
/**
* CHAT_GPT
*/
OPENAI("ChatGPT", "ChatGPT"),
WENXIN("WENXIN", "文心一言"),
CHATGLM("ChatGLM", "智谱清言"),
TONGYI("QIANWEN", "通义千问"),
SPARK("SPARK", "讯飞星火"),
MOONSHOT("Moonshot", "月之暗面"),
DEEPSEEK("DeepSeek", "深度求索"),
DOUBAO("Doubao", "豆包"),
INTERNLM("Internlm", "书生·浦语"),
LOCALLM("LocalLM", "本地模型"),
;
/**
* 值
*/
private final String value;
/**
* 标签
*/
private final String label;
ChatModelEnum(final String value, final String label) {
this.label = label;
this.value = value;
}
public static ChatModelEnum getEnum(String value) {
for (ChatModelEnum chatModelEnum : ChatModelEnum.values()) {
if (value.equals(chatModelEnum.value)) {
return chatModelEnum;
}
}
return null;
}
}
@@ -1,41 +0,0 @@
package com.hula.ai.client.enums;
import lombok.Getter;
/**
* 聊天角色枚举类
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum ChatRoleEnum {
/**
* 角色
*/
SYSTEM("system", "系统"),
ASSISTANT("assistant", "角色"),
USER("user", "用户");
/**
* 值
*/
private final String value;
/**
* 标签
*/
private final String label;
ChatRoleEnum(final String value, final String label) {
this.label = label;
this.value = value;
}
}
@@ -1,41 +0,0 @@
package com.hula.ai.client.enums;
import lombok.Getter;
/**
* 审核类型枚举类
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum ChatStatusEnum {
/**
* 回复状态 1 回复中 2正常 3 失败
*/
REPLY(1, "回复中"),
SUCCESS(2, "成功"),
ERROR(3, "失败");
/**
* 值
*/
private final Integer value;
/**
* 标签
*/
private final String label;
ChatStatusEnum(final Integer value, final String label) {
this.label = label;
this.value = value;
}
}
@@ -1,75 +0,0 @@
package com.hula.ai.client.model.command;
import com.hula.ai.common.api.CommonCommand;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.List;
/**
* 对话消息对象 Command
*
* @author: 云裂痕
* @date: 2025-03-08
* @version: 1.2.8
* 得其道 乾乾
*/
@Data
public class ChatCommand extends CommonCommand implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 聊天编号
*/
@NotBlank(message = "缺少对话标识")
private String chatNumber;
/**
* 对话id
*/
private String conversationId;
/**
* 用户id
*/
private Long uid;
/**
* 角色模型id
*/
private Long assistantId;
/**
* 系统提示
*/
private String systemPrompt;
/**
* 提示
*/
@NotBlank(message = "缺少提示词")
private String prompt;
/**
* 使用模型
*/
@NotBlank(message = "缺少模型信息")
private String model;
/**
* 使用模型版本
*/
private String modelVersion;
/**
* 是否api请求
*/
private Boolean api;
/**
* 聊天内容
*/
private List<ChatMessageCommand> messages;
}
@@ -1,92 +0,0 @@
package com.hula.ai.client.model.command;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 对话消息对象 Command
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessageCommand implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 聊天记录id
*/
private Long chatId;
/**
* 关联消息id
*/
private String parentMessageId;
/**
* 第三方消息id
*/
private String messageId;
/**
* 使用模型
*/
private String model;
/**
* 使用模型版本
*/
private String modelVersion;
/**
* 消息内容
*/
private String content;
/**
* 消息类型 text、image
*/
private String contentType;
/**
* 角色模型
*/
private String role;
/**
* finish_reason
*/
private String finishReason;
/**
* status
*/
private Integer status;
/**
* 调用token
*/
private String appKey;
/**
* 使用额度
*/
private Long usedTokens;
/**
* 响应全文
*/
private String response;
}
@@ -1,18 +0,0 @@
package com.hula.ai.client.model.command;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class CompletionsParam {
@Schema(description = "文件映射", required = true)
private List<String> fileIds;
private String ws;
@Schema(description = "会话id [每次发送消息都不一样]", required = true)
private String conversationId;
}
@@ -1,13 +0,0 @@
package com.hula.ai.client.model.command;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Data
public class DeleteFileParam extends FileParam {
@NotBlank(message = "请选择文件")
private List<String> fileIds;
}
@@ -1,12 +0,0 @@
package com.hula.ai.client.model.command;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class FileParam {
@NotBlank(message = "请选择解析模型")
private String model;
}
@@ -1,15 +0,0 @@
package com.hula.ai.client.model.command;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UploadParam {
@NotBlank(message = "缺少对话标识")
private String chatNumber;
@NotBlank(message = "请选择解析模型")
private String model;
}
@@ -1,72 +0,0 @@
package com.hula.ai.client.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 对话消息对象 DTO
*
* @author: 云裂痕
* @date: 2025-03-08
* 得其道 乾乾
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
/**
* 聊天记录id
*/
private Long chatId;
/**
* 关联消息id
*/
private String parentMessageId;
/**
* 消息id
*/
private String messageId;
/**
* 使用模型
*/
private String model;
/**
* 使用模型版本
*/
private String modelVersion;
/**
* 聊天摘要
*/
private String role;
/**
* 聊天摘要
*/
private String content;
/**
* 消息类型 text、image
*/
private String contentType;
/**
* 回复状态
*/
private Integer status;
}
@@ -1,87 +0,0 @@
package com.hula.ai.client.model.dto;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 大模型信息对象 DTO
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class ModelDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 创建人
*/
private Long createdBy;
/**
* 创建时间
*/
private LocalDateTime createdTime;
/**
* 更新人
*/
private String updateUser;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 模型名称
*/
private String name;
/**
* 模型logo
*/
private String icon;
/**
* 模型名称
*/
private String model;
/**
* 模型版本
*/
private String version;
/**
* 本地模型类型:1、Langchian2、ollama3、Giteeai
*/
private Integer localModelType;
/**
* 模型接口地址
*/
private String modelUrl;
/**
* 知识库名称
*/
private String knowledge;
/**
* 状态 0 禁用 1 启用
*/
private Integer status;
}
@@ -1,103 +0,0 @@
package com.hula.ai.client.service;
import com.hula.ai.client.model.command.ChatCommand;
import com.hula.ai.client.model.command.ChatMessageCommand;
import com.hula.ai.client.model.dto.ChatMessageDTO;
import com.hula.ai.client.model.dto.ModelDTO;
import java.util.List;
/**
* gpt服务
*
* @author: 云裂痕
* @date: 2024/5/29
* @version: 1.0.0
* Copyright Ⓒ 2024 Master Computer Corporation Limited All rights reserved.
*/
public interface GptService {
/**
* 新增聊天摘要
*
* @param command 聊天摘要
* @return 结果
*/
Long saveChat(ChatCommand command);
/**
* 提问
*
* @param command 提问内容
* @return 返回json
*/
String chatMessage(ChatCommand command);
/**
* 根据对话id 获取对话内容
*
* @param conversationId
* @return
*/
ChatMessageDTO getMessageByConverstationId(String conversationId);
/**
* 根据对话id 获取上下文内容 最多10条
*
* @param uid 操作人
* @param conversationId 对话id
* @return
*/
List<ChatMessageDTO> listMessageByConverstationId(Long uid, String conversationId);
/**
* 新增回复消息(流式输出使用)
*
* @param command 对话消息
* @return 结果
*/
void saveChatMessage(ChatMessageCommand command);
/**
* 新增对话消息(json使用)
*
* @param command 对话消息
* @return 结果
*/
List<ChatMessageDTO> saveChatMessage(ChatCommand command, Long chatId, String messageId);
/**
* 校验用户额度
*
* @param command
* @return
*/
ChatCommand validateGptCommand(ChatCommand command);
/**
* 修改对话状态
*
* @param messageId 消息id
* @param status 状态
* @return 结果
*/
void updateMessageStatus(String messageId, Integer status);
/**
* 更新对话使用token数
*
* @param messageId 消息id
* @param usedTokens 使用token数
* @return
*/
void updateMessageUsedTokens(String messageId, Long usedTokens);
/**
* 获取模型信息
*
* @param model 模型名称
* @return
*/
ModelDTO getModel(String model);
}
@@ -0,0 +1,22 @@
package com.hula.ai.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Key Value 的键值对
*
* @author 芋道源码
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> implements Serializable {
private K key;
private V value;
}
@@ -1,112 +0,0 @@
package com.hula.ai.common.api;
import java.io.Serializable;
import java.util.List;
/**
* 整个应用通用的Command
*
* @author: 云裂痕
* @date: 2020/12/8
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class CommonCommand implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 操作人
*/
private Long operater;
/**
* 操作人Id
*/
private Long operaterId;
/**
* 是否需要操作人
*/
private boolean needsOperator;
/**
* 操作ids
*/
private List<Long> ids;
/**
* 操作id
*/
private Long id;
/**
* 审核数据源id
*/
private Long auditId;
/**
* 更新数据源id
*/
private Long updateId;
public Long getOperater() {
return this.operater;
}
public void setOperater(Long operater) {
this.operater = operater;
needsOperator = true;
}
public void setOperaterId(Long operaterId) {
this.operaterId = operaterId;
needsOperator = true;
}
public Long getOperaterId() {
return this.operaterId;
}
public boolean isNeedsOperator() {
return needsOperator;
}
public void setNeedsOperator(boolean needsOperator) {
this.needsOperator = needsOperator;
}
public List<Long> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
this.ids = ids;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getAuditId() {
return auditId;
}
public void setAuditId(Long auditId) {
this.auditId = auditId;
}
public Long getUpdateId() {
return updateId;
}
public void setUpdateId(Long updateId) {
this.updateId = updateId;
}
}
@@ -1,28 +0,0 @@
package com.hula.ai.common.api;
import lombok.Getter;
import lombok.Setter;
/**
* 应用key信息
*
* @author: 云裂痕
* @date: 2023/7/27
* @version: 3.7.2
* 得其道 乾乾
*/
@Getter
@Setter
public class Key {
/**
* 密钥Key
*/
private String key;
/**
* 密钥Key Secret
*/
private String secret;
}
@@ -1,27 +0,0 @@
package com.hula.ai.common.constant;
/**
* 通用常量信息
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道 乾乾
*/
public interface AiConstants {
/**
* 资源映射路径 前缀
*/
String RESOURCE_PREFIX = "/files";
/**
* 基础信息
*/
String BASE_INFO = "baseInfo";
/**
* 应用信息
*/
String APP_INFO = "appInfo";
}
@@ -1,31 +0,0 @@
package com.hula.ai.common.constant;
/**
* 阿里云oss格式
*
* @author: 云裂痕
* @date: 2023/11/22
* @version: 1.2.0
* 得其道
* 乾乾
*/
public interface AliOssConstant {
/**
* 查看指定大小的云图片
* logo图标
*/
String STYLE_LOGO = "?x-oss-process=style/logo";
/**
* 查看指定大小的云图片
* 作滚动图
*/
String STYLE_BIGLOGO = "?x-oss-process=style/biglogo";
/**
* 查看指定大小的云图片
* 类别图
*/
String STYLE_CLASS = "?x-oss-process=style/class";
}
@@ -1,108 +0,0 @@
package com.hula.ai.common.constant;
/**
* 鉴权常量
*
* @author: 云裂痕
* @date: 2020/11/17
* @version: 1.0.0
* 得其道 乾乾
*/
public interface AuthConstant {
/**
* 登录类型
*/
String GRANT_TYPE = "grant_type";
/**
* 授权码
*/
String AUTHORIZATION_CODE = "authorization_code";
/**
* 刷新token
*/
String REFRESH_TOKEN = "refresh_token";
/**
* 认证信息Http请求头
*/
String JWT_TOKEN_HEADER = "Authorization";
/**
* 登录token
*/
String TOKEN = "token";
/**
* JWT令牌前缀
*/
String JWT_TOKEN_PREFIX = "Bearer ";
/**
* JWT载体key
*/
String JWT_PAYLOAD_KEY = "payload";
/**
* 密码加密方式
*/
String ENCODE = "encode";
/**
* 密码加密方式
*/
String BCRYPT = "{bcrypt}";
/**
* 密码加密方式(不加密)
*/
String NOOP = "{noop}";
/**
* jwt客户端id
*/
String CLIENT_ID_KEY = "client_id";
/**
* jwt客户端id
*/
String CLIENTID_KEY = "client-id";
/**
* jwt登录用户id
*/
String USERID_KEY = "id";
/**
* jwt登录用户名
*/
String USERNAME_KEY = "username";
/**
* jwt登录用户名
*/
String PASSWORD_KEY = "password";
/**
* JWT存储权限属性
*/
String AUTHORITY_CLAIM_NAME = "authorities";
/**
* JWT存储权限前缀
*/
String AUTHORITY_PREFIX = "ROLE_";
/**
* JWT唯一标识
*/
String JTI = "jti";
/**
* JWT过期时间戳
*/
String EXP = "exp";
}
@@ -1,160 +0,0 @@
package com.hula.ai.common.constant;
/**
* Http 常量
*
* @author: 云裂痕
* @date: 2020/12/9
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface HttpConstant {
/**
* all
*/
String SLASH = "/";
/**
* all
*/
String ALL = "/**";
/**
* http请求
*/
String HTTP = "http://";
/**
* https请求
*/
String HTTPS = "https://";
/**
* 成功
*/
String SUCCESS = "SUCCESS";
/**
* 错误
*/
String FAIL = "FAIL";
/**
* 成功
*/
String OK = "OK";
/**
* no-cache
*/
String NO_CACHE = "no-cache";
/**
* unknown
*/
String UNKNOWN = "unknown";
/**
* x-forwarded-for
*/
String X_FORWARDER_FOR = "x-forwarded-for";
/**
* Proxy-Client-IP
*/
String PROXY_CLIENT_IP = "Proxy-Client-IP";
/**
* WL-Proxy-Client-IP
*/
String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
/**
* HTTP_CLIENT_IP
*/
String HTTP_CLIENT_IP = "HTTP_CLIENT_IP";
/**
* HTTP_X_FORWARDED_FOR
*/
String HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR";
/**
* api
*/
String API = "api";
//header
/**
* 系统平台 ios、android、windows、macos
*/
String OS = "os";
/**
* App、wechat、tiktok-cn、alipay、baidu、H5
*/
String PLAT_FORM = "platform";
/**
* 请求token
*/
String TOKEN = "token";
/**
* 唯一设备号
*/
String DEVICE = "device";
/**
* 应用版本号
*/
String VERSION = "version";
/**
* 系统版本号
*/
String SYSTEM_VERSION = "systemVersion";
/**
* 微信版本号
*/
String WECHAT_VERSION = "wechatVersion";
/**
* 微信版本号
*/
String CLIENT_ID = "client-id";
/**
* key
*/
String APP_KEY = "appKey";
/**
* 密钥
*/
String APP_SECRET = "appSecret";
/**
* 时间戳
*/
String TIME_STAMP = "timestamp";
/**
* 随机数
*/
String NONCE = "nonce";
/**
* 签名
*/
String SIGN = "sign";
/**
* 签名忽略
*/
String SIGN_IGNORE = "signIgnore";
}
@@ -1,19 +0,0 @@
package com.hula.ai.common.constant;
/**
* 对象存储常量类
*
* @author: 云裂痕
* @date: 2023/4/28
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface OssConstant {
/**
* 默认文件夹
*/
String DEFAULT_FILE = "default/";
}
@@ -1,53 +0,0 @@
package com.hula.ai.common.constant;
/**
* redis常量类
*
* @author: 云裂痕
* @date: 2020/3/4
* @version: 1.0.0
* 得其道 乾乾
*/
public interface RedisConstant {
/**
* 默认redis过期时长,单位:秒 一年
*/
Long YEAT_EXPIRE = 12 * 30 * 24 * 3600L;
/**
* 默认redis过期时长,单位:秒 一个月
*/
Long DEFAULT_EXPIRE = 30 * 24 * 3600L;
/**
* 失效时间 单位:(秒)一周
*/
Long WEEK_EXPIRE = 7 * 24 * 3600L;
/**
* 失效时间 单位:(秒)一天
*/
Long DAY_EXPIRE = 24 * 3600L;
/**
* 失效时间 单位:(秒)一小时
*/
Long HOUR_SECONDS = 3600L;
/**
* 失效时间 单位:(秒)五分钟
*/
Long FIVE_MINUTES = 5 * 60L;
/**
* 失效时间 单位:(秒)一分钟
*/
Long ONE_MINUTES = 60L;
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = "rate_limit:";
}
@@ -1,28 +0,0 @@
package com.hula.ai.common.constant;
/**
* 缓存的key 常量
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class RedisConstants implements RedisConstant {
/**
* 有效期7天
*/
public static final long EXPIRE_TIME = 7 * 24 * 3600 * 1000;
/**
* 登录用户 redis key
*/
public static final String LOGIN_TOKEN_KEY = "login_tokens:";
/**
* 字典管理 cache key
*/
public static final String SYS_DICT_KEY = "sys_dict:";
}
@@ -1,54 +0,0 @@
package com.hula.ai.common.constant;
/**
* 资源常量信息
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface ResourceConstants {
/**
* 是否菜单外链(否)
*/
Integer NO_FRAME = 0;
/**
* 菜单类型(目录)
*/
Integer TYPE_DIR = 1;
/**
* 菜单类型(菜单)
*/
Integer TYPE_MENU = 2;
/**
* 菜单类型(按钮)
*/
Integer TYPE_BUTTON = 3;
/**
* 管理员权限标识
*/
String ADMIN_PERMISSIONS = "*:*:*";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
}
@@ -1,14 +0,0 @@
package com.hula.ai.common.constant;
/**
* 短信常量类
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface SmsConstant {
}
@@ -1,99 +0,0 @@
package com.hula.ai.common.constant;
/**
* 公用常量
*
* @author: 云裂痕
* @date: 2020/11/17
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface StringPoolConstant {
String AMPERSAND = "&";
String AND = "and";
String AT = "@";
String ASTERISK = "*";
String STAR = ASTERISK;
String BACK_SLASH = "\\";
String COLON = ":";
String COMMA = ",";
String DASH = "-";
String DOLLAR = "$";
String DOT = ".";
String DOTDOT = "..";
String DOT_CLASS = ".class";
String DOT_JAVA = ".java";
String DOT_XML = ".xml";
String EMPTY = "";
String EQUALS = "=";
String FALSE = "false";
String SLASH = "/";
String HASH = "#";
String HAT = "^";
String LEFT_BRACE = "{";
String LEFT_BRACKET = "(";
String LEFT_CHEV = "<";
String NEWLINE = "\n";
String N = "n";
String NO = "no";
String NULL = "null";
String OFF = "off";
String ON = "on";
String PERCENT = "%";
String PIPE = "|";
String PLUS = "+";
String QUESTION_MARK = "?";
String EXCLAMATION_MARK = "!";
String QUOTE = "\"";
String RETURN = "\r";
String TAB = "\t";
String RIGHT_BRACE = "}";
String RIGHT_BRACKET = ")";
String RIGHT_CHEV = ">";
String SEMICOLON = ";";
String SINGLE_QUOTE = "'";
String BACKTICK = "`";
String SPACE = " ";
String TILDA = "~";
String LEFT_SQ_BRACKET = "[";
String RIGHT_SQ_BRACKET = "]";
String TRUE = "true";
String UNDERSCORE = "_";
String Y = "y";
String YES = "yes";
String ONE = "1";
String ZERO = "0";
String DOLLAR_LEFT_BRACE = "${";
String HASH_LEFT_BRACE = "#{";
String CRLF = "\r\n";
String HTML_NBSP = "&nbsp;";
String HTML_AMP = "&amp";
String HTML_QUOTE = "&quot;";
String HTML_LT = "&lt;";
String HTML_GT = "&gt;";
// ---------------------------------------------------------------- array
String[] EMPTY_ARRAY = new String[0];
String PAGE = "page";
String LIMIT = "limit";
String HTTPS = "https://";
String SUCCESS = "SUCCESS";
String ERROR = "ERROR";
String APP_NAME = "appName";
String APP_ID = "appId";
String UNDEFINED = "undefined";
String RSA2 = "RSA2";
String JSON = "json";
String CORE_PREFIX = "99";
String MINUS_ONE = "-1";
String MD5 = "md5";
String KEY = "key";
String OK = "OK";
String RESULT_CODE = "result_code";
}
@@ -1,29 +0,0 @@
package com.hula.ai.common.constant;
/**
* 系统配置常量
*
* @author: 云裂痕
* @date: 2023/01/31
* @version: 1.0.0
* 得其道
* 乾乾
*/
public interface SysConfigConstants {
/**
* 验证码开关
*/
String CAPTCHA_ON_OFF = "sys.account.captchaOnOff";
/**
* 注册开关
*/
String REGISTER_ON_OFF = "sys.account.registerUser";
/**
* 是否开启同时登录
*/
String ALL_LOGIN = "sys.account.allLogin";
}
@@ -0,0 +1,42 @@
package com.hula.ai.common.constants;
import com.hula.ai.common.pojo.ErrorCode;
/**
* 全局错误码枚举
* 0-999 系统异常编码保留
*
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*
* @author 芋道源码
*/
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(0, "成功");
// ========== 客户端错误段 ==========
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
// ========== 服务端错误段 ==========
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
// ========== 自定义错误段 ==========
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
}
@@ -1,35 +0,0 @@
package com.hula.ai.common.converter;
import com.github.dozermapper.core.DozerConverter;
import com.hula.ai.common.constant.StringPoolConstant;
import java.math.BigDecimal;
/**
* BigDecimal数据类型转String
*
* @author: 云裂痕
* @date: 2023/1/15
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class BigDecimalToStringConverter extends DozerConverter<BigDecimal, String> {
public BigDecimalToStringConverter() {
super(BigDecimal.class, String.class);
}
@Override
public String convertTo(BigDecimal a1, String a2) {
return a1 == null ? StringPoolConstant.ZERO : a1.toPlainString();
}
@Override
public BigDecimal convertFrom(String a1, BigDecimal a2) {
return null;
}
}
@@ -1,31 +0,0 @@
package com.hula.ai.common.converter;
import com.github.dozermapper.core.DozerConverter;
import java.math.BigDecimal;
/**
* Double数据类型转BigDecimal
*
* @author: 云裂痕
* @date: 2023/1/15
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class DoubleToBigDecimalConverter extends DozerConverter<Double, BigDecimal> {
public DoubleToBigDecimalConverter() {
super(Double.class, BigDecimal.class);
}
@Override
public BigDecimal convertTo(Double a1, BigDecimal a2) {
a1 = a1 == null ? 0.0 : a1;
return new BigDecimal(a1);
}
@Override
public Double convertFrom(BigDecimal a1, Double a2) {
return null;
}
}
@@ -1,30 +0,0 @@
package com.hula.ai.common.converter;
import com.github.dozermapper.core.DozerConverter;
/**
* 转为数值类型处理
*
* @author: 云裂痕
* @date: 2023/1/15
* @version: 1.0.0
* 得其道 乾乾
*/
public class ToNumberConverter extends DozerConverter<Object, Object> {
public ToNumberConverter() {
super(Object.class, Object.class);
}
@Override
public Object convertTo(Object a1, Object a2) {
return a1 instanceof Number ? (Number) a1 : a1;
}
@Override
public Object convertFrom(Object a1, Object a2) {
return a1 instanceof Number ? (Number) a1 : a1;
}
}
@@ -0,0 +1,22 @@
package com.hula.ai.common.dict;
import com.hula.ai.common.dict.dto.DictDataRespDTO;
import java.util.List;
/**
* 字典数据 API 接口
*
*/
public interface DictDataCommonApi {
/**
* 获得指定字典类型的字典数据列表
*
* @param dictType 字典类型
* @return 字典数据列表
*/
List<DictDataRespDTO> getDictDataList(String dictType);
}
@@ -0,0 +1,32 @@
package com.hula.ai.common.dict.dto;
import lombok.Data;
/**
* 字典数据 Response DTO
*
* @author 芋道源码
*/
@Data
public class DictDataRespDTO {
/**
* 字典标签
*/
private String label;
/**
* 字典值
*/
private String value;
/**
* 字典类型
*/
private String dictType;
/**
* 状态
*
* 枚举 {@link com.hula.ai.enums.CommonStatusEnum}
*/
private Integer status;
}
@@ -1,70 +0,0 @@
package com.hula.ai.common.enums;
import lombok.Getter;
/**
* 数字枚举
*
* @author: 云裂痕
* @date: 2020/10/21
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum IntEnum {
/**
* 数值
*/
MINUS_ONE(-1),
ZERO(0),
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
TEN(10),
ELEVEN(11),
TWELVE(12),
THIRTEEN(13),
FOURTEEN(14),
FIFTEEN(15),
SIXTEEN(16),
SEVENTEEN(17),
EIGHTEEN(18),
NINETEEN(19),
TWENTY(20);
private final int value;
IntEnum(final int value) {
this.value = value;
}
}
@@ -1,70 +0,0 @@
package com.hula.ai.common.enums;
import lombok.Getter;
/**
* 数字枚举
*
* @author: 云裂痕
* @date: 2020/10/21
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum IntegerEnum {
/**
* 数值
*/
MINUS_ONE(-1),
ZERO(0),
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
TEN(10),
ELEVEN(11),
TWELVE(12),
TTHIRTEEN(13),
FOURTEEN(14),
FIFTEEN(15),
SIXTEEN(16),
SEVENTEEN(17),
EIGHTEEN(18),
NINETEEN(19),
TWENTY(20);
private final Integer value;
IntegerEnum(final Integer value) {
this.value = value;
}
}
@@ -1,45 +0,0 @@
package com.hula.ai.common.enums;
import lombok.Getter;
/**
* 运行操作系统枚举
*
* @author: 云裂痕
* @date: 2023/3/22 10:27
* @version: 1.0.0
* 得其道 乾乾
*/
@Getter
public enum OperateSystemEnum {
/**
* 操作系统简称
*/
Any("any"),
Linux("Linux"),
Mac_OS("Mac OS"),
Mac_OS_X("Mac OS X"),
Windows("Windows"),
OS2("OS/2"),
Solaris("Solaris"),
SunOS("SunOS"),
MPEiX("MPE/iX"),
HP_UX("HP-UX"),
AIX("AIX"),
OS390("OS/390"),
FreeBSD("FreeBSD"),
Irix("Irix"),
Digital_Unix("Digital Unix"),
NetWare_411("NetWare"),
OSF1("OSF1"),
OpenVMS("OpenVMS"),
Others("Others");
private final String value;
private OperateSystemEnum(final String value) {
this.value = value;
}
}
@@ -1,67 +0,0 @@
package com.hula.ai.common.enums;
import lombok.Getter;
/**
* 全局返回参数
*
* @author: 云裂痕
* @date: 2020/8/6
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Getter
public enum ResponseEnum {
/**
* 返回内容
*/
SUCCESS(200, "操作成功"),
SUCCESS_NODATE(204, "操作成功"),
BAD_REQUEST(400, "参数错误"),
PROHIBIT_VISIT(401, "账号认证失败,请重新登录"),
PERMISSION_DENIED(403, "权限不足,无法操作"),
RESOURCES_ERROR(404, "访问资源丢失,请升级相应版本或检查请求路径"),
SING_ERROR(406, "签名验证失败"),
REQUEST_METHOD_ERROR(407, "啊哟,请求方式错了哦,请确认API请求方式GET/POST/PUT/DELETE"),
CONNECT_TIME_OUT(408, "连接超时啦,请稍后再试哦"),
TOO_MANY_REQUESTS(429, "请求未受理,请降低频率后重试"),
ERROR(500, "服务器繁忙,请稍后再试。"),
BUSINESS_ERROR(600, "业务出错,请联系客服"),
SYSTEM_WARNING(601, "系统预警,请及时处理"),
/**
* 内部错误码
*/
NO_LOGIN(4011, "未登录"),
ACCOUNT_LOGIN_EXIST(4011, "该账号已在其他地方登录,请重新登录"),
ACCOUNT_NOT_EXIST(4012, "账号不存在,请先注册或联系管理员创建"),
ACCOUNT_IS_DISABLED(4013, "账号已被禁用,请联系管理员"),
NAME_IS_EXIST(4014, "账号已存在,请核对账号信息"),
TEL_IS_EXIST(4015, "该手机号已经注册,请直接登录"),
PHONE_BINDING(4016, "请先绑定手机号"),
PASSWORD_ERROR(4017, "账号密码错误"),
SMS_ERROR(4018, "验证码错误"),
REPEAT_REQUEST_SMS(4019, "验证码有限期为5分钟,无需重复获取"),
FILE_ERROR(6010, "文件处理失败,请稍后再试");
private final Integer code;
private final String msg;
ResponseEnum(final Integer code, final String msg) {
this.code = code;
this.msg = msg;
}
}
@@ -1,32 +0,0 @@
package com.hula.ai.common.enums;
import lombok.Getter;
/**
* 状态枚举
*
* @author: 云裂痕
* @date: 2020/8/6
* @version: 1.2.8
* 得其道 乾乾
*/
@Getter
public enum StatusEnum {
/**
* 禁用
*/
DISABLED(0),
/**
* 启用
*/
ENABLED(1);
private final Integer value;
StatusEnum(final Integer value) {
this.value = value;
}
}
@@ -1,33 +0,0 @@
package com.hula.ai.common.exception;
import com.hula.ai.common.enums.ResponseEnum;
import lombok.Data;
/**
* 文件处理异常
*
* @author: 云裂痕
* @date: 2020/3/4
* @version: 3.0.0
* 得其道
*/
@Data
public class FileException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String msg;
public FileException() {
this.code = ResponseEnum.FILE_ERROR.getCode();
this.msg = ResponseEnum.FILE_ERROR.getMsg();
}
public FileException(String msg) {
super(msg);
this.code = ResponseEnum.FILE_ERROR.getCode();
this.msg = msg;
}
}
@@ -1,34 +0,0 @@
package com.hula.ai.common.exception;
import com.hula.ai.common.enums.ResponseEnum;
import lombok.Data;
/**
* 数据库更新失败手动抛出异常
*
* @author: 云裂痕
* @date: 2020/3/4
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class UpdateFailedException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String msg;
public UpdateFailedException() {
this.code = ResponseEnum.ERROR.getCode();
this.msg = "数据更新失败,请稍后重新尝试";
}
public UpdateFailedException(String msg) {
super(msg);
this.code = ResponseEnum.ERROR.getCode();
this.msg = msg;
}
}
@@ -1,34 +0,0 @@
package com.hula.ai.common.exception;
import com.hula.ai.common.enums.ResponseEnum;
import lombok.Data;
/**
* 参数验证失败异常
*
* @author: 云裂痕
* @date: 2020/3/4
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class ValidateException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private String msg;
public ValidateException() {
this.code = ResponseEnum.BAD_REQUEST.getCode();
this.msg = ResponseEnum.BAD_REQUEST.getMsg();
}
public ValidateException(String msg) {
super(msg);
this.code = ResponseEnum.BAD_REQUEST.getCode();
this.msg = msg;
}
}
@@ -0,0 +1 @@
package com.hula.ai.common;
@@ -0,0 +1,103 @@
package com.hula.ai.common.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.hula.ai.common.constants.GlobalErrorCodeConstants;
import com.hula.ai.exception.ServiceException;
import com.hula.domain.vo.res.ApiResult;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
/**
* 通用返回
*
* @param <T> 数据泛型
*/
@Data
public class CommonResult<T> implements Serializable {
/**
* 错误码
*
* @see ErrorCode#getCode()
*/
private Integer code;
/**
* 返回数据
*/
private T data;
/**
* 错误提示,用户可阅读
*
* @see ErrorCode#getMsg() ()
*/
private String msg;
/**
* 将传入的 result 对象,转换成另外一个泛型结果的对象
*
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> ApiResult<T> error(CommonResult<?> result) {
return error(result.getCode(), result.getMsg());
}
public static <T> ApiResult<T> error(Integer code, String message) {
return ApiResult.fail(code, message);
}
public static <T> ApiResult<T> error(ErrorCode errorCode) {
return ApiResult.fail(errorCode.getCode(), errorCode.getMsg());
}
public static <T> ApiResult<T> success(T data) {
return ApiResult.success(data);
}
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore // 避免 jackson 序列化
public boolean isSuccess() {
return isSuccess(code);
}
@JsonIgnore // 避免 jackson 序列化
public boolean isError() {
return !isSuccess();
}
// ========= 和 Exception 异常体系集成 =========
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
*/
public void checkError() throws ServiceException {
if (isSuccess()) {
return;
}
// 业务异常
throw new ServiceException(code, msg);
}
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
* 如果没有,则返回 {@link #data} 数据
*/
@JsonIgnore // 避免 jackson 序列化
public T getCheckedData() {
checkError();
return data;
}
public static <T> ApiResult<Object> error(ServiceException serviceException) {
return error(serviceException.getCode(), serviceException.getMessage());
}
}
@@ -0,0 +1,30 @@
package com.hula.ai.common.pojo;
import lombok.Data;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999], 参见 {@link com.hula.ai.common.constants.GlobalErrorCodeConstants}
* 业务异常错误码,占用 [1 000 000 000, +∞),参见
*
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
*/
@Data
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String msg;
public ErrorCode(Integer code, String message) {
this.code = code;
this.msg = message;
}
}
@@ -0,0 +1,36 @@
package com.hula.ai.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
@Schema(description="分页参数")
@Data
public class PageParam implements Serializable {
private static final Integer PAGE_NO = 1;
private static final Integer PAGE_SIZE = 10;
/**
* 每页条数 - 不分页
*
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
*/
public static final Integer PAGE_SIZE_NONE = -1;
@Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为 1")
private Integer pageNo = PAGE_NO;
@Schema(description = "每页条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "每页条数不能为空")
@Min(value = 1, message = "每页条数最小值为 1")
@Max(value = 100, message = "每页条数最大值为 100")
private Integer pageSize = PAGE_SIZE;
}
@@ -0,0 +1,41 @@
package com.hula.ai.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "分页结果")
@Data
public final class PageResult<T> implements Serializable {
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<T> list;
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
private Long total;
public PageResult() {
}
public PageResult(List<T> list, Long total) {
this.list = list;
this.total = total;
}
public PageResult(Long total) {
this.list = new ArrayList<>();
this.total = total;
}
public static <T> PageResult<T> empty() {
return new PageResult<>(0L);
}
public static <T> PageResult<T> empty(Long total) {
return new PageResult<>(total);
}
}
@@ -0,0 +1,19 @@
package com.hula.ai.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Schema(description = "可排序的分页参数")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SortablePageParam extends PageParam {
@Schema(description = "排序字段")
private List<SortingField> sortingFields;
}
@@ -0,0 +1,37 @@
package com.hula.ai.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 排序字段 DTO
*
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SortingField implements Serializable {
/**
* 顺序 - 升序
*/
public static final String ORDER_ASC = "asc";
/**
* 顺序 - 降序
*/
public static final String ORDER_DESC = "desc";
/**
* 字段
*/
private String field;
/**
* 顺序
*/
private String order;
}
@@ -1,778 +0,0 @@
package com.hula.ai.common.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 日期工具类
*
* @author: 云裂痕
* @date: 2019/8/29
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class DateUtil {
/**
* 自定义格式化
*/
public static final String YEAR_MONTH_FORMATTER = "yyyy-MM";
public static final String DATE_FORMATTER = "yyyy-MM-dd";
public static final String TIME_MIN_FORMATTER = "HH:mm";
public static final String TIME_FORMATTER = "HH:mm:ss";
public static final String DATETIME_FORMATTER = "yyyy-MM-dd HH:mm:ss";
public static final String DATETIME_ZONE_FORMATTER = "yyyy-MM-dd'T'HH:mm:ssXXX";
public static final String DATE_FORMATTER_SLASH = "yyyy/MM/dd";
public static final String DATE_FORMATTER_SHORT = "yyyyMMdd";
public static final String YEAR_MONTH_FORMATTER_SHORT = "yyyyMM";
public static final String TIME_FORMATTER_SHORT = "HHmmss";
public static final String DATETIME_FORMATTER_SHORT = "yyyyMMddHHmmss";
public final static String YEAR_MONTH_CHINESE_FORMATTER = "yyyy年MM月";
public final static String DATE_CHINESE_FORMATTER = "yyyy年MM月dd日";
public final static String DATE_TIME_CHINESE_FORMATTER = "yyyy年MM月dd日 HH时mm分ss秒";
/**
* Mysql 日期格式(yyyy-MM-dd)
*/
public final static String MYSQL_YEAR_MONTH_PATTERN = "%Y-%m";
public final static String MYSQL_DATE_FORMATTER = "%Y-%m-%d";
public final static String MYSQL_DATETIME_PATTERN = "%Y-%m-%d %H:%i:%S";
/**
* 24小时时间正则表达式
*/
public static final String MATCH_TIME_24 = "(([0-1][0-9])|2[0-3]):[0-5][0-9]:[0-5][0-9]";
/**
* 日期正则表达式
*/
public static final String REGEX_DATA = "^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))";
/**
* 日期格式化字符串
*
* @param localDate 日期
* @param pattern 格式化
*/
public static String formatLocalDate(LocalDate localDate, String pattern) {
return localDate.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 时间格式化字符串
*
* @param localTime 时间
* @param pattern 格式化
*/
public static String formatLocalTime(LocalTime localTime, String pattern) {
return localTime.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 日期时间格式化字符串
*
* @param localDateTime 日期时间
* @param pattern 格式化
*/
public static String formatLocalDateTime(LocalDateTime localDateTime, String pattern) {
return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 日期格式化字符串
*
* @param localDate 日期
* @return yyyy-MM-dd
*/
public static String formatLocalDate(LocalDate localDate) {
return formatLocalDate(localDate, DATE_FORMATTER);
}
/**
* 日期格式化字符串
*
* @param localDate 日期
* @return yyyyMMdd
*/
public static String formatLocalDateShort(LocalDate localDate) {
return formatLocalDate(localDate, DATE_FORMATTER_SHORT);
}
/**
* 时间格式化字符串
*
* @param localTime 时间
* @return HH:mm:ss
*/
public static String formatLocalTime(LocalTime localTime) {
return formatLocalTime(localTime, TIME_FORMATTER);
}
/**
* 时间格式化字符串
*
* @param localTime 时间
* @return HHmmss
*/
public static String formatLocalTimeShort(LocalTime localTime) {
return formatLocalTime(localTime, TIME_FORMATTER_SHORT);
}
/**
* 日期时间格式化字符串
*
* @param localDateTime 日期时间
* @return yyyy-MM-dd HH:mm:ss
*/
public static String formatLocalDateTime(LocalDateTime localDateTime) {
return formatLocalDateTime(localDateTime, DATETIME_FORMATTER);
}
/**
* 日期时间格式化字符串
*
* @param localDateTime 日期时间
* @return yyyyMMddHHmmss
*/
public static String formatLocalDateTimeShort(LocalDateTime localDateTime) {
return formatLocalDateTime(localDateTime, DATETIME_FORMATTER_SHORT);
}
/**
* 时间戳转日期
*
* @param timestamp 时间戳
* @param pattern 格式化
*/
public static String formatTimestamp(long timestamp, String pattern) {
Instant instant = Instant.ofEpochMilli(timestamp);
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return formatLocalDateTime(localDateTime, pattern);
}
/**
* 时间戳转日期时间
*
* @param timestamp 时间戳
* @return yyyy-MM-dd HH:mm:ss
*/
public static String formatTimestamp(long timestamp) {
return formatTimestamp(timestamp, DATETIME_FORMATTER);
}
/**
* 时间戳转日期时间
*
* @param timestamp 时间戳
* @return yyyyMMddHHmmss
*/
public static String formatTimestampShort(long timestamp) {
return formatTimestamp(timestamp, DATETIME_FORMATTER_SHORT);
}
/**
* 日期字符串转日期
*
* @param date 日期字符串
* @param pattern 格式化
*/
public static LocalDate parseLocalDate(String date, String pattern) {
return LocalDate.parse(date, DateTimeFormatter.ofPattern(pattern));
}
/**
* 时间字符串转时间
*
* @param time 时间字符串
* @param pattern 格式化
*/
public static LocalTime parseLocalTime(String time, String pattern) {
return LocalTime.parse(time, DateTimeFormatter.ofPattern(pattern));
}
/**
* 日期时间字符串转日期时间
*
* @param dateTime 日期时间字符串
* @param pattern 格式化
*/
public static LocalDateTime parseLocalDateTime(String dateTime, String pattern) {
return LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(pattern));
}
/**
* 日期字符串转日期
*
* @param date 日期字符串
*/
public static LocalDate parseLocalDate(String date) {
return LocalDate.parse(date, DateTimeFormatter.ofPattern(DATE_FORMATTER));
}
/**
* 日期时间字符串转日期时间
*
* @param dateTime 日期时间字符串
*/
public static LocalTime parseLocalTime(String time) {
return LocalTime.parse(time, DateTimeFormatter.ofPattern(TIME_FORMATTER));
}
/**
* 日期时间字符串转日期时间
*
* @param dateTime 日期时间字符串
*/
public static LocalDateTime parseLocalDateTime(String dateTime) {
return parseLocalDateTime(dateTime, DATETIME_FORMATTER);
}
/**
* 日期时间字符串转日期时间
*
* @param dateTime 日期时间字符串
*/
public static LocalDateTime parseLocalDateTimeShort(String dateTime) {
return parseLocalDateTime(dateTime, DATETIME_FORMATTER_SHORT);
}
/**
* Date转换成LocalDateTime
*
* @param dateTime 日期时间字符串
* @param pattern 格式化
*/
public static LocalDateTime parseLocalDateTime(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
/**
* LocalDate转换成Date
*
* @param localDate
* @return
*/
public static Date parseDate(LocalDate localDate) {
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
return Date.from(instant);
}
/**
* LocalDateTime转换成Date
*
* @param localDateTime
* @return
*/
public static Date parseDate(LocalDateTime localDateTime) {
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
return Date.from(instant);
}
/**
* 日期时间字符串转日期时间
*
* @param dateTime 日期时间字符串
* @param pattern 格式化
*/
public static Date parseDate(String date, String pattern) {
SimpleDateFormat format = new SimpleDateFormat(pattern);
try {
return format.parse(date);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
/**
* 日期时间字符串转日期时
*
* @param dateTime 日期时间字符串
* @param pattern 格式化
*/
public static Date parseDate(String date) {
return parseDate(date, DATETIME_FORMATTER);
}
/**
* 时间戳转日期
*
* @param date 日期
* @param pattern 格式化
*/
public static String formatDate(Date date, String pattern) {
return formatTimestamp(date.getTime(), pattern);
}
/**
* 日期格式化字符串
*
* @param date 日期
* @return yyyy-MM-dd HH:mm:ss
*/
public static String formatDate(Date date) {
return formatDate(date, DATETIME_FORMATTER);
}
/**
* 日期转时间戳
*
* @param localDateTime 日期
*/
public static long localDateTimeToTimestamp(LocalDateTime localDateTime) {
return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
/**
* 时间戳转日期
*
* @param timestamp 时间戳
*/
public static LocalDateTime timestampToLocalDateTime(long timestamp) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
}
/**
* 获取当前时间戳
*
* @return timestamp
*/
public static long getTimestamp() {
return Instant.now().toEpochMilli();
}
/**
* 获取当前年份
*
* @return yyyy
*/
public static int getCurrentYear() {
return LocalDate.now().getYear();
}
/**
* 获取当前日期
*
* @param pattern 格式化
*/
public static String getCurrentDate(String pattern) {
return formatLocalDate(LocalDate.now(), pattern);
}
/**
* 获取当前时间
*
* @param pattern 格式化
*/
public static String getCurrentTime(String pattern) {
return formatLocalTime(LocalTime.now(), pattern);
}
/**
* 获取当前日期时间
*
* @param pattern 格式化
*/
public static String getCurrentDateTime(String pattern) {
return formatLocalDateTime(LocalDateTime.now(), pattern);
}
/**
* 获取当前年月
*
* @return yyyy-MM
*/
public static String getCurrentYearMonth() {
return getCurrentDate(YEAR_MONTH_FORMATTER);
}
/**
* 获取当前日期
*
* @return yyyy-MM-dd
*/
public static String getCurrentDate() {
return getCurrentDate(DATE_FORMATTER);
}
/**
* 获取当前时间
*
* @return HH:mm:ss
*/
public static String getCurrentTime() {
return getCurrentTime(TIME_FORMATTER);
}
/**
* 获取当前日期时间
*
* @return yyyy-MM-dd HH:mm:ss
*/
public static String getCurrentDateTime() {
return getCurrentDateTime(DATETIME_FORMATTER);
}
/**
* 获取当前年月
*
* @return yyyyMM
*/
public static String getCurrentYearMonthShort() {
return getCurrentDate(YEAR_MONTH_FORMATTER_SHORT);
}
/**
* 获取当前日期
*
* @return yyyyMMdd
*/
public static String getCurrentDateShort() {
return getCurrentDate(DATE_FORMATTER_SHORT);
}
/**
* 获取当前时间
*
* @return HHmmss
*/
public static String getCurrentTimeShort() {
return getCurrentTime(TIME_FORMATTER_SHORT);
}
/**
* 获取当前日期时间
*
* @return yyyyMMddHHmmss
*/
public static String getCurrentDateTimeShort() {
return getCurrentDateTime(DATETIME_FORMATTER_SHORT);
}
/**
* 获取当前星期
*
* @return 星期一、星期二、星期三、星期四、星期五、星期六、星期日
*/
public static String getCurrentWeek(int week) {
String[] strings = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
List<String> list = Arrays.asList(strings);
return list.get(week - 1);
}
/**
* 获取当前星期
*
* @return 1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六、7:星期日
*/
public static int getCurrentWeek() {
return LocalDate.now().getDayOfWeek().getValue();
}
/**
* 获取指定日期的星期
*
* @param date 日期字符串
* @param pattern 格式化
*/
public static int getWeek(String date, String pattern) {
return parseLocalDate(date, pattern).getDayOfWeek().getValue();
}
/**
* 获取当前星期
*
* @param localDate 日期
*/
public static int getWeek(LocalDate localDate) {
return localDate.getDayOfWeek().getValue();
}
/**
* 获取本周第一天
*/
public static LocalDate getCurrentWeekFirstDate() {
return LocalDate.now().minusWeeks(0).with(DayOfWeek.MONDAY);
}
/**
* 获取本周最后一天
*/
public static LocalDate getCurrentWeekLastDate() {
return LocalDate.now().minusWeeks(0).with(DayOfWeek.SUNDAY);
}
/**
* 获取指定周第一天
*
* @param localDate 日期
*/
public static LocalDate getWeekFirstDate(LocalDate localDate) {
return localDate.minusWeeks(0).with(DayOfWeek.MONDAY);
}
/**
* 获取指定周最后一天
*
* @param localDate 日期
*/
public static LocalDate getWeekLastDate(LocalDate localDate) {
return localDate.minusWeeks(0).with(DayOfWeek.SUNDAY);
}
/**
* 获取指定周第一天
*
* @param date 日期字符串
* @param pattern 格式化
*/
public static LocalDate getWeekFirstDate(String date, String pattern) {
return parseLocalDate(date, pattern).minusWeeks(0).with(DayOfWeek.MONDAY);
}
/**
* 获取指定周最后一天
*
* @param date 日期字符串
* @param pattern 格式化
*/
public static LocalDate getWeekLastDate(String date, String pattern) {
return parseLocalDate(date, pattern).minusWeeks(0).with(DayOfWeek.SUNDAY);
}
/**
* 获取本月第一天
*/
public static LocalDate getCurrentMonthFirstDate() {
return LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
}
/**
* 获取本月最后一天
*/
public static LocalDate getCurrentMonthLastDate() {
return LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
}
/**
* 获取指定月份月第一天
*
* @param date 日期字符串
* @param pattern 格式化
*/
public static LocalDate getMonthFirstDate(String date, String pattern) {
return parseLocalDate(date, pattern).with(TemporalAdjusters.firstDayOfMonth());
}
/**
* 获取指定月份月第一天
*
* @param localDate
*/
public static LocalDate getMonthFirstDate(LocalDate localDate) {
return localDate.with(TemporalAdjusters.firstDayOfMonth());
}
/**
* 获取下一天日期
*
* @param pattern 格式化
*/
public static String getNextDate(String pattern) {
return LocalDate.now().plusDays(1).format(DateTimeFormatter.ofPattern(pattern));
}
/**
* 获取下一天日期
*
* @return yyyy-MM-dd
*/
public static String getNextDate() {
return getNextDate(DATE_FORMATTER);
}
/**
* 获取下一天日期
*
* @return yyyy-MM-dd
*/
public static String getNextDateShort() {
return getNextDate(DATE_FORMATTER_SHORT);
}
/**
* 计算距今天指定天数的日期
*
* @param days
* @return
*/
public static String getDateAfterDays(int days) {
Calendar date = Calendar.getInstance();
date.add(Calendar.DATE, days);
SimpleDateFormat simpleDate = new SimpleDateFormat(DATE_FORMATTER);
return simpleDate.format(date.getTime());
}
/**
* 在指定的日期的前几天或后几天
*
* @param source 源日期(yyyy-MM-dd)
* @param days 指定的天数,正负皆可
* @return
* @throws ParseException
*/
public static String addDays(String source, int days) {
Date date = parseDate(parseLocalDate(source, DATE_FORMATTER));
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DATE, days);
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATTER);
return dateFormat.format(calendar.getTime());
}
/**
* 日期相隔天数
*
* @param startLocalDate 起日期
* @param endLocalDate 止日期
*/
public static long intervalDays(LocalDate startLocalDate, LocalDate endLocalDate) {
return endLocalDate.toEpochDay() - startLocalDate.toEpochDay();
}
/**
* 日期相隔小时
*
* @param startLocalDateTime 起日期时间
* @param endLocalDateTime 止日期时间
*/
public static long intervalHours(LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime) {
return Duration.between(startLocalDateTime, endLocalDateTime).toHours();
}
/**
* 日期相隔分钟
*
* @param startLocalDateTime 起日期时间
* @param endLocalDateTime 止日期时间
*/
public static long intervalMinutes(LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime) {
return Duration.between(startLocalDateTime, endLocalDateTime).toMinutes();
}
/**
* 日期相隔毫秒数
*
* @param startLocalDateTime 起日期时间
* @param endLocalDateTime 止日期时间
*/
public static long intervalMillis(LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime) {
return Duration.between(startLocalDateTime, endLocalDateTime).toMillis();
}
/**
* 相隔时间字符串
*/
public static String intervalTimes(LocalDateTime startDate, LocalDateTime endDate) {
return intervalDays(startDate.toLocalDate(), endDate.toLocalDate()) + "" + intervalHours(startDate, endDate) + "小时" + intervalMinutes(startDate, endDate) + "分钟";
}
/**
* 获取此日期时间与默认时区<Asia/Shanghai>组合的时间毫秒数
*
* @param localDateTime 日期时间
*/
public static Long toEpochMilli(LocalDateTime localDateTime) {
return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
/**
* 获取此日期时间与指定时区组合的时间毫秒数
*
* @param localDateTime 日期时间
*/
public static Long toSelectEpochMilli(LocalDateTime localDateTime, ZoneId zoneId) {
return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
}
/**
* 当前是否闰年
*/
public static boolean isCurrentLeapYear() {
return LocalDate.now().isLeapYear();
}
/**
* 是否闰年
*/
public static boolean isLeapYear(LocalDate localDate) {
return localDate.isLeapYear();
}
/**
* 是否当天
*
* @param localDate 日期
*/
public static boolean isToday(LocalDate localDate) {
return LocalDate.now().equals(localDate);
}
/**
* 24小时时间校验
*
* @param time
* @return
*/
public static boolean isValidate24(String time) {
Pattern p = Pattern.compile(MATCH_TIME_24);
return p.matcher(time).matches();
}
/**
* 日期校验
*
* @param date
* @return
*/
public static boolean isDate(String date) {
Pattern pat = Pattern.compile(REGEX_DATA);
Matcher mat = pat.matcher(date);
return mat.matches();
}
/**
* 判断当前时间是否在指定时间范围
*
* @param from 开始时间
* @param to 结束时间
* @return 结果
*/
public static boolean between(LocalTime from, LocalTime to) {
LocalTime now = LocalTime.now();
return now.isAfter(from) && now.isBefore(to);
}
}
@@ -1,182 +0,0 @@
package com.hula.ai.common.utils;
import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Java Bean 转换工具类
* 默认转换类
*
* @author: 云裂痕
* @date: 2020/12/24
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class DozerUtil {
/**
* 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
*/
private static Mapper MAPPER = DozerBeanMapperBuilder.buildDefault();
public static void init(Mapper mapper) {
DozerUtil.MAPPER = mapper;
}
/**
* List 实体类 转换器
*
* @param source 原数据
* @param clz 转换类型
* @param <T>
* @return
*/
public static <T, S> List<T> convertor(List<S> source, Class<T> clz) {
if (source == null) {
return null;
}
List<T> list = new ArrayList<>();
for (S s : source) {
list.add(MAPPER.map(s, clz));
}
return list;
}
/**
* List 实体类 深度转换器
*
* @param source 原数据
* @param clz 转换类型
* @param mapId 自定义转换
* @return
*/
public static <T, S> List<T> convertor(List<S> source, Class<T> clz, String mapId) {
if (source == null) {
return null;
}
List<T> list = new ArrayList<>();
for (S s : source) {
list.add(MAPPER.map(s, clz, mapId));
}
return list;
}
/**
* Set 实体类 转换器
*
* @param source 原数据
* @param clz 目标对象
* @return
*/
public static <T, S> Set<T> convertor(Set<S> source, Class<T> clz) {
if (source == null) {
return null;
}
Set<T> set = new TreeSet<>();
for (S s : source) {
set.add(MAPPER.map(s, clz));
}
return set;
}
/**
* Set 实体类 深度转换器
*
* @param source 原数据
* @param clz 目标对象
* @param mapId 自定义转换
* @return
*/
public static <T, S> Set<T> convertor(Set<S> source, Class<T> clz, String mapId) {
if (source == null) {
return null;
}
Set<T> set = new TreeSet<>();
for (S s : source) {
set.add(MAPPER.map(s, clz, mapId));
}
return set;
}
/**
* 实体类 转换器
*
* @param source 原数据
* @param clz 目标对象
* @return
*/
public static <T, S> T convertor(S source, Class<T> clz) {
if (source == null) {
return null;
}
return MAPPER.map(source, clz);
}
/**
* 实体类 深度转换器
*
* @param source 原数据
* @param clz 目标对象
* @param mapId 自定义转换
* @return
*/
public static <T, S> T convertor(S source, Class<T> clz, String mapId) {
if (source == null) {
return null;
}
return MAPPER.map(source, clz, mapId);
}
/**
* 实体类复制
*
* @param source 原数据
* @param object 目标对象
* @return
*/
public static void convertor(Object source, Object object) {
MAPPER.map(source, object);
}
/**
* 实体类深度复制
*
* @param source 原数据
* @param object 目标对象
* @param mapId 自定义转换
* @return
*/
public static void convertor(Object source, Object object, String mapId) {
MAPPER.map(source, object, mapId);
}
/**
* 实体类复制
*
* @param source 原数据
* @param object 目标对象
* @return
*/
public static <T> void copyConvertor(T source, Object object) {
MAPPER.map(source, object);
}
/**
* 实体类深度复制
*
* @param source 原数据
* @param object 目标对象
* @param mapId 自定义转换
* @return
*/
public static <T> void copyConvertor(T source, Object object, String mapId) {
MAPPER.map(source, object, mapId);
}
}
@@ -1,44 +0,0 @@
package com.hula.ai.common.utils;
import com.hula.ai.common.constant.StringPoolConstant;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 错误信息处理类
*
* @author: 云裂痕
* @date: 2023/10/20
* @version: 1.2.0
* 得其道
* 乾乾
*/
public class ExceptionUtil {
/**
* 获取exception的详细错误信息。
*/
public static String getExceptionMessage(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
String str = sw.toString();
return str;
}
public static String getRootErrorMessage(Exception e) {
Throwable root = ExceptionUtils.getRootCause(e);
root = (root == null ? e : root);
if (root == null) {
return StringPoolConstant.EMPTY;
}
String msg = root.getMessage();
if (msg == null) {
return StringPoolConstant.NULL;
}
return StringUtils.defaultString(msg);
}
}
@@ -1,85 +0,0 @@
package com.hula.ai.common.utils;
import com.hula.ai.common.constant.StringPoolConstant;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 填充null值数据
*
* @Author 葛盼
* @date 2020/3/26 16:13
* 得其道
* 乾乾
*/
public class FillNullUtil {
/**
* 要填充的实体类
*
* @param bean
*/
public static void fillNull(Object bean) {
if (bean instanceof Map) {
return;
}
if (bean instanceof List) {
return;
}
List<Field> fieldList = new ArrayList<>();
Class tempClass = bean.getClass();
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
for (Field field : fieldList) {
field.setAccessible(true);
if ("java.lang.String".equals(field.getType().getName())) {
setValue(StringPoolConstant.EMPTY, bean, field);
} else if ("java.lang.Integer".equals(field.getType().getName())) {
setValue(Integer.valueOf(0), bean, field);
} else if ("java.lang.Double".equals(field.getType().getName())) {
setValue(Double.valueOf(0), bean, field);
} else if ("java.lang.Long".equals(field.getType().getName())) {
setValue(Long.valueOf(0), bean, field);
} else if ("java.lang.Boolean".equals(field.getType().getName())) {
setValue(true, bean, field);
} else if ("java.math.BigDecimal".equals(field.getType().getName())) {
setValue(BigDecimal.ZERO, bean, field);
} else if ("java.time.LocalDateTime".equals(field.getType().getName())) {
setValue(LocalDateTime.now(), bean, field);
} else if ("java.util.Date".equals(field.getType().getName())) {
setValue(new Date(), bean, field);
}
}
}
private static void setValue(Object value, Object bean, Field field) {
if ("id".equals(field.getName())) {
return;
}
try {
Object mid = field.get(bean);
if (mid == null) {
field.set(bean, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String nullToEmpty(Object obj) {
if (obj == null) {
return StringPoolConstant.EMPTY;
}
if (obj instanceof String) {
return (String) obj;
}
return obj + StringPoolConstant.EMPTY;
}
}
@@ -1,141 +0,0 @@
package com.hula.ai.common.utils;
import com.hula.ai.common.enums.OperateSystemEnum;
/**
* 获取操作系统工具类
*
* @author: 云裂痕
* @date: 2023/3/22 10:27
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class OperateSystemUtil {
private static String OS = System.getProperty("os.name").toLowerCase();
private static OperateSystemUtil instance = new OperateSystemUtil();
private OperateSystemEnum platform;
private OperateSystemUtil() {
}
public static boolean isLinux() {
return OS.indexOf("linux") >= 0;
}
public static boolean isMacOS() {
return OS.indexOf("mac") >= 0 && OS.indexOf("os") > 0 && OS.indexOf("x") < 0;
}
public static boolean isMacOSX() {
return OS.indexOf("mac") >= 0 && OS.indexOf("os") > 0 && OS.indexOf("x") > 0;
}
public static boolean isWindows() {
return OS.indexOf("windows") >= 0;
}
public static boolean isOS2() {
return OS.indexOf("os/2") >= 0;
}
public static boolean isSolaris() {
return OS.indexOf("solaris") >= 0;
}
public static boolean isSunOS() {
return OS.indexOf("sunos") >= 0;
}
public static boolean isMPEiX() {
return OS.indexOf("mpe/ix") >= 0;
}
public static boolean isHPUX() {
return OS.indexOf("hp-ux") >= 0;
}
public static boolean isAix() {
return OS.indexOf("aix") >= 0;
}
public static boolean isOS390() {
return OS.indexOf("os/390") >= 0;
}
public static boolean isFreeBSD() {
return OS.indexOf("freebsd") >= 0;
}
public static boolean isIrix() {
return OS.indexOf("irix") >= 0;
}
public static boolean isDigitalUnix() {
return OS.indexOf("digital") >= 0 && OS.indexOf("unix") > 0;
}
public static boolean isNetWare() {
return OS.indexOf("netware") >= 0;
}
public static boolean isOSF1() {
return OS.indexOf("osf1") >= 0;
}
public static boolean isOpenVMS() {
return OS.indexOf("openvms") >= 0;
}
/**
* 获取操作系统名字
*
* @return 操作系统名
*/
public static OperateSystemEnum getOSname() {
if (isAix()) {
instance.platform = OperateSystemEnum.AIX;
} else if (isDigitalUnix()) {
instance.platform = OperateSystemEnum.Digital_Unix;
} else if (isFreeBSD()) {
instance.platform = OperateSystemEnum.FreeBSD;
} else if (isHPUX()) {
instance.platform = OperateSystemEnum.HP_UX;
} else if (isIrix()) {
instance.platform = OperateSystemEnum.Irix;
} else if (isLinux()) {
instance.platform = OperateSystemEnum.Linux;
} else if (isMacOS()) {
instance.platform = OperateSystemEnum.Mac_OS;
} else if (isMacOSX()) {
instance.platform = OperateSystemEnum.Mac_OS_X;
} else if (isMPEiX()) {
instance.platform = OperateSystemEnum.MPEiX;
} else if (isNetWare()) {
instance.platform = OperateSystemEnum.NetWare_411;
} else if (isOpenVMS()) {
instance.platform = OperateSystemEnum.OpenVMS;
} else if (isOS2()) {
instance.platform = OperateSystemEnum.OS2;
} else if (isOS390()) {
instance.platform = OperateSystemEnum.OS390;
} else if (isOSF1()) {
instance.platform = OperateSystemEnum.OSF1;
} else if (isSolaris()) {
instance.platform = OperateSystemEnum.Solaris;
} else if (isSunOS()) {
instance.platform = OperateSystemEnum.SunOS;
} else if (isWindows()) {
instance.platform = OperateSystemEnum.Windows;
} else {
instance.platform = OperateSystemEnum.Others;
}
return instance.platform;
}
}
File diff suppressed because one or more lines are too long
@@ -1,81 +0,0 @@
package com.hula.ai.common.utils;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import com.hula.ai.common.enums.IntEnum;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
/**
* 获取雪花算法Id
*
* @author: 云裂痕
* @date: 2020/12/16
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Slf4j
public class SnowFlakeUtil {
private static long workerId = 0;
private static long dataCenterId = 1;
private static int snowflakeLength = 19;
private static Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
@PostConstruct
public void init() {
try {
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
log.info("当前机器的workId: {}", workerId);
} catch (Exception e) {
e.printStackTrace();
log.error("当前机器的workId获取失败", e);
workerId = NetUtil.getLocalhostStr().hashCode();
}
}
public static synchronized long snowflakeId() {
return snowflake.nextId();
}
public static synchronized long snowflakeId(int length) {
long snowflakeId = snowflakeId();
if (length <= IntEnum.ZERO.getValue()) {
return snowflakeId;
}
return Long.valueOf(String.valueOf(snowflakeId).substring(snowflakeLength - length));
}
public static synchronized long snowflakeId(long workerId, long dataCenterId) {
Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
return snowflake.nextId();
}
public static synchronized long snowflakeId(long workerId, long dataCenterId, int length) {
long snowflakeId = snowflakeId(workerId, dataCenterId);
if (length <= IntEnum.ZERO.getValue()) {
return snowflakeId;
}
return Long.valueOf(String.valueOf(snowflakeId).substring(snowflakeLength - length));
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Stream.iterate(0, x -> x + 1).limit(20).
forEach(x -> {
executorService.submit(() -> {
long id = snowflakeId(13);
System.out.println(id);
});
});
}
}
@@ -1,124 +0,0 @@
package com.hula.ai.common.utils;
import com.hula.ai.common.constant.HttpConstant;
import com.hula.ai.common.constant.StringPoolConstant;
import org.apache.commons.lang3.StringUtils;
/**
* 字符串工具类
*
* @author: 云裂痕
* @date: 2020/12/30
* @version: 1.0.0
* 得其道
* 乾乾
*/
public class StringUtil extends StringUtils {
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link) {
return StringUtils.startsWithAny(link, HttpConstant.HTTP, HttpConstant.HTTPS);
}
/**
* 驼峰转下划线命名
*/
public static String toUnderScoreCase(String str) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase;
// 当前字符是否大写
boolean curreCharIsUpperCase;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (i > 0) {
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
} else {
preCharIsUpperCase = false;
}
curreCharIsUpperCase = Character.isUpperCase(c);
if (i < (str.length() - 1)) {
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}
if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
sb.append(StringPoolConstant.UNDERSCORE);
} else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
sb.append(StringPoolConstant.UNDERSCORE);
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String convertToCamelCase(String name) {
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty()) {
// 没必要转换
return StringPoolConstant.EMPTY;
} else if (!name.contains(StringPoolConstant.UNDERSCORE)) {
// 不含下划线,仅将首字母大写
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// 用下划线将原始字符串分割
String[] camels = name.split(StringPoolConstant.UNDERSCORE);
for (String camel : camels) {
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty()) {
continue;
}
// 首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}
/**
* 驼峰式命名法 例如:user_name->userName
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
char under = '_';
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == under) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
}
@@ -1,81 +0,0 @@
package com.hula.ai.common.utils;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* 线程相关工具类
*
* @author: 云裂痕
* @date: 2019/11/9
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Slf4j
public class Threadsutil {
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
return;
}
}
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍人超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool) {
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
try {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
log.info("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
log.error(t.getMessage(), t);
}
}
}
@@ -0,0 +1,242 @@
package com.hula.ai.config;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.hula.ai.core.AiModelFactory;
import com.hula.ai.core.AiModelFactoryImpl;
import com.hula.ai.core.model.*;
import com.hula.ai.core.model.silicon.SiliconFlowApiConstants;
import com.hula.ai.core.model.silicon.SiliconFlowChatModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
import org.springframework.ai.tokenizer.TokenCountEstimator;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
@EnableConfigurationProperties({HulaAiProperties.class,
QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
RedisVectorStoreProperties.class, // 解析 Redis 配置
MilvusVectorStoreProperties.class,
MilvusServiceClientProperties.class // 解析 Milvus 配置
})
@Slf4j
public class AiAutoConfiguration {
@Bean
public AiModelFactory aiModelFactory() {
return new AiModelFactoryImpl();
}
@Bean
@ConditionalOnProperty(value = "hula.ai.deepseek.enable", havingValue = "true")
public DeepSeekChatModel deepSeekChatModel(HulaAiProperties hulaAiProperties) {
HulaAiProperties.DeepSeekProperties properties = hulaAiProperties.getDeepseek();
return buildDeepSeekChatModel(properties);
}
public DeepSeekChatModel buildDeepSeekChatModel(HulaAiProperties.DeepSeekProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(DeepSeekChatModel.BASE_URL)
.apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new DeepSeekChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.doubao.enable", havingValue = "true")
public DouBaoChatModel douBaoChatClient(HulaAiProperties hulaAiProperties) {
HulaAiProperties.DouBaoProperties properties = hulaAiProperties.getDoubao();
return buildDouBaoChatClient(properties);
}
public DouBaoChatModel buildDouBaoChatClient(HulaAiProperties.DouBaoProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(DouBaoChatModel.BASE_URL)
.apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new DouBaoChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.siliconflow.enable", havingValue = "true")
public SiliconFlowChatModel siliconFlowChatClient(HulaAiProperties hulaAiProperties) {
HulaAiProperties.SiliconFlowProperties properties = hulaAiProperties.getSiliconflow();
return buildSiliconFlowChatClient(properties);
}
public SiliconFlowChatModel buildSiliconFlowChatClient(HulaAiProperties.SiliconFlowProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
.apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new SiliconFlowChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.hunyuan.enable", havingValue = "true")
public HunYuanChatModel hunYuanChatClient(HulaAiProperties hulaAiProperties) {
HulaAiProperties.HunYuanProperties properties = hulaAiProperties.getHunyuan();
return buildHunYuanChatClient(properties);
}
public HunYuanChatModel buildHunYuanChatClient(HulaAiProperties.HunYuanProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
}
// 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
if (StrUtil.isEmpty(properties.getBaseUrl())) {
properties.setBaseUrl(
StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
: HunYuanChatModel.BASE_URL);
}
// 创建 OpenAiChatModel、HunYuanChatModel 对象
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(properties.getBaseUrl())
.apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new HunYuanChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.xinghuo.enable", havingValue = "true")
public XingHuoChatModel xingHuoChatClient(HulaAiProperties hulaAiProperties) {
HulaAiProperties.XingHuoProperties properties = hulaAiProperties.getXinghuo();
return buildXingHuoChatClient(properties);
}
public XingHuoChatModel buildXingHuoChatClient(HulaAiProperties.XingHuoProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(XingHuoChatModel.BASE_URL)
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new XingHuoChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.baichuan.enable", havingValue = "true")
public BaiChuanChatModel baiChuanChatClient(HulaAiProperties hulaAiProperties) {
HulaAiProperties.BaiChuanProperties properties = hulaAiProperties.getBaichuan();
return buildBaiChuanChatClient(properties);
}
public BaiChuanChatModel buildBaiChuanChatClient(HulaAiProperties.BaiChuanProperties properties) {
if (StrUtil.isEmpty(properties.getModel())) {
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
}
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl(BaiChuanChatModel.BASE_URL)
.apiKey(properties.getApiKey())
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topP(properties.getTopP())
.build())
.toolCallingManager(getToolCallingManager())
.build();
return new BaiChuanChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "hula.ai.midjourney.enable", havingValue = "true")
public MidjourneyApi midjourneyApi(HulaAiProperties hulaAiProperties) {
HulaAiProperties.MidjourneyProperties config = hulaAiProperties.getMidjourney();
return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl());
}
@Bean
@ConditionalOnProperty(value = "hula.ai.suno.enable", havingValue = "true")
public SunoApi sunoApi(HulaAiProperties hulaAiProperties) {
return new SunoApi(hulaAiProperties.getSuno().getBaseUrl());
}
// ========== RAG 相关 ==========
@Bean
public TokenCountEstimator tokenCountEstimator() {
return new JTokkitTokenCountEstimator();
}
@Bean
public BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
private static ToolCallingManager getToolCallingManager() {
return SpringUtil.getBean(ToolCallingManager.class);
}
}
@@ -0,0 +1,158 @@
package com.hula.ai.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "hula.ai")
@Configuration
@Data
public class HulaAiProperties {
/**
* DeepSeek
*/
@SuppressWarnings("SpellCheckingInspection")
private DeepSeekProperties deepseek;
/**
* 字节豆包
*/
@SuppressWarnings("SpellCheckingInspection")
private DouBaoProperties doubao;
/**
* 腾讯混元
*/
@SuppressWarnings("SpellCheckingInspection")
private HunYuanProperties hunyuan;
/**
* 硅基流动
*/
@SuppressWarnings("SpellCheckingInspection")
private SiliconFlowProperties siliconflow;
/**
* 讯飞星火
*/
@SuppressWarnings("SpellCheckingInspection")
private XingHuoProperties xinghuo;
/**
* 百川
*/
@SuppressWarnings("SpellCheckingInspection")
private BaiChuanProperties baichuan;
/**
* Midjourney 绘图
*/
private MidjourneyProperties midjourney;
/**
* Suno 音乐
*/
@SuppressWarnings("SpellCheckingInspection")
private SunoProperties suno;
@Data
public static class DeepSeekProperties {
private String enable;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class DouBaoProperties {
private String enable;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class HunYuanProperties {
private String enable;
private String baseUrl;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class SiliconFlowProperties {
private String enable;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class XingHuoProperties {
private String enable;
private String appId;
private String appKey;
private String secretKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class BaiChuanProperties {
private String enable;
private String apiKey;
private String model;
private Double temperature;
private Integer maxTokens;
private Double topP;
}
@Data
public static class MidjourneyProperties {
private String enable;
private String baseUrl;
private String apiKey;
private String notifyUrl;
}
@Data
public static class SunoProperties {
private boolean enable = false;
private String baseUrl;
}
}
@@ -1,330 +0,0 @@
package com.hula.ai.config;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.hula.ai.client.enums.ChatModelEnum;
import com.hula.ai.common.constant.AiConstants;
import com.hula.ai.common.constant.StringPoolConstant;
import com.hula.ai.common.enums.IntegerEnum;
import com.hula.ai.config.dto.BaseInfoDTO;
import com.hula.ai.gpt.pojo.vo.OpenkeyVO;
import com.hula.ai.gpt.service.IOpenkeyService;
import com.hula.ai.llm.chatglm.ChatGLMClient;
import com.hula.ai.llm.deepseek.DeepSeekStreamClient;
import com.hula.ai.llm.deepseek.constant.DeepSeekConst;
import com.hula.ai.llm.doubao.DouBaoClient;
import com.hula.ai.llm.internlm.InternlmClient;
import com.hula.ai.llm.locallm.LocalLMClient;
import com.hula.ai.llm.moonshot.MoonshotClient;
import com.hula.ai.llm.openai.OpenAiClient;
import com.hula.ai.llm.openai.OpenAiStreamClient;
import com.hula.ai.llm.openai.function.KeyRandomStrategy;
import com.hula.ai.llm.openai.interceptor.OpenAILogger;
import com.hula.ai.llm.spark.SparkClient;
import com.hula.ai.llm.tongyi.TongYiClient;
import com.hula.ai.llm.wenxin.WenXinClient;
import com.hula.core.user.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 初始化大模型bean
*
* @author: 云裂痕
* @date: 2025/03/07
* 得其道 乾乾
*/
@Slf4j
@Configuration
public class InitBean {
@Autowired
private IOpenkeyService openkeyService;
@Autowired
private ConfigService configService;
@Bean
public OpenAiStreamClient openAiStreamClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.OPENAI.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到ChatGpt模型token数据");
return new OpenAiStreamClient();
}
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
// 如使用代理 请更换为代理地址
//.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)))
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
BaseInfoDTO baseInfo = configService.getBeanByName(AiConstants.BASE_INFO, BaseInfoDTO.class);
String apiHost = null;
if (baseInfo.getProxyType().equals(IntegerEnum.THREE.getValue()) && StrUtil.isEmpty((baseInfo.getProxyAddress()))) {
if (!baseInfo.getProxyAddress().contains(StringPoolConstant.COLON)) {
log.error("代理地址错误");
return new OpenAiStreamClient();
}
String[] proxyAddress = baseInfo.getProxyAddress().split(StringPoolConstant.COLON);
okHttpClient = new OkHttpClient
.Builder()
// 如使用代理 请更换为代理地址
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress[0], Integer.valueOf(proxyAddress[1]))))
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
} else if (baseInfo.getProxyType().equals(IntegerEnum.TWO.getValue()) && StrUtil.isEmpty((baseInfo.getProxyServer()))) {
apiHost = baseInfo.getProxyServer();
}
return OpenAiStreamClient
.builder()
.apiHost(apiHost)
.apiKey(openkeys.stream().map(v -> v.getAppKey()).collect(Collectors.toList()))
//自定义key使用策略 默认随机策略
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
@Bean
public OpenAiClient openAiClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.MOONSHOT.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到ChatGpt模型token数据");
return new OpenAiClient();
}
//本地开发需要配置代理地址
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
// 如使用代理 请更换为代理地址
//.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)))
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
BaseInfoDTO baseInfo = configService.getBeanByName(AiConstants.BASE_INFO, BaseInfoDTO.class);
String apiHost = null;
if (baseInfo.getProxyType().equals(IntegerEnum.THREE.getValue()) && StrUtil.isEmpty((baseInfo.getProxyAddress()))) {
if (!baseInfo.getProxyAddress().contains(StringPoolConstant.COLON)) {
log.error("代理地址错误");
return new OpenAiClient();
}
String[] proxyAddress = baseInfo.getProxyAddress().split(StringPoolConstant.COLON);
okHttpClient = new OkHttpClient
.Builder()
// 如使用代理 请更换为代理地址
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress[0], Integer.valueOf(proxyAddress[1]))))
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
} else if (baseInfo.getProxyType().equals(IntegerEnum.TWO.getValue()) && StrUtil.isNotEmpty(baseInfo.getProxyServer())) {
apiHost = baseInfo.getProxyServer();
}
return OpenAiClient
.builder()
.apiHost(apiHost)
.apiKey(openkeys.stream().map(v -> v.getAppKey()).collect(Collectors.toList()))
//自定义key使用策略 默认随机策略
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
/**
* 文心一言
*
* @return
*/
@Bean
public WenXinClient wenXinClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.WENXIN.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到文心一言模型token数据");
return new WenXinClient();
}
OpenkeyVO openkey = openkeys.get(0);
if (StrUtil.isEmpty(openkey.getAppKey()) || StrUtil.isEmpty(openkey.getAppSecret())) {
log.error("未获取到文心一言模型token数据");
return new WenXinClient();
}
return WenXinClient.builder().logLevel(HttpLoggingInterceptor.Level.BASIC)
.apiKey(openkey.getAppKey()).secretKey(openkey.getAppSecret()).build();
}
/**
* 通义千问
*
* @return
*/
@Bean
public TongYiClient tongYiClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.TONGYI.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到通义千问模型token数据");
return new TongYiClient();
}
OpenkeyVO openkey = openkeys.get(0);
return new TongYiClient(openkey.getAppKey());
}
/**
* 讯飞星火
*
* @return
*/
@Bean
public SparkClient sparkClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.SPARK.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到智谱清言模型token数据,请添加后需要重启系统");
return new SparkClient();
}
OpenkeyVO openkey = openkeys.get(0);
return new SparkClient(openkey.getAppId(), openkey.getAppKey(), openkey.getAppSecret());
}
/**
* 智谱清言
*
* @return
*/
@Bean
public ChatGLMClient chatGLMClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.CHATGLM.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到智谱清言模型token数据,请添加后需要重启系统");
return new ChatGLMClient();
}
OpenkeyVO openkey = openkeys.get(0);
if (StrUtil.isEmpty(openkey.getAppKey())) {
log.error("未获取到智谱清言模型token数据");
return new ChatGLMClient();
}
return ChatGLMClient.builder().appKey(openkey.getAppKey()).appSecret(openkey.getAppSecret()).apiSecretKey(openkey.getAppKey()).build();
}
/**
* 月之暗面
*
* @return
*/
@Bean
public MoonshotClient moonshotClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.MOONSHOT.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到月之暗面模型token数据,请添加后需要重启系统");
return new MoonshotClient();
}
OpenkeyVO openkey = openkeys.get(0);
if (StrUtil.isEmpty((openkey.getAppKey()))) {
log.error("未获取到月之暗面模型token数据");
return new MoonshotClient();
}
return MoonshotClient.builder().apiKey(openkey.getAppKey()).build();
}
/**
* DeepSeek
* @return
*/
@Bean
public DeepSeekStreamClient deepSeekStreamClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.DEEPSEEK.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到DeepSeek模型token数据");
return new DeepSeekStreamClient();
}
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
// 如使用代理 请更换为代理地址
//.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)))
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
return DeepSeekStreamClient
.builder()
.apiHost(DeepSeekConst.HOST)
.apiKey(openkeys.stream().map(v -> v.getAppKey()).collect(Collectors.toList()))
//自定义key使用策略 默认随机策略
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
/**
* 豆包
*
* @return
*/
@Bean
public DouBaoClient douBaoClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.DOUBAO.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到豆包模型token数据,请添加后需要重启系统");
return new DouBaoClient();
}
if (CollUtil.isNotEmpty(openkeys)) {
return DouBaoClient.builder().apiKey(openkeys.get(0).getAppKey()).build();
}
return DouBaoClient.builder().build();
}
/**
* 书生·浦语
*
* @return
*/
@Bean
public InternlmClient internlmClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.INTERNLM.getValue());
if (CollUtil.isEmpty(openkeys)) {
log.error("未加载到书生浦语模型token数据,请添加后需要重启系统");
return new InternlmClient();
}
OpenkeyVO openkey = openkeys.get(0);
if (StrUtil.isEmpty((openkey.getAppKey()))) {
log.error("未获取到书生浦语模型token数据");
return new InternlmClient();
}
return InternlmClient.builder().token(openkey.getAppKey()).build();
}
/**
* LocalLM 本地模型
* 支持Langchain-Chatchat、Ollama、GiteeAI、扣子、FastGPT、LinkAI、Dify
*
* @return
*/
@Bean
public LocalLMClient localLMClient() {
List<OpenkeyVO> openkeys = openkeyService.listOpenkeyByModel(ChatModelEnum.LOCALLM.getValue());
if (CollUtil.isEmpty(openkeys)) {
return LocalLMClient.builder().apiKey(openkeys.get(0).getAppKey()).build();
}
return LocalLMClient.builder().build();
}
}
@@ -0,0 +1,53 @@
package com.hula.ai.config;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List;
@ConfigurationProperties(prefix = "hula.security")
@Configuration
@Validated
@Data
public class SecurityProperties {
/**
* HTTP 请求时,访问令牌的请求 Header
*/
@NotEmpty(message = "Token Header 不能为空")
private String tokenHeader = "Authorization";
/**
* HTTP 请求时,访问令牌的请求参数
*
* 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接
*/
@NotEmpty(message = "Token Parameter 不能为空")
private String tokenParameter = "token";
/**
* mock 模式的开关
*/
@NotNull(message = "mock 模式的开关不能为空")
private Boolean mockEnable = false;
/**
* mock 模式的密钥
* 一定要配置密钥,保证安全性
*/
@NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
private String mockSecret = "test";
/**
* 免登录的 URL 列表
*/
private List<String> permitAllUrls = Collections.emptyList();
/**
* PasswordEncoder 加密复杂度,越高开销越大
*/
private Integer passwordEncoderLength = 4;
}
@@ -1,59 +0,0 @@
package com.hula.ai.config.dto;
import lombok.Data;
import java.io.Serializable;
/**
* APP信息
*
* @author: 云裂痕
* @date: 2025/03/07
* 得其道 乾乾
*/
@Data
public class AppInfoDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否无限制访问GPT
*/
private Integer isGPTLimit;
/**
* 是否开启兑换码
*/
private Integer isRedemption;
/**
* 是否开启短信
*/
private Integer isSms;
/**
* 是否开启分享
*/
private Integer isShare;
/**
* 分享赠送次数
*/
private Integer shareNum;
/**
* 免费体验次数
*/
private Integer freeNum;
/**
* H5地址
*/
private String h5Url;
/**
* 首页公告
*/
private String homeNotice;
}
@@ -1,73 +0,0 @@
package com.hula.ai.config.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 基础信息
*
* @author: 云裂痕
* @date: 2023/5/6
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class BaseInfoDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 内容安全审查接口 0 不检查 1 检查
*/
private Integer contentSecurity;
/**
* 站点名称
*/
private String siteTitle;
/**
* 站点logo
*/
private String siteLogo;
/**
* 代理方案 1 无需代理 2 反向代理 3 本地代理
*/
private Integer proxyType;
/**
* 反代地址
*/
private String proxyServer;
/**
* 本地代理地址
*/
private String proxyAddress;
/**
* 域名
*/
private String domain;
/**
* 站点版权
*/
private String copyright;
/**
* 站点描述
*/
private String descrip;
/**
* 关键词
*/
private List<String> keywords;
}
@@ -1,86 +0,0 @@
package com.hula.ai.config.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 基础信息
*
* @author: 云裂痕
* @date: 2023/5/6
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class ExtraInfoDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* oss类型
*/
private Integer ossType;
/**
* 上传大小限制
*/
private String uploadSize;
/**
* oss区域
*/
private String endpoint;
/**
* oss仓库
*/
private String bucketName;
/**
* osskey
*/
private String ossKeyId;
/**
* oss密钥
*/
private String ossKeySecret;
/**
* 短信类型
*/
private Integer smsType;
/**
* 短信区域id
*/
private String smsRegionId;
/**
* 短信应用id (阿里云sms没有)
*/
private String smsAppId;
/**
* 短信key
*/
private String smsKeyId;
/**
* 短信密钥
*/
private String smsKeySecret;
/**
* 短信签名
*/
private String smsSign;
/**
* 注册模版
*/
private String registerTemplate;
}
@@ -1,96 +0,0 @@
package com.hula.ai.config.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 微信信息
*
* @author: 云裂痕
* @date: 2023/5/6
* @version: 1.0.0
* 得其道
* 乾乾
*/
@Data
public class WxInfoDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否使用小程序
*/
private Integer isWxapp;
/**
* 小程序id
*/
private String wxAppId;
/**
* 小程序密钥
*/
private String wxAppSecret;
/**
* 是否使用公众号
*/
private Integer isMp;
/**
* 公众号id
*/
private String mpAppId;
/**
* 公众号密钥
*/
private String mpSecret;
/**
* 是否开启微信支付
*/
private Integer isWxPay;
/**
* 是否开启服务商支付
*/
private Integer isWxSpPay;
/**
* 服务商AppId
*/
private String spAppId;
/**
* 服务商商户号
*/
private String spMchId;
/**
* 商户号
*/
private String mchId;
/**
* V3密钥
*/
private String apiV3Key;
/**
* 应用私钥
*/
private String privateKey;
/**
* 证书序列号
*/
private String mchSerialNo;
/**
* 支付证书信息
*/
private String certPath;
}
@@ -1,154 +0,0 @@
package com.hula.ai.controller.app;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.hula.ai.client.model.command.ChatCommand;
import com.hula.ai.common.enums.StatusEnum;
import com.hula.ai.gpt.pojo.param.AgreementParam;
import com.hula.ai.gpt.pojo.vo.AgreementVO;
import com.hula.ai.gpt.service.IAgreementService;
import com.hula.ai.gpt.service.IAssistantService;
import com.hula.ai.gpt.service.IAssistantTypeService;
import com.hula.ai.llm.base.service.LLMService;
import com.hula.domain.vo.res.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Random;
/**
* 获取小程序基础信息接口
*
* @author: 云裂痕
* @date: 2023/5/4
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/app/api")
public class AppApiController {
@Autowired
private IAssistantTypeService assistantTypeService;
@Autowired
private IAssistantService assistantService;
@Autowired
private IAgreementService contentService;
@Autowired
private LLMService llmService;
/**
* 获取协议信息
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/content/agreement/{type}")
public ApiResult getAgreement(@PathVariable("type") Integer type) {
List<AgreementVO> contents = contentService.listContent(new AgreementParam(StatusEnum.ENABLED.getValue(), type));
if (CollUtil.isEmpty(contents)) {
return ApiResult.success();
}
return ApiResult.success(contents.get(0));
}
/**
* 获取系统内容信息
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/content/{type}")
public ApiResult listContent(@PathVariable("type") Integer type) {
return ApiResult.success(contentService.listContent(new AgreementParam(StatusEnum.ENABLED.getValue(), type)));
}
/**
* 获取内容详情
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/content/detail/{id}")
public ApiResult getContent(@PathVariable("id") Long id) {
return ApiResult.success(contentService.getContentById(id));
}
/**
* 获取Ai分类
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/assistant/type")
public ApiResult listAssistantType() {
AgreementParam param = new AgreementParam();
param.setStatus(StatusEnum.ENABLED.getValue());
return ApiResult.success(assistantTypeService.listAssistantType(param));
}
/**
* 获取Ai助手
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/assistant")
public ApiResult listAssistantByType(@RequestParam AgreementParam param) {
param.setStatus(StatusEnum.ENABLED.getValue());
return ApiResult.success(assistantService.listAssistantByApp(param));
}
/**
* 获取Ai助手
*
* @author: 云裂痕
* @date: 2023/1/9
* @version: 1.0.0
*/
@GetMapping("/assistant/{id}")
public ApiResult getAssistantById(@PathVariable Long id) {
return assistantService.getAssistantById(id);
}
/**
* 随机获取Ai助手
*
* @author: 乾乾
* @date: 2025/03/07
*/
@GetMapping("/assistant/random")
public ApiResult listAssistant(AgreementParam param) {
param.setStatus(StatusEnum.ENABLED.getValue());
if(ObjectUtil.isNull(param.getSize()) || param.getSize() == 0){
param.setSize(3);
}
param.setCurrent(new Random().nextInt(3));
return ApiResult.success(assistantService.listAssistantByApp(param));
}
/**
* 提问
*
* @author: 云裂痕
* @date: 2025/03/07
*/
@PostMapping("/chat")
public ApiResult sendMessage(@RequestBody ChatCommand command) {
command.setApi(true);
return ApiResult.success(llmService.chat(command));
}
}
@@ -1,199 +0,0 @@
package com.hula.ai.controller.app;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.hula.ai.client.enums.ChatStatusEnum;
import com.hula.ai.client.model.command.ChatCommand;
import com.hula.ai.client.model.command.CompletionsParam;
import com.hula.ai.client.service.GptService;
import com.hula.ai.framework.validator.base.BaseAssert;
import com.hula.ai.gpt.pojo.entity.ChatMessage;
import com.hula.ai.gpt.pojo.param.ChatParam;
import com.hula.ai.gpt.pojo.vo.ChatMessageVO;
import com.hula.ai.gpt.pojo.vo.ChatVO;
import com.hula.ai.gpt.service.IChatMessageService;
import com.hula.ai.gpt.service.IChatService;
import com.hula.ai.llm.base.service.LLMService;
import com.hula.common.domain.vo.res.CursorPageBaseResp;
import com.hula.core.chat.domain.vo.request.ChatMessagePageReq;
import com.hula.domain.vo.res.ApiResult;
import com.hula.utils.RequestHolder;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.awt.*;
import java.util.List;
/**
* 对话接口
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
* 得其道 乾乾
*/
@RestController(value = "ChatAiController")
@RequestMapping("/chat")
public class ChatController {
@Autowired
private IChatService chatService;
@Autowired
private IChatMessageService chatMessageService;
@Autowired
private GptService gptService;
@Autowired
private LLMService llmService;
/**
* 获取聊天列表
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("list")
public ApiResult listChat(@RequestBody ChatParam param) {
param.setUid(RequestHolder.get().getUid());
return ApiResult.success(chatService.listChat(param));
}
/**
* 删除聊天列表
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("/del/{chatNumber}")
public ApiResult<Integer> deleteChat(@PathVariable("chatNumber") String chatNumber) {
return ApiResult.success(chatService.removeChatByChatNumber(chatNumber));
}
/**
* 获取聊天内容列表
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@GetMapping("/message")
public ApiResult<List<ChatMessageVO>> listChatMessage(ChatParam param) {
BaseAssert.isBlankOrNull(param.getChatNumber(), "缺少会话标识");
param.setStatus(ChatStatusEnum.SUCCESS.getValue());
return ApiResult.success(chatMessageService.listChatMessage(param));
}
@GetMapping("/ai/message")
public ApiResult<CursorPageBaseResp<ChatMessage>> getChatMessagePage(@Valid ChatMessagePageReq request) {
return ApiResult.success(chatMessageService.getChatMessagePage(request));
}
/**
* 获取聊天内容
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@GetMapping("/message/{conversationId}")
public ApiResult listChatMessageById(@PathVariable String conversationId) {
return ApiResult.returnResult("获取", ObjectUtil.isNotNull(chatMessageService.getChatByMessageId(conversationId)));
}
/**
* 删除聊天内容
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("/message/{conversationId}")
public ApiResult removeChatMessageById(@PathVariable String conversationId) {
return ApiResult.returnResult("删除", chatMessageService.removeChatMessageByMessageId(conversationId));
}
/**
* 创建对话
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping
public ApiResult<ChatVO> saveChat(@RequestBody ChatCommand command) {
command.setUid(RequestHolder.get().getUid());
return ApiResult.success(chatService.saveSSEChat(command));
}
/**
* 发送消息
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("/message")
public ApiResult sendMessage(@Validated @RequestBody ChatCommand command) {
command.setUid(RequestHolder.get().getUid());
command.setOperater(command.getUid());
command = gptService.validateGptCommand(command);
return ApiResult.success(gptService.chatMessage(command));
}
/**
* 创建sse连接
* @return
*/
@GetMapping("/sse/create")
public SseEmitter createConnect() {
return llmService.createSse(RequestHolder.get().getUid());
}
/**
* 关闭连接
*/
@GetMapping("/sse/close")
public void closeConnect() {
llmService.closeSse(RequestHolder.get().getUid());
}
/**
* 对话响应
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping(value = "/completions", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public void completions(HttpServletResponse response, @RequestBody CompletionsParam completionsParam) {
Boolean isWs = false;
if (StrUtil.isNotEmpty(completionsParam.getWs())) {
isWs = Boolean.valueOf(completionsParam.getWs());
}
llmService.sseChat(response, isWs, RequestHolder.get().getUid(), completionsParam);
}
/**
* 同步响应
*
* @author: 云裂痕
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("/completions/sync")
public ApiResult syncCompletions(@RequestBody ChatCommand command) {
command.setUid(RequestHolder.get().getUid());
return ApiResult.success(llmService.chat(command));
}
}
@@ -1,66 +0,0 @@
package com.hula.ai.controller.app;
import com.alibaba.fastjson.JSONArray;
import com.hula.ai.client.model.command.DeleteFileParam;
import com.hula.ai.client.model.command.FileParam;
import com.hula.ai.client.model.command.UploadParam;
import com.hula.ai.framework.util.file.FileUploadResponse;
import com.hula.ai.llm.base.service.LLMService;
import com.hula.domain.vo.res.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件管理
*
* @author: 云裂痕
* @date: 2025/03/13
* @version: 1.0.0
* 得其道 乾乾
*/
@RestController
@RequestMapping("/file")
public class FileController {
@Autowired
private LLMService llmService;
/**
* 上传文件
*
* @author: 乾乾
*/
@PostMapping("/upload")
public ApiResult<FileUploadResponse> uploadFile(@Validated @ModelAttribute UploadParam param, @RequestParam("file") MultipartFile file) {
return ApiResult.success(llmService.uploadFile(file, param));
}
/**
* 文件列表
*
* @author: 乾乾
*/
@GetMapping("/fileList")
public ApiResult<JSONArray> fileList(@Validated FileParam param) {
return ApiResult.success(llmService.fileList(param.getModel()));
}
/**
* 删除文件
*
* @author: 乾乾
*/
@PostMapping("/delete")
public ApiResult<Boolean> fileList(@Validated @RequestBody DeleteFileParam param) {
return ApiResult.success(llmService.deleteFile(param));
}
}
@@ -1,105 +0,0 @@
package com.hula.ai.controller.app;
import cn.hutool.core.util.ObjectUtil;
import com.hula.ai.common.utils.DateUtil;
import com.hula.ai.gpt.pojo.param.StatisticsParam;
import com.hula.ai.gpt.service.IStatisticsService;
import com.hula.domain.vo.res.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
/**
* 统计接口
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
* 得其道 乾乾
*/
@RestController
@RequestMapping("/statistics")
public class StatisticsController {
@Autowired
private IStatisticsService statisticsService;
/**
* 获取总数据
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
*/
@GetMapping("/index/total")
public ApiResult getTotalData() {
return ApiResult.success(statisticsService.getTotalData());
}
/**
* 获取折线图数据
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
*/
@GetMapping("/index/line")
public ApiResult getLineData(@RequestParam StatisticsParam param) {
if (ObjectUtil.isNull(param.getStartDate()) && ObjectUtil.isNull(param.getEndDate())) {
LocalDate endDate = LocalDate.now();
LocalDate startDate = LocalDate.now().plusWeeks(-1);
param.setStartDate(DateUtil.formatLocalDate(startDate));
param.setEndDate(DateUtil.formatLocalDate(endDate));
}
return ApiResult.success(statisticsService.getLineData(param));
}
/**
* 获取雷达图数据
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
*/
@GetMapping("/index/raddar")
public ApiResult getRaddarData(@RequestParam StatisticsParam param) {
LocalDate endDate = LocalDate.now();
LocalDate startDate = LocalDate.now().plusWeeks(-1);
param.setStartDate(DateUtil.formatLocalDate(startDate));
param.setEndDate(DateUtil.formatLocalDate(endDate));
return ApiResult.success(statisticsService.getRaddarData(param));
}
/**
* 获取饼图数据
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
*/
@GetMapping("/index/pie")
public ApiResult getPieData(@RequestParam StatisticsParam param) {
return ApiResult.success(statisticsService.getPieData(param));
}
/**
* 获取柱状图图数据
*
* @author: 云裂痕
* @date: 2025/03/07
* @version: 1.2.8
*/
@GetMapping("/index/bar")
public ApiResult getBarData(@RequestParam StatisticsParam param) {
LocalDate endDate = LocalDate.now();
LocalDate startDate = LocalDate.now().plusWeeks(-1);
param.setStartDate(DateUtil.formatLocalDate(startDate));
param.setEndDate(DateUtil.formatLocalDate(endDate));
return ApiResult.success(statisticsService.getBarData(param));
}
}
@@ -0,0 +1,111 @@
package com.hula.ai.controller.chat;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.hula.ai.common.pojo.PageResult;
import com.hula.ai.controller.chat.vo.conversation.AiChatConversationCreateMyReqVO;
import com.hula.ai.controller.chat.vo.conversation.AiChatConversationPageReqVO;
import com.hula.ai.controller.chat.vo.conversation.AiChatConversationRespVO;
import com.hula.ai.controller.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
import com.hula.ai.dal.chat.AiChatConversationDO;
import com.hula.ai.service.chat.AiChatConversationService;
import com.hula.ai.service.chat.AiChatMessageService;
import com.hula.ai.utils.BeanUtils;
import com.hula.domain.vo.res.ApiResult;
import com.hula.utils.RequestHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import static com.hula.ai.common.pojo.CommonResult.success;
import static com.hula.common.utils.CollectionUtils.convertList;
@Tag(name = "管理后台 - AI 聊天对话")
@RestController
@RequestMapping("/ai/chat/conversation")
@Validated
public class AiChatConversationController {
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiChatMessageService chatMessageService;
@PostMapping("/create-my")
@Operation(summary = "创建【我的】聊天对话")
public ApiResult<Long> createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) {
return success(chatConversationService.createChatConversationMy(createReqVO, RequestHolder.get().getUid()));
}
@PutMapping("/update-my")
@Operation(summary = "更新【我的】聊天对话")
public ApiResult<Boolean> updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) {
chatConversationService.updateChatConversationMy(updateReqVO, RequestHolder.get().getUid());
return success(true);
}
@GetMapping("/my-list")
@Operation(summary = "获得【我的】聊天对话列表")
public ApiResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(RequestHolder.get().getUid());
return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
}
@GetMapping("/get-my")
@Operation(summary = "获得【我的】聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
public ApiResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), RequestHolder.get().getUid())) {
conversation = null;
}
return success(BeanUtils.toBean(conversation, AiChatConversationRespVO.class));
}
@DeleteMapping("/delete-my")
@Operation(summary = "删除聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
public ApiResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationMy(id, RequestHolder.get().getUid());
return success(true);
}
@DeleteMapping("/delete-by-unpinned")
@Operation(summary = "删除未置顶的聊天对话")
public ApiResult<Boolean> deleteChatConversationMyByUnpinned() {
chatConversationService.deleteChatConversationMyByUnpinned(RequestHolder.get().getUid());
return success(true);
}
// ========== 对话管理 ==========
@GetMapping("/page")
@Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
public ApiResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接关联数据
Map<Long, Integer> messageCountMap = chatMessageService.getChatMessageCountMap(
convertList(pageResult.getList(), AiChatConversationDO::getId));
return success(BeanUtils.toBean(pageResult, AiChatConversationRespVO.class,
conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
}
@Operation(summary = "管理员删除对话")
@DeleteMapping("/delete-by-admin")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
public ApiResult<Boolean> deleteChatConversationByAdmin(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationByAdmin(id);
return success(true);
}
}
@@ -0,0 +1,155 @@
package com.hula.ai.controller.chat;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.hula.ai.common.pojo.PageResult;
import com.hula.ai.controller.chat.vo.message.AiChatMessagePageReqVO;
import com.hula.ai.controller.chat.vo.message.AiChatMessageRespVO;
import com.hula.ai.controller.chat.vo.message.AiChatMessageSendReqVO;
import com.hula.ai.controller.chat.vo.message.AiChatMessageSendRespVO;
import com.hula.ai.dal.chat.AiChatConversationDO;
import com.hula.ai.dal.chat.AiChatMessageDO;
import com.hula.ai.dal.knowledge.AiKnowledgeDocumentDO;
import com.hula.ai.dal.knowledge.AiKnowledgeSegmentDO;
import com.hula.ai.dal.model.AiChatRoleDO;
import com.hula.ai.service.chat.AiChatConversationService;
import com.hula.ai.service.chat.AiChatMessageService;
import com.hula.ai.service.knowledge.AiKnowledgeDocumentService;
import com.hula.ai.service.knowledge.AiKnowledgeSegmentService;
import com.hula.ai.service.model.AiChatRoleService;
import com.hula.ai.utils.BeanUtils;
import com.hula.ai.utils.MapUtils;
import com.hula.domain.vo.res.ApiResult;
import com.hula.utils.RequestHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.hula.ai.common.pojo.CommonResult.success;
import static com.hula.common.utils.CollectionUtils.*;
@Tag(name = "管理后台 - 聊天消息")
@RestController
@RequestMapping("/ai/chat/message")
@Slf4j
public class AiChatMessageController {
@Resource
private AiChatMessageService chatMessageService;
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiChatRoleService chatRoleService;
@Resource
private AiKnowledgeSegmentService knowledgeSegmentService;
@Resource
private AiKnowledgeDocumentService knowledgeDocumentService;
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
@PostMapping("/send")
public ApiResult<AiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
return success(chatMessageService.sendMessage(sendReqVO, RequestHolder.get().getUid()));
}
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ApiResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
return chatMessageService.sendChatMessageStream(sendReqVO, RequestHolder.get().getUid());
}
@Operation(summary = "获得指定对话的消息列表")
@GetMapping("/list-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public ApiResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
@RequestParam("conversationId") Long conversationId) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), RequestHolder.get().getUid())) {
return success(Collections.emptyList());
}
// 1. 获取消息列表
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
if (CollUtil.isEmpty(messageList)) {
return success(Collections.emptyList());
}
// 2. 拼接数据,主要是知识库段落信息
Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
List<AiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AiChatMessageRespVO.class);
for (int i = 0; i < messageList.size(); i++) {
AiChatMessageDO message = messageList.get(i);
if (CollUtil.isEmpty(message.getSegmentIds())) {
continue;
}
// 设置知识库段落信息
messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
if (segment == null) {
return null;
}
AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
if (document == null) {
return null;
}
return new AiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
.setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
}));
}
return success(messageVOList);
}
@Operation(summary = "删除消息")
@DeleteMapping("/delete")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
public ApiResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
chatMessageService.deleteChatMessage(id, RequestHolder.get().getUid());
return success(true);
}
@Operation(summary = "删除指定对话的消息")
@DeleteMapping("/delete-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public ApiResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
chatMessageService.deleteChatMessageByConversationId(conversationId, RequestHolder.get().getUid());
return success(true);
}
// ========== 对话管理 ==========
@GetMapping("/page")
@Operation(summary = "获得消息分页", description = "用于【对话管理】菜单")
public ApiResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接数据
Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(),
role -> respVO.setRoleName(role.getName()))));
}
@Operation(summary = "管理员删除消息")
@DeleteMapping("/delete-by-admin")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
public ApiResult<Boolean> deleteChatMessageByAdmin(@RequestParam("id") Long id) {
chatMessageService.deleteChatMessageByAdmin(id);
return success(true);
}
}
@@ -0,0 +1,16 @@
package com.hula.ai.controller.chat.vo.conversation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天对话创建【我的】 Request VO")
@Data
public class AiChatConversationCreateMyReqVO {
@Schema(description = "聊天角色编号", example = "666")
private Long roleId;
@Schema(description = "知识库编号", example = "1204")
private Long knowledgeId;
}
@@ -0,0 +1,27 @@
package com.hula.ai.controller.chat.vo.conversation;
import com.hula.ai.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.hula.utils.DateUtil.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 聊天对话的分页 Request VO")
@Data
public class AiChatConversationPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "对话标题", example = "你好")
private String title;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,71 @@
package com.hula.ai.controller.chat.vo.conversation;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.fhs.core.trans.vo.VO;
import com.hula.ai.dal.model.AiChatRoleDO;
import com.hula.ai.dal.model.AiModelDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - AI 聊天对话 Response VO")
@Data
public class AiChatConversationRespVO implements VO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long userId;
@Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean pinned;
@Schema(description = "角色编号", example = "1")
@Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
private Long roleId;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
private Long modelId;
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
private String model;
@Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
private String modelName;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer maxContexts;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
// ========== 关联 role 信息 ==========
@Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
private String roleAvatar;
@Schema(description = "角色名字", example = "小黄")
private String roleName;
// ========== 仅在【对话管理】时加载 ==========
@Schema(description = "消息数量", example = "20")
private Integer messageCount;
}
@@ -0,0 +1,39 @@
package com.hula.ai.controller.chat.vo.conversation;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天对话更新【我的】 Request VO")
@Data
public class AiChatConversationUpdateMyReqVO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "对话编号不能为空")
private Long id;
@Schema(description = "对话标题", example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", example = "true")
private Boolean pinned;
@Schema(description = "模型编号", example = "1")
private Long modelId;
@Schema(description = "知识库编号", example = "1")
private Long knowledgeId;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", example = "10")
private Integer maxContexts;
}
@@ -0,0 +1,30 @@
package com.hula.ai.controller.chat.vo.message;
import com.hula.ai.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.hula.utils.DateUtil.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 聊天消息的分页 Request VO")
@Data
public class AiChatMessagePageReqVO extends PageParam {
@Schema(description = "对话编号", example = "2048")
private Long conversationId;
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "消息内容", example = "你好")
private String content;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,77 @@
package com.hula.ai.controller.chat.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - AI 聊天消息 Response VO")
@Data
@Accessors(chain = true)
public class AiChatMessageRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long conversationId;
@Schema(description = "回复消息编号", example = "1024")
private Long replyId;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
private String type; // 参见 MessageType 枚举类
@Schema(description = "用户编号", example = "4096")
private Long userId;
@Schema(description = "角色编号", example = "888")
private Long roleId;
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
private String model;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
private Long modelId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content;
@Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean useContext;
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
private List<Long> segmentIds;
@Schema(description = "知识库段落数组")
private List<KnowledgeSegment> segments;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
private LocalDateTime createTime;
// ========== 仅在【对话管理】时加载 ==========
@Schema(description = "角色名字", example = "小黄")
private String roleName;
@Schema(description = "知识库段落", example = "Java 开发手册")
@Data
public static class KnowledgeSegment {
@Schema(description = "段落编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
private String content;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790")
private Long documentId;
@Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
private String documentName;
}
}
@@ -0,0 +1,23 @@
package com.hula.ai.controller.chat.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天消息发送 Request VO")
@Data
public class AiChatMessageSendReqVO {
@Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "聊天对话编号不能为空")
private Long conversationId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
@NotEmpty(message = "聊天内容不能为空")
private String content;
@Schema(description = "是否携带上下文", example = "true")
private Boolean useContext;
}
@@ -0,0 +1,45 @@
package com.hula.ai.controller.chat.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - AI 聊天消息发送 Response VO")
@Data
@Accessors(chain = true)
public class AiChatMessageSendRespVO {
@Schema(description = "发送消息", requiredMode = Schema.RequiredMode.REQUIRED)
private Message send;
@Schema(description = "接收消息", requiredMode = Schema.RequiredMode.REQUIRED)
private Message receive;
@Schema(description = "消息")
@Data
public static class Message {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
private String type; // 参见 MessageType 枚举类
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content;
@Schema(description = "知识库段落编号数组", example = "[1,2,3]")
private List<Long> segmentIds;
@Schema(description = "知识库段落数组")
private List<AiChatMessageRespVO.KnowledgeSegment> segments;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
}
@@ -1,103 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.AgreementCommand;
import com.hula.ai.gpt.pojo.param.AgreementParam;
import com.hula.ai.gpt.pojo.vo.AgreementVO;
import com.hula.ai.gpt.service.IAgreementService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 内容管理接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/content")
public class AgreementController {
@Resource
private IAgreementService contentService;
/**
* 查询内容管理分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<AgreementVO>> pageContent(@RequestParam AgreementParam param) {
return ApiResult.success(contentService.pageContent(param));
}
/**
* 查询内容管理列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<AgreementVO>> listContent(@RequestBody AgreementParam param) {
return ApiResult.success(contentService.listContent(param));
}
/**
* 获取内容管理详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<AgreementVO> getContentById(@PathVariable("id") Long id) {
return ApiResult.success(contentService.getContentById(id));
}
/**
* 新增内容管理
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveContent(@Validated @RequestBody AgreementCommand command) {
contentService.saveContent(command);
return ApiResult.success();
}
/**
* 修改内容管理
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateContent(@Validated @RequestBody AgreementCommand command) {
return ApiResult.returnResult("修改", contentService.updateContent(command));
}
/**
* 批量删除内容管理
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeContentByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", contentService.removeContentByIds(ids));
}
}
@@ -1,100 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.AssistantCommand;
import com.hula.ai.gpt.pojo.param.AssustantParams;
import com.hula.ai.gpt.pojo.vo.AssistantVO;
import com.hula.ai.gpt.service.IAssistantService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* AI助理功能接口
*
* @author: 云裂痕
* @date: 2025-03-06
* 得其道 乾乾
*/
@RestController
@RequestMapping("/gpt/assistant")
public class AssistantController {
@Resource
private IAssistantService assistantService;
/**
* 查询AI助理功能分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<AssistantVO>> pageAssistant(@RequestParam AssustantParams param) {
return ApiResult.success(assistantService.pageAssistant(param));
}
/**
* 查询AI助理功能列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<AssistantVO>> listAssistant(@RequestBody AssustantParams param) {
return ApiResult.success(assistantService.listAssistant(param));
}
/**
* 获取AI助理功能详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<AssistantVO> getAssistantById(@PathVariable("id") Long id) {
return assistantService.getAssistantById(id);
}
/**
* 新增AI助理功能
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveAssistant(@Validated @RequestBody AssistantCommand command) {
return assistantService.saveAssistant(command);
}
/**
* 修改AI助理功能
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateAssistant(@Validated @RequestBody AssistantCommand command) {
return assistantService.updateAssistant(command);
}
/**
* 批量删除AI助理功能
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeAssistantByIds(@PathVariable List<Long> ids) {
return assistantService.removeAssistantByIds(ids);
}
}
@@ -1,103 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.AssistantTypeCommand;
import com.hula.ai.gpt.pojo.param.AgreementParam;
import com.hula.ai.gpt.pojo.param.AssustantTypeParams;
import com.hula.ai.gpt.pojo.vo.AssistantTypeVO;
import com.hula.ai.gpt.service.IAssistantTypeService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 助手分类接口
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/assistant-type")
public class AssistantTypeController {
@Resource
private IAssistantTypeService assistantTypeService;
/**
* 查询助手分类分页列表
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<AssistantTypeVO>> pageAssistantType(@RequestParam AssustantTypeParams param) {
return ApiResult.success(assistantTypeService.pageAssistantType(param));
}
/**
* 查询助手分类列表
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<AssistantTypeVO>> listAssistantType(@RequestBody AgreementParam param) {
return ApiResult.success(assistantTypeService.listAssistantType(param));
}
/**
* 获取助手分类详细信息
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<AssistantTypeVO> getAssistantTypeById(@PathVariable("id") Long id) {
return ApiResult.success(assistantTypeService.getAssistantTypeById(id));
}
/**
* 新增助手分类
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveAssistantType(@Validated @RequestBody AssistantTypeCommand command) {
return ApiResult.returnResult("新增", assistantTypeService.saveAssistantType(command));
}
/**
* 修改助手分类
*
* @author: 云裂痕
* @date: 2023-11-22
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateAssistantType(@Validated @RequestBody AssistantTypeCommand command) {
return ApiResult.returnResult("修改", assistantTypeService.updateAssistantType(command));
}
/**
* 批量删除助手分类
*
* @author: 云裂痕 false
* @date: 2023-11-22
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeAssistantTypeByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", assistantTypeService.removeAssistantTypeByIds(ids));
}
}
@@ -1,82 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.param.ChatParam;
import com.hula.ai.gpt.pojo.vo.ChatVO;
import com.hula.ai.gpt.service.IChatService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 聊天摘要接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController(value = "ChatAiListController")
@RequestMapping("/gpt/chat")
public class ChatController {
@Resource
private IChatService chatService;
/**
* 查询聊天摘要分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<ChatVO>> pageChat(@RequestParam ChatParam param) {
return ApiResult.success(chatService.pageChat(param));
}
/**
* 查询聊天摘要列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<ChatVO>> listChat(@RequestBody ChatParam param) {
return ApiResult.success(chatService.listChat(param));
}
/**
* 获取聊天摘要详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<ChatVO> getChatById(@PathVariable("id") Long id) {
return ApiResult.success(chatService.getChatById(id));
}
/**
* 批量删除聊天摘要
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeChatByIds(@PathVariable List<Long> ids) {
return ApiResult.success(chatService.removeChatByIds(ids));
}
}
@@ -1,85 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.entity.ChatMessage;
import com.hula.ai.gpt.pojo.param.ChatMessageParam;
import com.hula.ai.gpt.pojo.param.ChatParam;
import com.hula.ai.gpt.pojo.vo.ChatMessageVO;
import com.hula.ai.gpt.service.IChatMessageService;
import com.hula.common.domain.vo.res.CursorPageBaseResp;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 对话消息接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/chat-message")
public class ChatMessageController {
@Resource
private IChatMessageService chatMessageService;
/**
* 查询对话消息分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<ChatMessageVO>> pageChatMessage(@RequestParam ChatMessageParam param) {
return ApiResult.success(chatMessageService.pageChatMessage(param));
}
/**
* 查询对话消息列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<ChatMessageVO>> listChatMessage(@RequestBody ChatParam param) {
return ApiResult.success(chatMessageService.listChatMessage(param));
}
/**
* 获取对话消息详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<ChatMessageVO> getChatMessageById(@PathVariable("id") Long id) {
return ApiResult.success(chatMessageService.getChatMessageById(id));
}
/**
* 批量删除对话消息
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeChatMessageByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", chatMessageService.removeChatMessageByIds(ids));
}
}
@@ -1,106 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.CombCommand;
import com.hula.ai.gpt.pojo.param.CombParam;
import com.hula.ai.gpt.pojo.vo.CombVO;
import com.hula.ai.gpt.service.ICombService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 会员套餐接口
*
* @author: 云裂痕
* @date: 2025-03-07
* 得其道 乾乾
*/
@RestController
@RequestMapping("/gpt/comb")
public class CombController {
@Resource
private ICombService combService;
/**
* 查询会员套餐分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<CombVO>> pageComb(CombParam param) {
return ApiResult.success(combService.pageComb(param));
}
/**
* 查询会员套餐列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<CombVO>> listComb(@RequestBody CombParam param) {
return ApiResult.success(combService.listComb(param));
}
/**
* 获取会员套餐详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<CombVO> getCombById(@PathVariable("id") Long id) {
return ApiResult.success(combService.getCombById(id));
}
/**
* 新增会员套餐
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveComb(@Validated @RequestBody CombCommand command) {
return ApiResult.returnResult("新增", combService.saveComb(command));
}
/**
* 修改会员套餐
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateComb(@Validated @RequestBody CombCommand command) {
return ApiResult.returnResult("修改", combService.updateComb(command));
}
/**
* 批量删除会员套餐
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeCombByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", combService.removeCombByIds(ids));
}
}
@@ -1,117 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.ModelCommand;
import com.hula.ai.gpt.pojo.param.ModelParam;
import com.hula.ai.gpt.pojo.vo.ModelVO;
import com.hula.ai.gpt.service.IModelService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 大模型信息接口
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/model")
public class ModelController {
@Resource
private IModelService modelService;
/**
* 获取用户模型接口
*/
@GetMapping("/userModel")
public ApiResult<List<ModelVO>> getUserModel(ModelParam param) {
param.setStatus(1);
return ApiResult.success(modelService.listModel(param));
}
/**
* 查询大模型信息分页列表
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<ModelVO>> pageModel(ModelParam param) {
return ApiResult.success(modelService.pageModel(param));
}
/**
* 查询大模型信息列表
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<ModelVO>> listModel(@RequestBody ModelParam params) {
return ApiResult.success(modelService.listModel(params));
}
/**
* 获取大模型信息详细信息
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<ModelVO> getModelById(@PathVariable("id") Long id) {
return ApiResult.success(modelService.getModelById(id));
}
/**
* 新增大模型信息
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveModel(@Validated @RequestBody ModelCommand command) {
return ApiResult.returnResult("新增", modelService.saveModel(command));
}
/**
* 修改大模型信息
*
* @author: 云裂痕
* @date: 2023-12-01
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateModel(@Validated @RequestBody ModelCommand command) {
return ApiResult.returnResult("修改", modelService.updateModel(command));
}
/**
* 批量删除大模型信息
*
* @author: 云裂痕 false
* @date: 2023-12-01
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeModelByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", modelService.removeModelByIds(ids));
}
}
@@ -1,108 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.OpenkeyCommand;
import com.hula.ai.gpt.pojo.param.OpenKeyParam;
import com.hula.ai.gpt.pojo.vo.OpenkeyVO;
import com.hula.ai.gpt.service.IOpenkeyService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* openai token接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/openkey")
public class OpenkeyController {
@Resource
private IOpenkeyService openkeyService;
/**
* 查询openai token分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<OpenkeyVO>> pageOpenkey(OpenKeyParam param) {
return ApiResult.success(openkeyService.pageOpenkey(param));
}
/**
* 查询openai token列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<OpenkeyVO>> listOpenkey(@RequestBody OpenKeyParam param) {
return ApiResult.success(openkeyService.listOpenkey(param));
}
/**
* 获取openai token详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<OpenkeyVO> getOpenkeyById(@PathVariable("id") Long id) {
return ApiResult.success(openkeyService.getOpenkeyById(id));
}
/**
* 新增openai token
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveOpenkey(@Validated @RequestBody OpenkeyCommand command) {
return ApiResult.returnResult("新增", openkeyService.saveOpenkey(command));
}
/**
* 修改openai token
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateOpenkey(@Validated @RequestBody OpenkeyCommand command) {
return ApiResult.returnResult("编辑", openkeyService.updateOpenkey(command));
}
/**
* 批量删除openai token
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeOpenkeyByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", openkeyService.removeOpenkeyByIds(ids));
}
}
@@ -1,108 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.OrderCommand;
import com.hula.ai.gpt.pojo.param.OrderParam;
import com.hula.ai.gpt.pojo.vo.OrderVO;
import com.hula.ai.gpt.service.IOrderService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 订单接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/order")
public class OrderController {
@Resource
private IOrderService orderService;
/**
* 查询订单分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<OrderVO>> pageOrder(OrderParam param) {
return ApiResult.success(orderService.pageOrder(param));
}
/**
* 查询订单列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<OrderVO>> listOrder(@RequestBody OrderParam param) {
return ApiResult.success(orderService.listOrder(param));
}
/**
* 获取订单详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<OrderVO> getOrderById(@PathVariable("id") Long id) {
return ApiResult.success(orderService.getOrderById(id));
}
/**
* 新增订单
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveOrder(@Validated @RequestBody OrderCommand command) {
return ApiResult.returnResult("新增", orderService.saveOrder(command));
}
/**
* 修改订单
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateOrder(@Validated @RequestBody OrderCommand command) {
return ApiResult.returnResult("修改", orderService.updateOrder(command));
}
/**
* 批量删除订单
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeOrderByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", orderService.removeOrderByIds(ids));
}
}
@@ -1,102 +0,0 @@
package com.hula.ai.controller.gpt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hula.ai.gpt.pojo.command.RedemptionCommand;
import com.hula.ai.gpt.pojo.param.RedemptionParam;
import com.hula.ai.gpt.pojo.vo.RedemptionVO;
import com.hula.ai.gpt.service.IRedemptionService;
import com.hula.domain.vo.res.ApiResult;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 兑换码接口
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
* 得其道
* 乾乾
*/
@RestController
@RequestMapping("/gpt/redemption")
public class RedemptionController {
@Resource
private IRedemptionService redemptionService;
/**
* 查询兑换码分页列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping("/page")
public ApiResult<IPage<RedemptionVO>> pageRedemption(@RequestParam RedemptionParam param) {
return ApiResult.success(redemptionService.pageRedemption(param));
}
/**
* 查询兑换码列表
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/list")
public ApiResult<List<RedemptionVO>> listRedemption(@RequestBody RedemptionParam param) {
return ApiResult.success(redemptionService.listRedemption(param));
}
/**
* 获取兑换码详细信息
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@GetMapping(value = "/{id}")
public ApiResult<RedemptionVO> getRedemptionById(@PathVariable("id") Long id) {
return ApiResult.success(redemptionService.getRedemptionById(id));
}
/**
* 新增兑换码
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping
public ApiResult saveRedemption(@Validated @RequestBody RedemptionCommand command) {
return ApiResult.returnResult("保存", redemptionService.saveRedemption(command));
}
/**
* 修改兑换码
*
* @author: 云裂痕
* @date: 2023-04-28
* @version: 1.0.0
*/
@PutMapping
public ApiResult updateRedemption(@Validated @RequestBody RedemptionCommand command) {
return ApiResult.returnResult("修改", redemptionService.updateRedemption(command));
}
/**
* 批量删除兑换码
*
* @author: 云裂痕 false
* @date: 2023-04-28
* @version: 1.0.0
*/
@PostMapping("/{ids}")
public ApiResult removeRedemptionByIds(@PathVariable List<Long> ids) {
return ApiResult.returnResult("删除", redemptionService.removeRedemptionByIds(ids));
}
}
@@ -0,0 +1,42 @@
### 生成图片:OpenAIDALL
POST {{baseUrl}}/ai/image/draw
Content-Type: application/json
Authorization: {{token}}
{
"platform": "OpenAI",
"prompt": "可爱的小喵星人",
"model": "dall-e-3",
"height": "1024",
"width": "1024",
"options": {
"style": "vivid"
}
}
### 生成图片:StableDiffusion
POST {{baseUrl}}/ai/image/draw
Content-Type: application/json
Authorization: {{token}}
{
"platform": "StableDiffusion",
"prompt": "中国长城",
"model": "stable-diffusion-v1-6",
"height": "1024",
"width": "1024",
"style": "vivid"
}
### 生成图片:生成图片(Midjourney)
POST {{baseUrl}}/ai/image/midjourney/imagine
Content-Type: application/json
Authorization: {{token}}
{
"prompt": "中国旗袍",
"model": "midjourney",
"width": "1",
"height": "1",
"version": "6.0"
}
@@ -0,0 +1,133 @@
package com.hula.ai.controller.image;
import cn.hutool.core.util.ObjUtil;
import com.hula.ai.common.pojo.PageResult;
import com.hula.ai.controller.image.vo.*;
import com.hula.ai.controller.image.vo.midjourney.AiMidjourneyActionReqVO;
import com.hula.ai.controller.image.vo.midjourney.AiMidjourneyImagineReqVO;
import com.hula.ai.core.model.MidjourneyApi;
import com.hula.ai.dal.image.AiImageDO;
import com.hula.ai.service.image.AiImageService;
import com.hula.ai.utils.BeanUtils;
import com.hula.domain.vo.res.ApiResult;
import com.hula.utils.RequestHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.hula.ai.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - AI 绘画")
@RestController
@RequestMapping("/ai/image")
@Slf4j
public class AiImageController {
@Resource
private AiImageService imageService;
@GetMapping("/my-page")
@Operation(summary = "获取【我的】绘图分页")
public ApiResult<PageResult<AiImageRespVO>> getImagePageMy(@Validated AiImagePageReqVO pageReqVO) {
PageResult<AiImageDO> pageResult = imageService.getImagePageMy(RequestHolder.get().getUid(), pageReqVO);
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
}
@GetMapping("/public-page")
@Operation(summary = "获取公开的绘图分页")
public ApiResult<PageResult<AiImageRespVO>> getImagePagePublic(AiImagePublicPageReqVO pageReqVO) {
PageResult<AiImageDO> pageResult = imageService.getImagePagePublic(pageReqVO);
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
}
@GetMapping("/get-my")
@Operation(summary = "获取【我的】绘图记录")
@Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
public ApiResult<AiImageRespVO> getImageMy(@RequestParam("id") Long id) {
AiImageDO image = imageService.getImage(id);
if (image == null || ObjUtil.notEqual(RequestHolder.get().getUid(), image.getUserId())) {
return success(null);
}
return success(BeanUtils.toBean(image, AiImageRespVO.class));
}
@GetMapping("/my-list-by-ids")
@Operation(summary = "获取【我的】绘图记录列表")
@Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048")
public ApiResult<List<AiImageRespVO>> getImageListMyByIds(@RequestParam("ids") List<Long> ids) {
List<AiImageDO> imageList = imageService.getImageList(ids);
imageList.removeIf(item -> !ObjUtil.equal(RequestHolder.get().getUid(), item.getUserId()));
return success(BeanUtils.toBean(imageList, AiImageRespVO.class));
}
@Operation(summary = "生成图片")
@PostMapping("/draw")
public ApiResult<Long> drawImage(@Valid @RequestBody AiImageDrawReqVO drawReqVO) {
return success(imageService.drawImage(RequestHolder.get().getUid(), drawReqVO));
}
@Operation(summary = "删除【我的】绘画记录")
@DeleteMapping("/delete-my")
@Parameter(name = "id", required = true, description = "绘画编号", example = "1024")
public ApiResult<Boolean> deleteImageMy(@RequestParam("id") Long id) {
imageService.deleteImageMy(id, RequestHolder.get().getUid());
return success(true);
}
// ================ midjourney 专属 ================
@Operation(summary = "【Midjourney】生成图片")
@PostMapping("/midjourney/imagine")
public ApiResult<Long> midjourneyImagine(@Valid @RequestBody AiMidjourneyImagineReqVO reqVO) {
Long imageId = imageService.midjourneyImagine(RequestHolder.get().getUid(), reqVO);
return success(imageId);
}
@Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调")
@PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错
@PermitAll
public ApiResult<Boolean> midjourneyNotify(@Valid @RequestBody MidjourneyApi.Notify notify) {
imageService.midjourneyNotify(notify);
return success(true);
}
@Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等")
@PostMapping("/midjourney/action")
public ApiResult<Long> midjourneyAction(@Valid @RequestBody AiMidjourneyActionReqVO reqVO) {
Long imageId = imageService.midjourneyAction(RequestHolder.get().getUid(), reqVO);
return success(imageId);
}
// ================ 绘图管理 ================
@GetMapping("/page")
@Operation(summary = "获得绘画分页")
public ApiResult<PageResult<AiImageRespVO>> getImagePage(@Valid AiImagePageReqVO pageReqVO) {
PageResult<AiImageDO> pageResult = imageService.getImagePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AiImageRespVO.class));
}
@PutMapping("/update")
@Operation(summary = "更新绘画")
public ApiResult<Boolean> updateImage(@Valid @RequestBody AiImageUpdateReqVO updateReqVO) {
imageService.updateImage(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除绘画")
@Parameter(name = "id", description = "编号", required = true)
public ApiResult<Boolean> deleteImage(@RequestParam("id") Long id) {
imageService.deleteImage(id);
return success(true);
}
}
@@ -0,0 +1,49 @@
package com.hula.ai.controller.image.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.ai.openai.OpenAiImageOptions;
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
import java.util.Map;
@Schema(description = "管理后台 - AI 绘画 Request VO")
@Data
public class AiImageDrawReqVO {
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "模型编号不能为空")
private Long modelId;
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城")
@NotEmpty(message = "提示词不能为空")
@Size(max = 1200, message = "提示词最大 1200")
private String prompt;
/**
* 1. dall-e-2 模型:256x256、512x512、1024x1024
* 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792
*/
@Schema(description = "图片高度")
@NotNull(message = "图片高度不能为空")
private Integer height;
@Schema(description = "图片宽度")
@NotNull(message = "图片宽度不能为空")
private Integer width;
// ========== 各平台绘画的拓展参数 ==========
/**
* 绘制参数,不同 platform 的不同参数
*
* 1. {@link OpenAiImageOptions}
* 2. {@link StabilityAiImageOptions}
*/
@Schema(description = "绘制参数")
private Map<String, String> options;
}
@@ -0,0 +1,36 @@
package com.hula.ai.controller.image.vo;
import com.hula.ai.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.hula.utils.DateUtil.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 绘画分页 Request VO")
@Data
public class AiImagePageReqVO extends PageParam {
@Schema(description = "用户编号", example = "28987")
private Long userId;
@Schema(description = "平台", example = "OpenAI")
private String platform;
@Schema(description = "提示词", example = "1")
private String prompt;
@Schema(description = "绘画状态", example = "1")
private Integer status;
@Schema(description = "是否发布", example = "1")
private Boolean publicStatus;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,14 @@
package com.hula.ai.controller.image.vo;
import com.hula.ai.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 绘画公开的分页 Request VO")
@Data
public class AiImagePublicPageReqVO extends PageParam {
@Schema(description = "提示词")
private String prompt;
}
@@ -0,0 +1,60 @@
package com.hula.ai.controller.image.vo;
import com.hula.ai.core.model.MidjourneyApi;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - AI 绘画 Response VO")
@Data
public class AiImageRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long userId;
@Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI")
private String platform; // 参见 AiPlatformEnum 枚举
@Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6")
private String model;
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅")
private String prompt;
@Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer width;
@Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer height;
@Schema(description = "绘画状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer status;
@Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "public")
private Boolean publicStatus;
@Schema(description = "图片地址", example = "https://www.iocoder.cn/1.png")
private String picUrl;
@Schema(description = "绘画错误信息", example = "图片错误信息")
private String errorMessage;
@Schema(description = "绘制参数")
private Map<String, String> options;
@Schema(description = "mj buttons 按钮")
private List<MidjourneyApi.Button> buttons;
@Schema(description = "完成时间")
private LocalDateTime finishTime;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,18 @@
package com.hula.ai.controller.image.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 绘画修改 Request VO")
@Data
public class AiImageUpdateReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
@NotNull(message = "编号不能为空")
private Long id;
@Schema(description = "是否发布", example = "true")
private Boolean publicStatus;
}
@@ -0,0 +1,20 @@
package com.hula.ai.controller.image.vo.midjourney;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 绘图操作(Midjourney Request VO")
@Data
public class AiMidjourneyActionReqVO {
@Schema(description = "图片编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "图片编号不能为空")
private Long id;
@Schema(description = "操作按钮编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "MJ::JOB::variation::4::06aa3e66-0e97-49cc-8201-e0295d883de4")
@NotEmpty(message = "操作按钮编号不能为空")
private String customId;
}
@@ -0,0 +1,35 @@
package com.hula.ai.controller.image.vo.midjourney;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 绘画生成(Midjourney Request VO")
@Data
public class AiMidjourneyImagineReqVO {
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙")
@NotEmpty(message = "提示词不能为空!")
private String prompt;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "模型编号不能为空")
private Long modelId;
@Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "图片宽度不能为空")
private Integer width;
@Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "图片高度不能为空")
private Integer height;
@Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6.0")
@NotEmpty(message = "版本号不能为空")
private String version;
@Schema(description = "参考图", example = "https://www.iocoder.cn/x.png")
private String referImageUrl;
}

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