!23 合并到master分支

Merge pull request !23 from Dawn/dev
This commit is contained in:
Dawn
2025-05-13 03:48:38 +00:00
committed by Gitee
49 changed files with 735 additions and 199 deletions

View File

@@ -17,6 +17,7 @@ import com.hula.domain.vo.res.ApiResult;
import com.hula.utils.RequestHolder;
import jakarta.servlet.http.HttpServletResponse;
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;
@@ -26,6 +27,7 @@ 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;
/**
@@ -134,6 +136,7 @@ public class ChatController {
@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));
}
@@ -162,7 +165,7 @@ public class ChatController {
* @date: 2025/03/16
* @version: 1.0.0
*/
@PostMapping("/completions")
@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())) {

View File

@@ -38,7 +38,6 @@ public interface ChatMessageMapper extends BaseMapper<ChatMessage> {
*/
List<ChatMessageVO> listChatMessage(@Param("q") ChatParam query);
/**
* 更新消息状态
*

View File

@@ -64,4 +64,14 @@ public class ChatParam implements Serializable {
* 结束原因
*/
private String finishReason;
public Integer getOffset() {
if (current == null || current < 1) {
current = 1; // 默认第一页
}
if (size == null || size < 1) {
size = 10; // 默认每页10条
}
return (current - 1) * size;
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@@ -125,6 +126,7 @@ public class ChatMessageServiceImpl extends ServiceImpl<ChatMessageMapper, ChatM
.content(command.getPrompt()).contentType(ChatContentEnum.TEXT.getValue()).role(ChatRoleEnum.USER.getValue()).status(ChatStatusEnum.REPLY.getValue())
.build();
chatMessage.setCreatedBy(command.getOperater());
chatMessage.setCreatedTime(LocalDateTime.now());
chatMessageMapper.insert(chatMessage);
return chatMessage.getMessageId();
}

View File

@@ -21,6 +21,8 @@ import com.hula.exception.BizException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -104,6 +106,8 @@ public class ChatServiceImpl extends ServiceImpl<ChatMapper, Chat> implements IC
int maxLength = 30;
chat.setTitle(command.getPrompt().substring(0, command.getPrompt().length() > maxLength ? maxLength : command.getPrompt().length()));
chat.setUid(command.getUid());
chat.setCreatedBy(command.getUid());
chat.setCreatedTime(LocalDateTime.now());
chatMapper.insert(chat);
return ChatVO.builder().chatNumber(chat.getChatNumber()).prompt(command.getPrompt()).build();
}

View File

@@ -72,9 +72,9 @@ public class RedisKey {
public static final String GROUP_ANNOUNCEMENTS_FORMAT = "groupInfo:announcements_%d";
/**
* 用户token存放
* 用户token存放 格式:终端:uid:uuid
*/
public static final String USER_TOKEN_FORMAT = "userToken:%s:uid_%d";
public static final String USER_TOKEN_FORMAT = "userToken:%s:uid_%d:%s";
/**
* 用户refreshToken存放

View File

@@ -16,10 +16,11 @@ import java.util.stream.Collectors;
@AllArgsConstructor
@Getter
public enum YesOrNoEnum {
NO(0, ""),
YES(1, ""),
NO(false,0, ""),
YES(true,1, ""),
;
private final Boolean bool;
private final Integer status;
private final String desc;

View File

@@ -14,7 +14,7 @@ import java.util.UUID;
* @author nyh
*/
@Slf4j
@WebFilter(urlPatterns = "/*")
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HttpTraceIdFilter implements Filter {
@Override

View File

@@ -4,6 +4,10 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* IP 工具类
@@ -15,6 +19,10 @@ public class IPUtils {
// 需要检查的代理头,按优先级排序
private static final String[] HEADERS_TO_CHECK = {
"CF-Connecting-IP", // Cloudflare
"True-Client-IP", // Akamai
"X-Original-Forwarded-For", // Kubernetes
"X-Client-IP",
"X-Real-IP",
"X-Forwarded-For",
"Proxy-Client-IP",
@@ -23,117 +31,194 @@ public class IPUtils {
"HTTP_X_FORWARDED_FOR"
};
/**
* 从多级反向代理中获取第一个合法IP
*/
public static String getMultistageReverseProxyIp(String ip) {
if (ip != null && ip.contains(",")) {
String[] ips = ip.trim().split(",");
for (String subIp : ips) {
subIp = extractPureIp(subIp.trim());
if (isValidIP(subIp) && !isUnknown(subIp)) {
return subIp;
}
}
}
String pureIp = extractPureIp(ip);
return isValidIP(pureIp) ? pureIp : null;
// 私有IP范围CIDR支持IPv4/IPv6
private static final List<String> PRIVATE_CIDRS = Arrays.asList(
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
"::1/128"
);
// IP格式预编译正则
private static final Pattern IPv4_PATTERN = Pattern.compile(
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
);
private static final Pattern IPv6_PATTERN = Pattern.compile(
"^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^[0-9a-fA-F]{1,4}::([0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$"
);
private static boolean isValidHeaderValue(String value) {
return value != null &&
!value.trim().isEmpty() &&
!"unknown".equalsIgnoreCase(value);
}
/**
* 去除端口号并提取纯IP
* 提取无端口IP增强IPv6处理
*/
private static String extractPureIp(String ip) {
if (ip == null) return null;
ip = ip.trim();
private static String extractPureIP(String ipWithPort) {
if (ipWithPort == null) return null;
String trimmed = ipWithPort.trim();
// 处理IPv6带端口格式 [::1]:8080
if (ip.startsWith("[") && ip.contains("]")) {
int end = ip.indexOf(']');
ip = ip.substring(1, end);
} else {
// 处理IPv4或普通格式
int colonPos = ip.indexOf(':');
if (colonPos > 0 && !ip.substring(0, colonPos).contains(":")) {
ip = ip.substring(0, colonPos);
}
if (trimmed.startsWith("[") && trimmed.contains("]")) {
int endIndex = trimmed.indexOf(']');
return trimmed.substring(1, endIndex);
}
return ip;
// 处理IPv4或简单IPv6
int colonIndex = trimmed.indexOf(':');
if (colonIndex > 0 && !trimmed.substring(0, colonIndex).contains(":")) {
return trimmed.substring(0, colonIndex);
}
return trimmed;
}
/**
* 检查字符串是否为"unknown"或空
* 增强IP格式验证
*/
public static boolean isUnknown(String checkString) {
return checkString == null || checkString.trim().isEmpty() || "unknown".equalsIgnoreCase(checkString);
}
/**
* 严格验证IP合法性
*/
public static boolean isValidIP(String ip) {
private static boolean isValidIPFormat(String ip) {
if (ip == null || ip.isEmpty()) return false;
ip = ip.trim();
// 检查IPv4
if (ip.contains(".")) {
return isValidIPv4(ip);
}
// 检查IPv6
else if (ip.contains(":")) {
return isValidIPv6(ip);
}
return false;
return IPv4_PATTERN.matcher(ip).matches() ||
IPv6_PATTERN.matcher(ip).matches();
}
private static boolean isValidIPv4(String ip) {
String[] parts = ip.split("\\.");
if (parts.length != 4) return false;
/**
* 检查是否为私有IP
*/
private static boolean isPrivateIP(String ip) {
try {
for (String part : parts) {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) return false;
}
return !ip.endsWith(".");
} catch (NumberFormatException e) {
return false;
}
}
private static boolean isValidIPv6(String ip) {
try {
InetAddress.getByName(ip);
return ip.contains(":") && (ip.split(":").length <= 8);
InetAddress address = InetAddress.getByName(ip);
return PRIVATE_CIDRS.stream()
.anyMatch(cidr -> {
IPWithMask cidrRange;
try {
cidrRange = new IPWithMask(cidr);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
return cidrRange.contains(address);
});
} catch (UnknownHostException e) {
return false;
}
}
/**
* 获取客户端真实IP
* 解析代理头值
*/
private static String parseProxyHeader(String headerValue) {
if (headerValue.contains(",")) {
List<String> ips = Arrays.stream(headerValue.split(","))
.map(String::trim)
.map(IPUtils::extractPureIP)
.filter(IPUtils::isValidIPFormat)
.collect(Collectors.toList());
// 优先选择首个公网IP
for (String candidate : ips) {
if (!isPrivateIP(candidate)) {
return candidate;
}
}
// 如果没有公网IP则返回第一个合法IP
return ips.isEmpty() ? null : ips.get(0);
}
return extractPureIP(headerValue);
}
/**
* 标准化特殊地址
*/
private static String normalizeSpecialIPs(String ip) {
if (ip == null) return null;
// IPv6本地地址标准化
if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
return "127.0.0.1";
}
// 处理IPv4兼容格式
return ip.replaceAll("^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$", "$1");
}
/**
* 验证是否为有效公网IP
*/
private static boolean isValidPublicIP(String ip) {
return isValidIPFormat(ip) && !isPrivateIP(ip);
}
private static boolean isInvalidIP(String ip) {
return ip == null ||
!isValidIPFormat(ip) ||
"127.0.0.1".equals(ip);
}
public static String getHostIp(HttpServletRequest request) {
String ip = null;
// 按优先级检查代理头
// 1. 检查代理头
for (String header : HEADERS_TO_CHECK) {
String headerValue = request.getHeader(header);
if (!isUnknown(headerValue)) {
ip = getMultistageReverseProxyIp(headerValue);
if (ip != null) break;
if (isValidHeaderValue(headerValue)) {
ip = parseProxyHeader(headerValue);
if (ip != null) {
log.debug("Found valid IP [{}] in header: {}", ip, header);
break;
}
}
}
// 直接获取远端地址并处理特殊IP
if (isUnknown(ip)) {
// 2. 回退到远端地址
if (isInvalidIP(ip)) {
ip = request.getRemoteAddr();
// 处理IPv6本地地址
if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
ip = "127.0.0.1";
}
log.debug("使用远程ip地址: {}", ip);
}
return isValidIP(ip) ? ip : null;
// 3. 处理特殊地址
ip = normalizeSpecialIPs(ip);
// 4. 最终验证
return isValidPublicIP(ip) ? ip : null;
}
/**
* CIDR处理辅助类
*/
private static class IPWithMask {
private final InetAddress inetAddress;
private final int mask;
IPWithMask(String cidr) throws UnknownHostException {
String[] parts = cidr.split("/");
this.inetAddress = InetAddress.getByName(parts[0]);
this.mask = (parts.length > 1) ? Integer.parseInt(parts[1]) :
(inetAddress.getAddress().length == 4) ? 32 : 128;
}
boolean contains(InetAddress address) {
if (!address.getClass().equals(inetAddress.getClass())) return false;
byte[] targetIP = address.getAddress();
byte[] cidrIP = inetAddress.getAddress();
int maskBytes = mask / 8;
int remainingBits = mask % 8;
// 检查完整字节部分
for (int i = 0; i < maskBytes; i++) {
if (targetIP[i] != cidrIP[i]) return false;
}
// 处理剩余位
if (remainingBits > 0) {
byte maskByte = (byte) (0xFF << (8 - remainingBits));
return (targetIP[maskBytes] & maskByte) == (cidrIP[maskBytes] & maskByte);
}
return true;
}
}
}

View File

@@ -0,0 +1,139 @@
package com.hula.common.utils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* IP 工具类
*
* @author 乾乾
*/
@Slf4j
public class OldIPUtils {
// 需要检查的代理头,按优先级排序
private static final String[] HEADERS_TO_CHECK = {
"X-Real-IP",
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
/**
* 从多级反向代理中获取第一个合法IP
*/
public static String getMultistageReverseProxyIp(String ip) {
if (ip != null && ip.contains(",")) {
String[] ips = ip.trim().split(",");
for (String subIp : ips) {
subIp = extractPureIp(subIp.trim());
if (isValidIP(subIp) && !isUnknown(subIp)) {
return subIp;
}
}
}
String pureIp = extractPureIp(ip);
return isValidIP(pureIp) ? pureIp : null;
}
/**
* 去除端口号并提取纯IP
*/
private static String extractPureIp(String ip) {
if (ip == null) return null;
ip = ip.trim();
// 处理IPv6带端口格式 [::1]:8080
if (ip.startsWith("[") && ip.contains("]")) {
int end = ip.indexOf(']');
ip = ip.substring(1, end);
} else {
// 处理IPv4或普通格式
int colonPos = ip.indexOf(':');
if (colonPos > 0 && !ip.substring(0, colonPos).contains(":")) {
ip = ip.substring(0, colonPos);
}
}
return ip;
}
/**
* 检查字符串是否为"unknown"或空
*/
public static boolean isUnknown(String checkString) {
return checkString == null || checkString.trim().isEmpty() || "unknown".equalsIgnoreCase(checkString);
}
/**
* 严格验证IP合法性
*/
public static boolean isValidIP(String ip) {
if (ip == null || ip.isEmpty()) return false;
ip = ip.trim();
// 检查IPv4
if (ip.contains(".")) {
return isValidIPv4(ip);
}
// 检查IPv6
else if (ip.contains(":")) {
return isValidIPv6(ip);
}
return false;
}
private static boolean isValidIPv4(String ip) {
String[] parts = ip.split("\\.");
if (parts.length != 4) return false;
try {
for (String part : parts) {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) return false;
}
return !ip.endsWith(".");
} catch (NumberFormatException e) {
return false;
}
}
private static boolean isValidIPv6(String ip) {
try {
InetAddress.getByName(ip);
return ip.contains(":") && (ip.split(":").length <= 8);
} catch (UnknownHostException e) {
return false;
}
}
/**
* 获取客户端真实IP
*/
public static String getHostIp(HttpServletRequest request) {
String ip = null;
// 按优先级检查代理头
for (String header : HEADERS_TO_CHECK) {
String headerValue = request.getHeader(header);
if (!isUnknown(headerValue)) {
ip = getMultistageReverseProxyIp(headerValue);
if (ip != null) break;
}
}
// 直接获取远端地址并处理特殊IP
if (isUnknown(ip)) {
ip = request.getRemoteAddr();
// 处理IPv6本地地址
if ("0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) {
ip = "127.0.0.1";
}
}
return isValidIP(ip) ? ip : null;
}
}

View File

@@ -22,7 +22,6 @@ public class ChatMessageMarkDTO {
@Schema(description ="消息id")
private Long msgId;
@Schema(description ="标记类型 1点赞 2举报")
private Integer markType;
@Schema(description ="动作类型 1确认 2取消")

View File

@@ -42,7 +42,7 @@ public class MessageMark implements Serializable {
private Long uid;
/**
* 标记类型 1点赞 2举报
* 标记类型 @see com.hula.core.chat.domain.enums.MessageMarkTypeEnum
*/
@TableField("type")
private Integer type;

View File

@@ -15,21 +15,36 @@ import java.util.stream.Collectors;
@AllArgsConstructor
@Getter
public enum MessageMarkTypeEnum {
LIKE(1, "点赞", 10),
DISLIKE(2, "点踩", 5),
;
LIKE(1, "点赞", 10),
DISLIKE(2, "不满", 5),
// 新增表情互动类型
HEART(3, "爱心", 15), // 表达喜爱
ANGRY(4, "愤怒", 20), // 表达不满
CELEBRATE(5, "礼炮", 30), // 庆祝消息
ROCKET(6, "火箭", 50), // 超级认可
LOL(7, "笑哭", 15), // 觉得有趣
APPLAUSE(8, "鼓掌", 25), // 表示赞赏
FLOWER(9, "鲜花", 20), // 送花支持
BOMB(10, "炸弹", 40), // 强烈反对
CONFUSED(11, "疑问", 15), // 表示疑惑
VICTORY(12, "胜利", 35), // 庆祝胜利
LIGHT(13, "灯光", 30), // 打call支持
MONEY(14, "红包", 45); // 打赏支持
private final Integer type;
private final String desc;
private final Integer riseNum;//需要多少个标记升级
private final Integer type;
private final String desc;
private final Integer riseNum; // 需要多少个标记升级 TODO 达到多少次数之后触发的效果
private static Map<Integer, MessageMarkTypeEnum> cache;
private static final Map<Integer, MessageMarkTypeEnum> CACHE;
static {
cache = Arrays.stream(MessageMarkTypeEnum.values()).collect(Collectors.toMap(MessageMarkTypeEnum::getType, Function.identity()));
}
static {
CACHE = Arrays.stream(values()).collect(Collectors.toMap(
MessageMarkTypeEnum::getType,
Function.identity()
));
}
public static MessageMarkTypeEnum of(Integer type) {
return cache.get(type);
}
}
public static MessageMarkTypeEnum of(Integer type) {
return CACHE.get(type);
}
}

View File

@@ -21,8 +21,10 @@ public class ChatMessageMarkReq {
@Schema(description ="消息id")
private Long msgId;
/**
* @see com.hula.core.chat.domain.enums.MessageMarkTypeEnum
*/
@NotNull
@Schema(description ="标记类型 1点赞 2举报")
private Integer markType;
@NotNull

View File

@@ -20,4 +20,7 @@ public class MemberExitReq {
@NotNull
@Schema(description ="会话id")
private Long roomId;
@Schema(description ="搜索关键字")
private String account;
}

View File

@@ -8,6 +8,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.Map;
/**
* 消息
@@ -42,19 +43,16 @@ public class ChatMessageResp extends BaseEntity {
private Integer type;
@Schema(description ="消息内容不同的消息类型,内容体不同")
private Object body;
@Schema(description ="消息标记")
private MessageMark messageMark;
@Schema(description = "扩展标记统计type为MessageMarkTypeEnum的type字段")
private Map<Integer, MarkItem> messageMarks;
}
@Data
public static class MessageMark {
@Schema(description ="点赞数")
private Integer likeCount;
@Schema(description ="该用户是否已经点赞 0否 1是")
private Integer userLike;
@Schema(description ="举报数")
private Integer dislikeCount;
@Schema(description ="该用户是否已经举报 0否 1是")
private Integer userDislike;
}
@Data
@AllArgsConstructor
public static class MarkItem {
@Schema(description = "标记数量")
private Integer count;
@Schema(description = "当前用户是否标记")
private Boolean userMarked;
}
}

View File

@@ -60,21 +60,28 @@ public class MessageAdapter {
messageVO.setBody(msgHandler.showMsg(message));
}
//消息标记
messageVO.setMessageMark(buildMsgMark(marks, receiveUid));
messageVO.setMessageMarks(buildMsgMark(marks, receiveUid));
return messageVO;
}
private static ChatMessageResp.MessageMark buildMsgMark(List<MessageMark> marks, Long receiveUid) {
Map<Integer, List<MessageMark>> typeMap = marks.stream().collect(Collectors.groupingBy(MessageMark::getType));
List<MessageMark> likeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.LIKE.getType(), new ArrayList<>());
List<MessageMark> dislikeMarks = typeMap.getOrDefault(MessageMarkTypeEnum.DISLIKE.getType(), new ArrayList<>());
ChatMessageResp.MessageMark mark = new ChatMessageResp.MessageMark();
mark.setLikeCount(likeMarks.size());
mark.setUserLike(Optional.ofNullable(receiveUid).filter(uid -> likeMarks.stream().anyMatch(a -> Objects.equals(a.getUid(), uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
mark.setDislikeCount(dislikeMarks.size());
mark.setUserDislike(Optional.ofNullable(receiveUid).filter(uid -> dislikeMarks.stream().anyMatch(a -> Objects.equals(a.getUid(), uid))).map(a -> YesOrNoEnum.YES.getStatus()).orElse(YesOrNoEnum.NO.getStatus()));
return mark;
}
private static Map<Integer, ChatMessageResp.MarkItem> buildMsgMark(List<MessageMark> marks, Long receiveUid) {
Map<Integer, List<MessageMark>> typeMap = marks.stream().collect(Collectors.groupingBy(MessageMark::getType));
// 构造动态统计为主数据源
Map<Integer, ChatMessageResp.MarkItem> stats = new HashMap<>();
// 批量映射操作数量
Arrays.stream(MessageMarkTypeEnum.values()).forEach(typeEnum -> {
List<MessageMark> list = typeMap.getOrDefault(typeEnum.getType(), Collections.emptyList());
stats.put(typeEnum.getType(), new ChatMessageResp.MarkItem(list.size(), Optional.ofNullable(receiveUid)
.filter(uid -> list.stream().anyMatch(m -> m != null && Objects.equals(m.getUid(), uid)))
.map(uid -> YesOrNoEnum.YES.getBool())
.orElse(YesOrNoEnum.NO.getBool())));
});
return stats;
}
private static ChatMessageResp.UserInfo buildFromUser(Long fromUid) {
ChatMessageResp.UserInfo userInfo = new ChatMessageResp.UserInfo();

View File

@@ -9,6 +9,7 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -39,6 +40,14 @@ public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
return roomGroups.stream().collect(Collectors.toMap(RoomGroup::getRoomId, Function.identity()));
}
public RoomGroup getByRoomId(Long roomId) {
return roomGroupDao.getByRoomId(roomId);
}
public boolean removeById(Serializable id) {
return roomGroupDao.removeById(id);
}
/**
* 根据群号查询群信息
*/
@@ -56,4 +65,11 @@ public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
public List<Long> evictGroup(String account) {
return null;
}
/**
* 清除所有群组相关缓存
*/
@CacheEvict(cacheNames = "room", allEntries = true)
public void evictAllCaches() {
}
}

View File

@@ -84,7 +84,6 @@ public class ChatServiceImpl implements ChatService {
private RoomCache roomCache;
private GroupMemberDao groupMemberDao;
private RoomGroupCache roomGroupCache;
private RoomGroupDao roomGroupDao;
/**
* 发送消息
@@ -172,7 +171,7 @@ public class ChatServiceImpl implements ChatService {
}
// 获取群成员角色ID
List<Long> uidList = resultList.stream().map(item -> Long.parseLong(item.getUid())).collect(Collectors.toList());
RoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
RoomGroup roomGroup = roomGroupCache.getByRoomId(request.getRoomId());
Map<String, Integer> uidMapRole = groupMemberDao.getMemberMapRole(roomGroup.getId(), uidList);
resultList.forEach(member -> member.setRoleId(uidMapRole.get(member.getUid())));
// 组装结果

View File

@@ -1,6 +1,8 @@
package com.hula.core.chat.service.impl;
import cn.hutool.core.util.StrUtil;
import com.hula.core.chat.service.adapter.RoomAdapter;
import com.hula.core.chat.service.cache.RoomGroupCache;
import com.hula.enums.CommonErrorEnum;
import com.hula.enums.GroupErrorEnum;
import com.hula.utils.AssertUtil;
@@ -35,10 +37,10 @@ import static com.hula.core.chat.constant.GroupConst.MAX_MANAGE_COUNT;
public class GroupMemberServiceImpl implements IGroupMemberService {
private GroupMemberDao groupMemberDao;
private RoomGroupDao roomGroupDao;
private RoomDao roomDao;
private ContactDao contactDao;
private MessageDao messageDao;
private RoomGroupCache roomGroupCache;
private GroupMemberCache groupMemberCache;
private PushService pushService;
@@ -51,7 +53,7 @@ public class GroupMemberServiceImpl implements IGroupMemberService {
@Override
public void addAdmin(Long uid, AdminAddReq request) {
// 1. 判断群聊是否存在
RoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
RoomGroup roomGroup = roomGroupCache.getByRoomId(request.getRoomId());
AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST);
// 2. 判断该用户是否是群主
@@ -83,7 +85,7 @@ public class GroupMemberServiceImpl implements IGroupMemberService {
@Override
public void revokeAdmin(Long uid, AdminRevokeReq request) {
// 1. 判断群聊是否存在
RoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
RoomGroup roomGroup = roomGroupCache.getByRoomId(request.getRoomId());
AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST);
// 2. 判断该用户是否是群主
@@ -109,7 +111,7 @@ public class GroupMemberServiceImpl implements IGroupMemberService {
public void exitGroup(Long uid, MemberExitReq request) {
Long roomId = request.getRoomId();
// 1. 判断群聊是否存在
RoomGroup roomGroup = roomGroupDao.getByRoomId(roomId);
RoomGroup roomGroup = roomGroupCache.getByRoomId(roomId);
AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST);
// 2. 判断房间是否是大群聊 (大群聊禁止退出)
@@ -123,8 +125,13 @@ public class GroupMemberServiceImpl implements IGroupMemberService {
// 4. 判断该用户是否是群主
Boolean isLord = groupMemberDao.isLord(roomGroup.getId(), uid);
if (isLord) {
// 4.1 删除房间
// 4.1 删除房间和群并清除缓存
boolean isDelRoom = roomDao.removeById(roomId);
roomGroupCache.removeById(roomGroup.getId());
roomGroupCache.evictGroup(roomGroup.getAccount());
if(StrUtil.isNotEmpty(request.getAccount())){
roomGroupCache.evictGroup(request.getAccount());
}
AssertUtil.isTrue(isDelRoom, CommonErrorEnum.SYSTEM_ERROR);
// 4.2 删除会话
Boolean isDelContact = contactDao.removeByRoomId(roomId, Collections.EMPTY_LIST);

View File

@@ -600,6 +600,7 @@ public class RoomAppServiceImpl implements RoomAppService {
groupMemberDao.saveBatch(groupMembers);
// 发送邀请加群消息 ==> 触发每个人的会话
roomGroupCache.evictAllCaches();
applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMembers, uid));
return roomGroup.getRoomId();
}

View File

@@ -0,0 +1,25 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 愤怒标记策略
* @author 乾乾
*/
@Component
public class AngryStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.ANGRY;
}
// @Override
// public void doMark(Long uid, Long msgId) {
// super.doMark(uid, msgId);
// // 标记愤怒时清除正向表情
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.LIKE.getType()).unMark(uid, msgId);
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.HEART.getType()).unMark(uid, msgId);
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.MONEY.getType()).unMark(uid, msgId);
// }
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 鼓掌标记策略
* @author 乾乾
*/
@Component
public class ApplauseStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.APPLAUSE;
}
}

View File

@@ -0,0 +1,24 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 炸弹标记策略
* @author 乾乾
*/
@Component
public class BombStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.BOMB;
}
@Override
public void doMark(Long uid, Long msgId) {
super.doMark(uid, msgId);
// 使用炸弹时清除正向标记
MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.LIKE.getType()).unMark(uid, msgId);
MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.MONEY.getType()).unMark(uid, msgId);
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 礼炮标记策略
* @author 乾乾
*/
@Component
public class CelebrateStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.CELEBRATE;
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 疑问标记策略
* @author 乾乾
*/
@Component
public class ConfusedStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.CONFUSED;
}
}

View File

@@ -4,7 +4,7 @@ import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 点踩标记策略类
* 不满标记策略类
* @author nyh
*/
@Component

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 鲜花标记策略
* @author 乾乾
*/
@Component
public class FlowerStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.FLOWER;
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 爱心标记策略
* @author 乾乾
*/
@Component
public class HeartStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.HEART;
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 笑哭标记策略
* @author 乾乾
*/
@Component
public class LOLStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.LOL;
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 灯光标记策略
* @author 乾乾
*/
@Component
public class LightStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.LIGHT;
}
}

View File

@@ -18,7 +18,8 @@ public class LikeStrategy extends AbstractMsgMarkStrategy {
@Override
public void doMark(Long uid, Long msgId) {
super.doMark(uid, msgId);
//同时取消点踩的动作
//同时取消不满的动作
MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.DISLIKE.getType()).unMark(uid, msgId);
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.BOMB.getType()).unMark(uid, msgId);
}
}

View File

@@ -0,0 +1,25 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 红包标记策略
* @author 乾乾
*/
@Component
public class MoneyStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.MONEY;
}
// @Override
// public void doMark(Long uid, Long msgId) {
// super.doMark(uid, msgId);
// // 打赏时清除所有负面标记
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.BOMB.getType()).unMark(uid, msgId);
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.DISLIKE.getType()).unMark(uid, msgId);
// MsgMarkFactory.getStrategyNoNull(MessageMarkTypeEnum.ANGRY.getType()).unMark(uid, msgId);
// }
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 火箭标记策略
* @author 乾乾
*/
@Component
public class RocketStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.ROCKET;
}
}

View File

@@ -0,0 +1,16 @@
package com.hula.core.chat.service.strategy.mark;
import com.hula.core.chat.domain.enums.MessageMarkTypeEnum;
import org.springframework.stereotype.Component;
/**
* 胜利标记策略
* @author 乾乾
*/
@Component
public class VictoryStrategy extends AbstractMsgMarkStrategy {
@Override
protected MessageMarkTypeEnum getTypeEnum() {
return MessageMarkTypeEnum.VICTORY;
}
}

View File

@@ -29,4 +29,7 @@ public class BindEmailReq implements Serializable {
@Schema(description = "uuid")
private String uuid;
@Schema(description = "操作类型")
private String operationType = "register";
}

View File

@@ -1,16 +1,16 @@
package com.hula.core.user.domain.vo.req.user;
import com.hula.domain.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.io.Serializable;
/**
* @author 乾乾
*/
@Data
public class ForgotPasswordReq extends BaseEntity {
public class ForgotPasswordReq implements Serializable {
@NotEmpty(message = "请填写邮箱")
@Schema(description = "邮箱")

View File

@@ -28,12 +28,16 @@ public class OffLineResp {
@Schema(description = "登录ip")
private String ip;
@Schema(description = "本次登录uuid (不需要被清空)")
private String uuid;
public OffLineResp() {
}
public OffLineResp(Long uid, String client, String ip) {
public OffLineResp(Long uid, String client, String ip, String uuid) {
this.ip = ip;
this.uid = uid;
this.client = client;
this.uuid = uuid;
}
}

View File

@@ -24,7 +24,9 @@ public class WSMsgMark {
private Long uid;
@Schema(description ="消息id")
private Long msgId;
@Schema(description ="标记类型 1点赞 2举报")
/**
* @see com.hula.core.chat.domain.enums.MessageMarkTypeEnum
*/
private Integer markType;
@Schema(description ="被标记的数量")
private Integer markCount;

View File

@@ -48,7 +48,7 @@ public class EmailServiceImpl implements EmailService {
}
// 2. 校验邮箱是否存在
if (userDao.existsByEmailAndIdNot(null, req.getEmail())) {
if ("register".equals(req.getOperationType()) && userDao.existsByEmailAndIdNot(null, req.getEmail())) {
throw new BizException("该邮箱已被其他账号绑定");
}

View File

@@ -136,19 +136,19 @@ public class LoginServiceImpl implements LoginService {
// 1. 拿到token
String token = RequestHolder.get().getToken();
// 2. 解析token里面的数据精准拿到当前用户的refreshToken
try {
// 2. 解析token里面的数据精准拿到当前用户的refreshToken
Map<String, Claim> verifyToken = JwtUtils.verifyToken(token);
Long uid = verifyToken.get(JwtUtils.UID_CLAIM).asLong();
String type = verifyToken.get(JwtUtils.LOGIN_TYPE_CLAIM).asString();
String uuid = verifyToken.get(JwtUtils.UUID_CLAIM).asString();
if(!autoLogin){
// 2.1 用户启用自动登录删除refreshToken
Map<String, Claim> verifyToken = JwtUtils.verifyToken(token);
Long uid = verifyToken.get(JwtUtils.UID_CLAIM).asLong();
String type = verifyToken.get(JwtUtils.LOGIN_TYPE_CLAIM).asString();
String key = RedisKey.getKey(RedisKey.USER_REFRESH_TOKEN_FORMAT, type, uid, verifyToken.get(JwtUtils.UUID_CLAIM).asString());
RedisUtils.del(key);
RedisUtils.del(RedisKey.getKey(RedisKey.USER_REFRESH_TOKEN_FORMAT, type, uid, uuid));
}
// 3. 删除token
RedisUtils.del(RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, JwtUtils.getLoginType(token), RequestHolder.get().getUid()));
RedisUtils.del(RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, type, uid, uuid));
applicationEventPublisher.publishEvent(new UserOfflineEvent(this, User.builder().id(RequestHolder.get().getUid()).lastOptTime(DateUtil.date()).build()));
} catch (Exception e) {
throw TokenExceedException.expired();

View File

@@ -39,18 +39,21 @@ public class TokenServiceImpl implements TokenService {
@Override
public boolean verify(String token) {
Long uid = JwtUtils.getUidOrNull(token);
if (Objects.isNull(uid)) {
return false;
}
return Objects.equals(token, RedisUtils.getStr(RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, JwtUtils.getLoginType(token), uid)));
Map<String, Claim> claim = JwtUtils.verifyToken(token);
if (Objects.isNull(claim)) {
return false;
}
Long uid = claim.get(JwtUtils.UID_CLAIM).asLong();
String loginType = claim.get(JwtUtils.LOGIN_TYPE_CLAIM).asString();
String uuid = claim.get(JwtUtils.UUID_CLAIM).asString();
return Objects.equals(token, RedisUtils.getStr(RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, loginType, uid, uuid)));
}
@Override
public LoginResultVO createToken(Long uid, String loginType) {
// 1. uuid用于后续区分续签是给哪个token续签
String uuid = UUID.randomUUID().toString(true);
String tokenKey = RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, loginType, uid);
String tokenKey = RedisKey.getKey(RedisKey.USER_TOKEN_FORMAT, loginType, uid, uuid);
String refreshTokenKey = RedisKey.getKey(RedisKey.USER_REFRESH_TOKEN_FORMAT, loginType, uid, uuid);
String token = RedisUtils.getStr(tokenKey), refreshToken;
String key = RedisKey.getKey(RedisKey.USER_REFRESH_TOKEN_UID_FORMAT, loginType, uid);
@@ -58,7 +61,7 @@ public class TokenServiceImpl implements TokenService {
// 1.2 token存在 旧设备下线
if (StrUtil.isNotBlank(token)) {
applicationEventPublisher.publishEvent(new TokenExpireEvent(this, new OffLineResp(uid, loginType, RequestHolder.get().getIp())));
applicationEventPublisher.publishEvent(new TokenExpireEvent(this, new OffLineResp(uid, loginType, RequestHolder.get().getIp(), uuid)));
}
// 2. 创建用户token

View File

@@ -183,7 +183,7 @@ public class WXMsgServiceImpl implements WebSocketService {
}
// 让前端的token失效
else {
sendMsg(channel, WsAdapter.buildInvalidateTokenResp(new OffLineResp(JwtUtils.getUidOrNull(wsAuthorize.getToken()), LoginTypeEnum.PC.getType(), null)));
sendMsg(channel, WsAdapter.buildInvalidateTokenResp(new OffLineResp(JwtUtils.getUidOrNull(wsAuthorize.getToken()), LoginTypeEnum.PC.getType(), null, null)));
}
}

View File

@@ -25,25 +25,25 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
t.id, t.created_by, t.created_time, t.updated_by, t.updated_time, t.avatar, t.icon, t.title, t.tag, t.description, t.system_prompt, t.first_message,
t.type_id, t.main_model, t.status, t.sort, t.del
t.type_id, t.main_model, t.status, t.sort, t.is_del
</sql>
<!-- 通用查询条件 -->
<sql id="BaseSelect">
select <include refid="Base_Column_List"></include>, t1.name as typeName
from gpt_assistant t
left join gpt_assistant_type t1 on t.type_id = t1.id
from ai_gpt_assistant t
left join ai_gpt_assistant_type t1 on t.type_id = t1.id
<where>
t.del = 0
<if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>
t.is_del = 0
<!-- <if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>-->
<if test="q.title != null and q.title != ''"> and t.title like concat('%', #{q.title}, '%')</if>
<if test="q.typeId != null and q.typeId != '' and q.typeId != -1"> and t.type_id = #{q.typeId}</if>
<if test="q.mainModel != null"> and t.main_model = #{q.mainModel}</if>
<if test="q.sort != null"> and t.sort = #{q.sort}</if>
<if test="q.status != null"> and t.status = #{q.status}</if>
<if test="q.description != null and q.description != ''"> and t.description = #{q.description}</if>
<if test="q.startDate != null and q.startDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &gt;= #{q.startDate} </if>
<if test="q.endDate != null and q.endDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &lt;= #{q.endDate} </if>
<!-- <if test="q.startDate != null and q.startDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &gt;= #{q.startDate} </if>-->
<!-- <if test="q.endDate != null and q.endDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &lt;= #{q.endDate} </if>-->
</where>
order by t.sort desc, t.id desc
</sql>
@@ -58,18 +58,18 @@
<select id="listAssistantRandom" resultType="com.hula.ai.gpt.pojo.vo.AssistantVO">
select <include refid="Base_Column_List"></include>
from gpt_assistant t
from ai_gpt_assistant t
<where>
t.del = 0
<if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>
t.is_del = 0
<!-- <if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>-->
<if test="q.title != null and q.title != ''"> and t.title = #{q.title}</if>
<if test="q.typeId != null and q.typeId != -1"> and t.type_id = #{q.typeId}</if>
<if test="q.mainModel != null"> and t.main_model = #{q.mainModel}</if>
<if test="q.sort != null"> and t.sort = #{q.sort}</if>
<if test="q.status != null"> and t.status = #{q.status}</if>
<if test="q.startDate != null and q.startDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &gt;= #{q.startDate} </if>
<if test="q.endDate != null and q.endDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &lt;= #{q.endDate} </if>
<if test="q.dataScope != null and q.dataScope != ''"> ${q.dataScope} </if>
<!-- <if test="q.startDate != null and q.startDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &gt;= #{q.startDate} </if>-->
<!-- <if test="q.endDate != null and q.endDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &lt;= #{q.endDate} </if>-->
<!-- <if test="q.dataScope != null and q.dataScope != ''"> ${q.dataScope} </if>-->
</where>
ORDER BY RAND() LIMIT #{q.size}
</select>

View File

@@ -11,30 +11,30 @@
<result column="updated_time" property="updatedTime"/>
<result column="chat_number" property="chatNumber"/>
<result column="assistant_id" property="assistantId"/>
<result column="user_id" property="userId"/>
<result column="uid" property="uid"/>
<result column="title" property="title"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
t.id, t.created_by, t.created_time, t.updated_by, t.updated_time, t.chat_number, t.assistant_id, t.user_id, t.title, t.del
t.id, t.created_by, t.created_time, t.updated_by, t.updated_time, t.chat_number, t.assistant_id, t.uid, t.title, t.is_del
</sql>
<!-- 通用查询条件 -->
<sql id="BaseSelect">
select <include refid="Base_Column_List"></include>, t1.nick_name as userName, t2.title as assistantTitle, t2.icon as assistantIcon, t2.avatar as assistantAvatar
from gpt_chat t
left join gpt_user t1 on t.user_id = t1.id
left join gpt_assistant t2 on t.assistant_id = t2.id
select <include refid="Base_Column_List"></include>, t1.name as userName, t2.title as assistantTitle, t2.icon as assistantIcon, t2.avatar as assistantAvatar
from ai_gpt_chat t
left join user t1 on t.uid = t1.id
left join ai_gpt_assistant t2 on t.assistant_id = t2.id
<where>
t.del = 0
<if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>
<if test="q.userId != null"> and t.user_id = #{q.userId}</if>
<if test="q.userName != null"> and (t1.nick_name like concat('%', #{q.userName}, '%') or t1.tel like concat('%', #{q.userName}, '%') )</if>
t.is_del = 0
<!-- <if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>-->
<if test="q.uid != null"> and t.uid = #{q.uid}</if>
<if test="q.userName != null"> and (t1.name like concat('%', #{q.userName}, '%') or t1.tel like concat('%', #{q.userName}, '%') )</if>
<if test="q.title != null and q.title != ''"> and t.title = #{q.title}</if>
<if test="q.assistantId != null"> and t.assistant_id = #{q.assistantId}</if>
<if test="q.startDate != null and q.startDate != ''"> and t.create_time &gt;= #{q.startDate} </if>
<if test="q.endDate != null and q.endDate != ''"> and t.create_time &lt;= #{q.endDate} </if>
<!-- <if test="q.startDate != null and q.startDate != ''"> and t.create_time &gt;= #{q.startDate} </if>-->
<!-- <if test="q.endDate != null and q.endDate != ''"> and t.create_time &lt;= #{q.endDate} </if>-->
</where>
order by t.id desc
</sql>

View File

@@ -25,29 +25,32 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
t.id, t.created_by, t.created_time, t.chat_id, parent_message_id, t.message_id, t.model, t.model_version, t.role, t.content, t.content_type, t.finish_reason,
t.status, t.app_key, t.used_tokens, t.response, t.del
t.status, t.app_key, t.used_tokens, t.response, t.is_del
</sql>
<!-- 通用查询条件 -->
<sql id="BaseSelect">
select <include refid="Base_Column_List"></include>, t1.title as chatTitle
from gpt_chat_message t
left join gpt_chat t1 on t.chat_id = t1.id
from ai_gpt_chat_message t
left join ai_gpt_chat t1 on t.chat_id = t1.id
<where>
t.del = 0
<if test="q.id != null and q.id != ''"> and t.id = #{q.id} </if>
t.is_del = 0
<if test="q.chatId != null"> and t1.id = #{q.chatId}</if>
<if test="q.chatNumber != null"> and t1.chat_number = #{q.chatNumber}</if>
<if test="q.userId != null and q.userId != ''"> and t1.user_id = #{q.userId}</if>
<if test="q.messageId != null"> and t.message_id = #{q.messageId}</if>
<if test="q.model != null and q.model != ''"> and t.model = #{q.model}</if>
<if test="q.role != null and q.role != ''"> and t.role = #{q.role}</if>
<if test="q.appKey != null and q.appKey != ''"> and t.app_key = #{q.appKey}</if>
<if test="q.finishReason != null and q.finishReason != ''"> and t.finish_reason = #{q.finishReason}</if>
<if test="q.status != null"> and t.status = #{q.status}</if>
<if test="q.startDate != null and q.startDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &gt;= #{q.startDate} </if>
<if test="q.endDate != null and q.endDate != ''"> and date_format(t.created_time,'%Y-%m-%d') &lt;= #{q.endDate} </if>
</where>
order by t.created_time desc
<if test="q.size != null and q.size > 0">
LIMIT #{q.size}
<if test="q.current != null and q.current > 0">
OFFSET #{q.offset}
</if>
</if>
</sql>
<select id="pageChatMessage" resultType="com.hula.ai.gpt.pojo.vo.ChatMessageVO">

View File

@@ -22,20 +22,33 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
t.id, t.created_by, t.created_time, t.updated_by, t.updated_time, t.name, t.icon, t.model, t.version, t.local_model_type, t.model_url, t.knowledge, t.sort, t.status, t.del
t.id, t.created_by, t.created_time, t.updated_by, t.updated_time, t.name, t.icon, t.model, t.version, t.local_model_type, t.model_url, t.knowledge, t.sort, t.status, t.is_del
</sql>
<!-- 通用查询条件 -->
<sql id="BaseSelect">
select <include refid="Base_Column_List"></include>
from gpt_model t
from ai_gpt_model t
<where>
t.del = 0
<if test="model != null and model != ''"> and t.model = #{model}</if>
t.is_del = 0
<!-- 针对 pageModel 和 listModel -->
<if test="q != null and q.model != null and q.model != ''"> and t.model = #{q.model}</if>
</where>
order by t.sort desc, t.id desc
</sql>
<!-- 单条查询条件 -->
<sql id="GetSelect">
select <include refid="Base_Column_List"></include>
from ai_gpt_model t
where t.is_del = 0
<if test="model != null and model != ''">
and t.model = #{model}
</if>
order by t.sort desc, t.id desc
limit 1
</sql>
<select id="pageModel" resultType="com.hula.ai.gpt.pojo.vo.ModelVO">
<include refid="BaseSelect"></include>
</select>
@@ -45,8 +58,7 @@
</select>
<select id="getModel" resultType="com.hula.ai.gpt.pojo.vo.ModelVO">
<include refid="BaseSelect"></include>
limit 1
<include refid="GetSelect"></include>
</select>
</mapper>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -188,8 +188,8 @@ CREATE TABLE `ai_gpt_comb` (
`price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '价格',
`status` smallint NULL DEFAULT 1 COMMENT '状态 0 禁用 1 启用',
`remark` varchar(250) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '备注',
`created_by` bigint NOT NULL COMMENT '创建者',
`created_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`created_by` bigint DEFAULT 0 NOT NULL COMMENT '创建者',
`created_time` datetime DEFAULT (now()) NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_by` bigint NULL DEFAULT NULL COMMENT '更新者',
`updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`is_del` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',