refactor: 重命名 ruoyi 目录和文件为 hzhub

This commit is contained in:
2026-03-27 06:45:32 +00:00
parent f115a793a7
commit ff0ec6d2e3
1015 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-bom</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<description>
hzhub-common-bom common依赖项
</description>
<properties>
<revision>3.0.0</revision>
</properties>
<dependencyManagement>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- 对话模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-chat</artifactId>
<version>${revision}</version>
</dependency>
<!-- 接口模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-doc</artifactId>
<version>${revision}</version>
</dependency>
<!-- excel -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-excel</artifactId>
<version>${revision}</version>
</dependency>
<!-- 幂等 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-idempotent</artifactId>
<version>${revision}</version>
</dependency>
<!-- 调度模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-job</artifactId>
<version>${revision}</version>
</dependency>
<!-- 日志记录 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-log</artifactId>
<version>${revision}</version>
</dependency>
<!-- 邮件服务 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库服务 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-mybatis</artifactId>
<version>${revision}</version>
</dependency>
<!-- OSS -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-ratelimiter</artifactId>
<version>${revision}</version>
</dependency>
<!-- 缓存服务 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-redis</artifactId>
<version>${revision}</version>
</dependency>
<!-- satoken -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-satoken</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-security</artifactId>
<version>${revision}</version>
</dependency>
<!-- 短信模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-sms</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-social</artifactId>
<version>${revision}</version>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-web</artifactId>
<version>${revision}</version>
</dependency>
<!-- 翻译模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-translation</artifactId>
<version>${revision}</version>
</dependency>
<!-- 脱敏模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-sensitive</artifactId>
<version>${revision}</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-json</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库加解密模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-encrypt</artifactId>
<version>${revision}</version>
</dependency>
<!-- 租户模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-tenant</artifactId>
<version>${revision}</version>
</dependency>
<!-- WebSocket模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-websocket</artifactId>
<version>${revision}</version>
</dependency>
<!-- SSE模块 -->
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-sse</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hzhub-common-chat</artifactId>
<description>
hzhub-common-chat chat服务
</description>
<dependencies>
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-sse</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-excel</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
package org.hzhub.common.chat.base;
import cn.dev33.satoken.stp.StpUtil;
import org.apache.commons.lang3.StringUtils;
import org.hzhub.common.chat.entity.User;
import org.hzhub.common.chat.enums.UserStatusEnum;
import org.hzhub.common.core.domain.model.LoginUser;
import org.hzhub.common.core.exception.base.BaseException;
import org.hzhub.common.satoken.utils.LoginHelper;
import static org.hzhub.common.chat.enums.ErrorEnum.A_USER_NOT_FOUND;
/**
* 线程上下文适配器,统一接入 Sa-Token 登录态。
*/
public class ThreadContext {
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();
private static final ThreadLocal<String> CURRENT_TOKEN = new ThreadLocal<>();
private ThreadContext() {
}
/**
* 获取当前登录的工作流用户。
*/
public static User getCurrentUser() {
User cached = CURRENT_USER.get();
if (cached != null) {
return cached;
}
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new BaseException(A_USER_NOT_FOUND.getInfo());
}
User mapped = mapToWorkflowUser(loginUser);
CURRENT_USER.set(mapped);
return mapped;
}
/**
* 允许在测试或特殊场景下显式设置当前用户。
*/
public static void setCurrentUser(User user) {
if (user == null) {
CURRENT_USER.remove();
} else {
CURRENT_USER.set(user);
}
}
/**
* 获取当前登录用户 ID。
*/
public static Long getCurrentUserId() {
Long userId = LoginHelper.getUserId();
if (userId != null) {
return userId;
}
return getCurrentUser().getId();
}
/**
* 获取当前访问 token。
*/
public static String getToken() {
String token = CURRENT_TOKEN.get();
if (StringUtils.isNotBlank(token)) {
return token;
}
try {
token = StpUtil.getTokenValue();
} catch (Exception ignore) {
token = null;
}
if (StringUtils.isNotBlank(token)) {
CURRENT_TOKEN.set(token);
}
return token;
}
public static void setToken(String token) {
if (StringUtils.isBlank(token)) {
CURRENT_TOKEN.remove();
} else {
CURRENT_TOKEN.set(token);
}
}
public static boolean isLogin() {
return LoginHelper.isLogin();
}
public static User getExistCurrentUser() {
return getCurrentUser();
}
public static void unload() {
CURRENT_USER.remove();
CURRENT_TOKEN.remove();
}
private static User mapToWorkflowUser(LoginUser loginUser) {
User user = new User();
user.setId(loginUser.getUserId());
user.setName(loginUser.getUsername());
user.setEmail(loginUser.getUsername());
user.setUuid(String.valueOf(loginUser.getUserId()));
user.setUserStatus(UserStatusEnum.NORMAL);
user.setIsAdmin(LoginHelper.isSuperAdmin(loginUser.getUserId()));
user.setUnderstandContextMsgPairNum(0);
user.setQuotaByTokenDaily(0);
user.setQuotaByTokenMonthly(0);
user.setQuotaByRequestDaily(0);
user.setQuotaByRequestMonthly(0);
user.setQuotaByImageDaily(0);
user.setQuotaByImageMonthly(0);
user.setIsDeleted(false);
return user;
}
}

View File

@@ -0,0 +1,67 @@
package org.hzhub.common.chat.domain.bo.chat;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.chat.entity.chat.ChatMessage;
import org.hzhub.common.core.validate.AddGroup;
import org.hzhub.common.core.validate.EditGroup;
import org.hzhub.common.mybatis.core.domain.BaseEntity;
/**
* 聊天消息业务对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatMessage.class, reverseConvertGenerate = false)
public class ChatMessageBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
@NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,74 @@
package org.hzhub.common.chat.domain.bo.chat;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.chat.entity.chat.ChatModel;
import org.hzhub.common.core.validate.EditGroup;
import org.hzhub.common.mybatis.core.domain.BaseEntity;
/**
* 模型管理业务对象 chat_model
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatModel.class, reverseConvertGenerate = false)
public class ChatModelBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 模型分类
*/
private String category;
/**
* 模型名称
*/
private String modelName;
/**
* 模型供应商
*/
private String providerCode;
/**
* 模型描述
*/
private String modelDescribe;
/**
* 是否显示
*/
private String modelShow;
/**
* 向量维度
*/
private Integer modelDimension;
/**
* 请求地址
*/
private String apiHost;
/**
* 密钥
*/
private String apiKey;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,83 @@
package org.hzhub.common.chat.domain.dto.request;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* 对话请求对象
*
* @author ageerle
* @date 2023-04-08
*/
@Data
public class ChatRequest {
@NotEmpty(message = "传入的模型不能为空")
private String model;
/**
* 对话消息
*/
@NotEmpty(message = "对话消息不能为空")
private String content;
/**
* 工作流请求体
*/
private WorkFlowRunner workFlowRunner;
/**
* 人机交互信息体
*/
private ReSumeRunner reSumeRunner;
/**
* 是否为人机交互用户继续输入
*/
private Boolean isResume = false;
/**
* 是否启用工作流
*/
private Boolean enableWorkFlow = false;
/**
* 会话id
*/
@JsonSerialize(using = ToStringSerializer.class)
@JSONField(serializeUsing = String.class)
private Long sessionId;
/**
* 知识库id
*/
private String knowledgeId;
/**
* 应用ID
*/
private String appId;
/**
* 对话id(每个聊天窗口都不一样)
*/
@JsonSerialize(using = ToStringSerializer.class)
@JSONField(serializeUsing = String.class)
private Long uuid;
/**
* 是否启用深度思考
*/
private Boolean enableThinking = false;
/**
* 是否支持联网
*/
private Boolean enableInternet;
}

View File

@@ -0,0 +1,19 @@
package org.hzhub.common.chat.domain.dto.request;
import lombok.Data;
/**
* 人机交互输入信息
*/
@Data
public class ReSumeRunner {
/**
* 运行节点UUID
*/
private String runtimeUuid;
/**
* 人机交互用户输入信息
*/
private String feedbackContent;
}

View File

@@ -0,0 +1,15 @@
package org.hzhub.common.chat.domain.dto.request;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import java.util.List;
/**
* 工作流请求体信息
*/
@Data
public class WorkFlowRunner {
private List<ObjectNode> inputs;
private String uuid;
}

View File

@@ -0,0 +1,77 @@
package org.hzhub.common.chat.domain.vo.chat;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.hzhub.common.chat.entity.chat.ChatMessage;
import java.io.Serial;
import java.io.Serializable;
/**
* 聊天消息视图对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatMessage.class)
public class ChatMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 会话id
*/
@ExcelProperty(value = "会话id")
private Long sessionId;
/**
* 用户id
*/
@ExcelProperty(value = "用户id")
private Long userId;
/**
* 消息内容
*/
@ExcelProperty(value = "消息内容")
private String content;
/**
* 对话角色
*/
@ExcelProperty(value = "对话角色")
private String role;
/**
* 累计 Tokens
*/
@ExcelProperty(value = "累计 Tokens")
private Long totalTokens;
/**
* 模型名称
*/
@ExcelProperty(value = "模型名称")
private String modelName;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,89 @@
package org.hzhub.common.chat.domain.vo.chat;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.hzhub.common.chat.entity.chat.ChatModel;
import java.io.Serial;
import java.io.Serializable;
/**
* 模型管理视图对象 chat_model
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatModel.class)
public class ChatModelVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 模型分类
*/
@ExcelProperty(value = "模型分类")
private String category;
/**
* 模型名称
*/
@ExcelProperty(value = "模型名称")
private String modelName;
/**
* 模型供应商
*/
@ExcelProperty(value = "模型供应商")
private String providerCode;
/**
* 模型描述
*/
@ExcelProperty(value = "模型描述")
private String modelDescribe;
/**
* 是否显示
*/
@ExcelProperty(value = "是否显示")
private String modelShow;
/**
* 向量维度
*/
@ExcelProperty(value = "向量维度")
private Integer modelDimension;
/**
* 请求地址
*/
@ExcelProperty(value = "请求地址")
private String apiHost;
/**
* 密钥
*/
@ExcelProperty(value = "密钥")
private String apiKey;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,31 @@
package org.hzhub.common.chat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "create_time")
private LocalDateTime createTime;
@TableField(value = "update_time")
private LocalDateTime updateTime;
@Schema(title = "是否删除0未删除1已删除")
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -0,0 +1,66 @@
package org.hzhub.common.chat.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.chat.enums.UserStatusEnum;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("adi_user")
@Schema(title = "User对象")
public class User extends BaseEntity {
@Schema(name = "用户名称")
@TableField("name")
private String name;
@TableField("email")
private String email;
@TableField("password")
private String password;
@TableField("uuid")
private String uuid;
@Schema(name = "上下文理解中需要携带的消息对数量(提示词及回复)")
@TableField("understand_context_msg_pair_num")
private Integer understandContextMsgPairNum;
@Schema(name = "token quota in one day")
@TableField("quota_by_token_daily")
private Integer quotaByTokenDaily;
@Schema(name = "token quota in one month")
@TableField("quota_by_token_monthly")
private Integer quotaByTokenMonthly;
@Schema(name = "request quota in one day")
@TableField("quota_by_request_daily")
private Integer quotaByRequestDaily;
@Schema(name = "request quota in one month")
@TableField("quota_by_request_monthly")
private Integer quotaByRequestMonthly;
@TableField("quota_by_image_daily")
private Integer quotaByImageDaily;
@TableField("quota_by_image_monthly")
private Integer quotaByImageMonthly;
@TableField("user_status")
private UserStatusEnum userStatus;
@TableField("active_time")
private LocalDateTime activeTime;
@Schema(title = "是否管理员01")
@TableField(value = "is_admin")
private Boolean isAdmin;
}

View File

@@ -0,0 +1,68 @@
package org.hzhub.common.chat.entity.chat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.tenant.core.TenantEntity;
import java.io.Serial;
/**
* 聊天消息对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_message")
public class ChatMessage extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,78 @@
package org.hzhub.common.chat.entity.chat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.tenant.core.TenantEntity;
import java.io.Serial;
/**
* 模型管理对象 chat_model
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_model")
public class ChatModel extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 模型分类
*/
private String category;
/**
* 模型名称
*/
private String modelName;
/**
* 模型供应商
*/
private String providerCode;
/**
* 模型描述
*/
private String modelDescribe;
/**
* 是否显示
*/
private String modelShow;
/**
* 向量维度
*/
private Integer modelDimension;
/**
* 请求地址
*/
private String apiHost;
/**
* 密钥
*/
private String apiKey;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,45 @@
package org.hzhub.common.chat.entity.image;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hzhub.common.chat.domain.vo.chat.ChatModelVo;
/**
* 文生图对话上下文对象
*
* @author zengxb
* @date 2026-02-14
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class ImageContext {
/**
* 模型管理视图对象
*/
@NotNull(message = "模型管理视图对象不能为空")
private ChatModelVo chatModelVo;
/**
* 提示词
*/
@NotNull(message = "提示词不能为空")
private String prompt;
/**
* 图片尺寸大小
*/
private String size;
/**
* 随机数种子
*/
@Min(value = 0, message = "随机数种子不能小于0")
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
private Integer seed;
}

View File

@@ -0,0 +1,12 @@
package org.hzhub.common.chat.enums;
import com.baomidou.mybatisplus.annotation.IEnum;
public interface BaseEnum extends IEnum<Integer> {
/**
* 获取对应名称
*
* @return String
*/
String getDesc();
}

View File

@@ -0,0 +1,127 @@
package org.hzhub.common.chat.enums;
import lombok.Getter;
@Getter
public enum ErrorEnum {
SUCCESS("00000", "成功"),
A_URL_NOT_FOUND("A0001", "地址不存在"),
A_PARAMS_ERROR("A0002", "参数校验不通过"),
A_REQUEST_TOO_MUCH("A0003", "访问次数太多"),
A_LOGIN_ERROR("A0004", "登陆失败,账号或密码错误"),
A_LOGIN_ERROR_MAX("A0005", "失败次数太多,请输入验证码重试"),
A_LOGIN_CAPTCHA_ERROR("A0006", "验证码不正确"),
A_USER_NOT_EXIST("A0007", "用户不存在"),
A_CONVERSATION_NOT_EXIST("A0008", "对话不存在"),
A_IMAGE_NUMBER_ERROR("A0009", "图片数量不对"),
A_IMAGE_SIZE_ERROR("A0010", "图片尺寸不对"),
A_FILE_NOT_EXIST("A0011", "文件不存在"),
A_DRAWING("A0012", "作图还未完成"),
A_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
A_USER_NOT_AUTH("A0016", "用户无权限"),
A_DATA_NOT_FOUND("A0017", "数据不存在"),
A_UPLOAD_FAIL("A0018", "上传失败"),
A_QA_ASK_LIMIT("A0019", "请求次数太多"),
A_QA_ITEM_LIMIT("A0020", "知识点生成已超额度"),
A_CONVERSATION_EXIST("A0021", "会话(角色)已存在"),
A_MODEL_NOT_FOUND("A0022", "模型不存在"),
A_MODEL_ALREADY_EXIST("A0023", "模型已存在"),
A_CONVERSATION_NOT_FOUND("A0024", "会话(角色)找不到"),
A_AI_IMAGE_NOT_FOUND("A0024", "图片找不到"),
A_ENABLE_MODEL_NOT_FOUND("A0025", "没有可用的模型"),
A_DOC_INDEX_DOING("A0026", "文档索引正在进行中,请稍后重试"),
A_PRESET_CONVERSATION_NOT_EXIST("A0027", "预设会话或角色不存在"),
A_CONVERSATION_TITLE_EXIST("A0028", "会话(角色)标题已存在"),
A_AI_IMAGE_NO_AUTH("A0029", "无权限查看该图片"),
A_USER_NOT_FOUND("A0030", "用户不存在"),
A_ACTIVE_CODE_INVALID("A0031", "激活码已失效"),
A_OLD_PASSWORD_INVALID("A0032", "原密码不正确"),
A_OPT_TOO_FREQUENTLY("A0032", "操作太频繁"),
A_DRAW_NOT_FOUND("A00033", "绘图记录找不到"),
A_WF_NOT_FOUND("A00034", "工作流找不到"),
A_WF_DISABLED("A0035", "工作流已停用"),
A_WF_NODE_NOT_FOUND("A0036", "工作流节点找不到"),
A_WF_NODE_CONFIG_NOT_FOUND("A0037", "工作流节点配置找不到"),
A_WF_NODE_CONFIG_ERROR("A0038", "工作流节点配置异常"),
A_WF_INPUT_INVALID("A0039", "工作流输入参数错误"),
A_WF_INPUT_MISSING("A0040", "工作流输入缺少参数"),
A_WF_MULTIPLE_START_NODE("A0041", "多个开始节点"),
A_WF_START_NODE_NOT_FOUND("A0042", "没有开始节点"),
A_WF_END_NODE_NOT_FOUND("A0043", "没有结束节点"),
A_WF_EDGE_NOT_FOUND("A0044", "工作流的边找不到"),
A_WF_RUNTIME_NOT_FOUND("A00045", "工作流运行时数据找不到"),
A_SEARCH_QUERY_IS_EMPTY("A00046", "搜索内容不能为空"),
A_WF_COMPONENT_NOT_FOUND("A00047", "工作流基础组件找不到"),
A_WF_RESUME_FAIL("A00048", "工作流恢复执行时失败"),
A_MAIL_SENDER_EMPTY("A00049", "邮件发送人不能为空"),
A_MAIL_SENDER_CONFIG_ERROR("A00050", "邮件发送人配置错误"),
A_MAIL_RECEIVER_EMPTY("A00051", "邮件接收人不能为空"),
A_MCP_SERVER_NOT_FOUND("A00052", "MCP服务找不到"),
A_USER_MCP_SERVER_NOT_FOUND("A00053", "用户的MCP服务找不到"),
A_PARAMS_INVALID_BY_("A00054", "参数校验异常:{0}"),
A_AI_MESSAGE_NOT_FOUND("A00055", "找不到AI的消息"),
A_USER_QUESTION_NOT_FOUND("A00056", "用户问题不存在"),
A_PLATFORM_NOT_MATCH("A0057", "平台不匹配"),
B_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
B_COMMON_ERROR("B0002", "业务出错"),
B_GLOBAL_ERROR("B0003", "全局异常"),
B_SAVE_IMAGE_ERROR("B0004", "保存图片异常"),
B_FIND_IMAGE_404("B0005", "无法找到图片"),
B_DAILY_QUOTA_USED("B0006", "今天额度已经用完"),
B_MONTHLY_QUOTA_USED("B0007", "当月额度已经用完"),
B_LLM_NOT_SUPPORT("B0008", "LLM不支持该功能"),
B_LLM_SECRET_KEY_NOT_SET("B0009", "LLM的secret key没设置"),
B_MESSAGE_NOT_FOUND("B0008", "消息不存在"),
B_LLM_SERVICE_DISABLED("B0009", "LLM服务不可用"),
B_KNOWLEDGE_BASE_IS_EMPTY("B0010", "知识库内容为空"),
B_NO_ANSWER("B0011", "[无答案]"),
B_SAVE_FILE_ERROR("B0012", "保存文件异常"),
B_BREAK_SEARCH("B0013", "中断搜索"),
B_GRAPH_FILTER_NOT_FOUND("B0014", "图过滤器未定义"),
B_DB_ERROR("B0015", "数据库查询异常"),
B_ACTIVE_USER_ERROR("B0016", "激活用户失败"),
B_RESET_PASSWORD_ERROR("B0017", "重置密码失败"),
B_IMAGE_LOAD_ERROR("B0018", "加载图片失败"),
B_IO_EXCEPTION("B0019", "IO异常"),
B_SERVER_EXCEPTION("B0020", "服务端异常"),
B_DELETE_FILE_ERROR("B0021", "删除文件异常"),
B_WF_RUN_ERROR("B0022", "工作流运行异常"),
B_WF_NODE_DEFINITION_NOT_FOUND("B0023", "工作流节点定义找不到"),
B_DIR_CREATE_FAIL("B0024", "创建目录失败"),
B_LLM_TEMPERATURE_ERROR("B0025", "采样温度应该在 0.1-1之间"),
B_ASR_SETTING_NOT_FOUND("B0026", "语音识别设置未找到"),
B_URL_INVALID("B0027", "不是有效的网络地址"),
B_ASR_MODEL_NOT_FOUND("B0028", "语音识别模型未找到"),
B_TTS_SETTING_NOT_FOUND("B0029", "语音合成设置未找到"),
B_TTS_MODEL_NOT_FOUND("B0030", "语音合成模型未找到"),
B_VOICE_NOT_FOUND("B0031", "声音不存在"),
C_DRAW_FAIL("C0001", "大模型生成图片失败,原因:{0}"),
C_ALI_OSS_CONFIG_ERROR("C0002", "阿里云OSS初始化失败,原因:{0}"),
C_LLM_RESPONSE_INVALID("C0003", "大模型生成结果内容无效"),
C_WF_COMPONENT_DELETED_FAIL_BY_USED("C0004", "工作流组件已经被使用,无法被删除,可先停用");
private final String code;
private final String info;
ErrorEnum(String code, String info) {
this.code = code;
this.info = info;
}
public static ErrorEnum getErrorEnum(String code) {
ErrorEnum result = null;
for (ErrorEnum c : ErrorEnum.values()) {
if (c.getCode().equals(code)) {
result = c;
break;
}
}
if (null == result) {
result = B_COMMON_ERROR;
}
return result;
}
}

View File

@@ -0,0 +1,26 @@
package org.hzhub.common.chat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 角色枚举
*
* @author ageerle@163.com
* @date 2025-12-17
*/
@Getter
@AllArgsConstructor
public enum RoleType {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
FUNCTION("function"),
TOOL("tool"),
WORKFLOW("workFlow")
;
private final String name;
}

View File

@@ -0,0 +1,23 @@
package org.hzhub.common.chat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum UserStatusEnum implements BaseEnum {
WAIT_CONFIRM(1, "待验证"),
NORMAL(2, "正常"),
FREEZE(3, "冻结");
private final Integer value;
private final String desc;
public static UserStatusEnum getByValue(Integer val) {
return Arrays.stream(UserStatusEnum.values()).filter(item -> item.value.equals(val)).findFirst().orElse(null);
}
}

View File

@@ -0,0 +1,45 @@
package org.hzhub.common.chat.factory;
import org.hzhub.common.chat.service.image.IImageGenerationService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 文生图服务工厂类
*
* @author zengxb
* @date 2026-02-14
*/
@Component
public class ImageServiceFactory implements ApplicationContextAware {
private final Map<String, IImageGenerationService> imageSerivceMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 初始化时收集所有IImageGenerationService的实现
Map<String, IImageGenerationService> serviceMap = applicationContext.getBeansOfType(IImageGenerationService.class);
for (IImageGenerationService service : serviceMap.values()) {
if (service != null ) {
imageSerivceMap.put(service.getProviderName(), service);
}
}
}
/**
* 获取原始服务(不包装代理)
*/
public IImageGenerationService getOriginalService(String category) {
IImageGenerationService service = imageSerivceMap.get(category);
if (service == null) {
throw new IllegalArgumentException("不支持的模型类别: " + category);
}
return service;
}
}

View File

@@ -0,0 +1,76 @@
package org.hzhub.common.chat.service.chat;
import org.hzhub.common.chat.domain.bo.chat.ChatModelBo;
import org.hzhub.common.chat.domain.vo.chat.ChatModelVo;
import org.hzhub.common.mybatis.core.page.PageQuery;
import org.hzhub.common.mybatis.core.page.TableDataInfo;
import java.util.Collection;
import java.util.List;
/**
* 模型管理Service接口
*
* @author ageerle
* @date 2025-12-14
*/
public interface IChatModelService {
/**
* 查询模型管理
*
* @param id 主键
* @return 模型管理
*/
ChatModelVo queryById(Long id);
/**
* 根据模型名称查询模型
*
* @param modelName 模型名称
* @return 模型管理
*/
ChatModelVo selectModelByName(String modelName);
/**
* 分页查询模型管理列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 模型管理分页列表
*/
TableDataInfo<ChatModelVo> queryPageList(ChatModelBo bo, PageQuery pageQuery);
/**
* 查询符合条件的模型管理列表
*
* @param bo 查询条件
* @return 模型管理列表
*/
List<ChatModelVo> queryList(ChatModelBo bo);
/**
* 新增模型管理
*
* @param bo 模型管理
* @return 是否新增成功
*/
Boolean insertByBo(ChatModelBo bo);
/**
* 修改模型管理
*
* @param bo 模型管理
* @return 是否修改成功
*/
Boolean updateByBo(ChatModelBo bo);
/**
* 校验并批量删除模型管理信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,27 @@
package org.hzhub.common.chat.service.chat;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.validation.Valid;
import org.hzhub.common.chat.domain.dto.request.ChatRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 公共大模型对话接口
*/
public interface IChatService {
/**
* 客户端发送对话消息到服务端
*/
SseEmitter chat(@Valid ChatRequest chatRequest);
/**
* 支持外部 handler 的对话接口(跨模块调用)
* 同时发送到 SSE 和外部 handler
*
* @param chatRequest 聊天请求
* @param externalHandler 外部响应处理器(可为 null
*/
void chat(@Valid ChatRequest chatRequest, StreamingChatResponseHandler externalHandler);
}

View File

@@ -0,0 +1,20 @@
package org.hzhub.common.chat.service.image;
import jakarta.validation.Valid;
import org.hzhub.common.chat.entity.image.ImageContext;
/**
* 公共文生图接口
*/
public interface IImageGenerationService {
/**
* 根据文字生成图片
*/
String generateImage(@Valid ImageContext imageContext);
/**
* 获取服务提供商名称
*/
String getProviderName();
}

View File

@@ -0,0 +1,33 @@
package org.hzhub.common.chat.service.workFlow;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.hzhub.common.chat.entity.User;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* 工作流启动Service接口
*
* @author Zengxb
* @date 2026-02-24
*/
public interface IWorkFlowStarterService {
/**
* 启动工作流
* @param user 用户
* @param workflowUuid 工作流UUID
* @param userInputs 用户输入信息
* @return 流式输出结果
*/
SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs, Long sessionId);
/**
* 恢复工作流
* @param runtimeUuid 运行时UUID
* @param userInput 用户输入
* @param sseEmitter SSE连接对象
*/
void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter);
}

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.hzhub</groupId>
<artifactId>hzhub-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hzhub-common-core</artifactId>
<description>
hzhub-common-core 核心模块
</description>
<dependencies>
<!-- Spring框架基本的核心工具 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- 自定义验证注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- servlet包 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 自动生成YML配置关联JSON文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
</dependency>
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
package org.hzhub.common.core.config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 程序注解配置
*
* @author Lion Li
*/
@AutoConfiguration
@EnableAspectJAutoProxy
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig {
}

View File

@@ -0,0 +1,112 @@
package org.hzhub.common.core.config;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.hzhub.common.core.config.properties.ThreadPoolProperties;
import org.hzhub.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import java.util.concurrent.*;
/**
* 线程池配置
*
* @author Lion Li
**/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig {
/**
* 核心线程数 = cpu 核心数 + 1
*/
private final int core = Runtime.getRuntime().availableProcessors() + 1;
private ScheduledExecutorService scheduledExecutorService;
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
// daemon 必须为 true
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
if (SpringUtils.isVirtual()) {
builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
} else {
builder.namingPattern("schedule-pool-%d");
}
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
builder.build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
}
};
this.scheduledExecutorService = scheduledThreadPoolExecutor;
return scheduledThreadPoolExecutor;
}
/**
* 销毁事件
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
@PreDestroy
public void destroy() {
try {
log.info("====关闭后台任务任务线程池====");
ScheduledExecutorService pool = scheduledExecutorService;
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();
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 打印线程异常信息
*/
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);
}
}
}

View File

@@ -0,0 +1,41 @@
package org.hzhub.common.core.config;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Properties;
/**
* 校验框架配置类
*
* @author Lion Li
*/
@AutoConfiguration(before = ValidationAutoConfiguration.class)
public class ValidatorConfig {
/**
* 配置校验框架 快速失败模式
*/
@Bean
public Validator validator(MessageSource messageSource) {
try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化
factoryBean.setValidationMessageSource(messageSource);
// 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties();
// 设置快速失败模式fail-fast即校验过程中一旦遇到失败立即停止并返回错误
properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties);
// 加载配置
factoryBean.afterPropertiesSet();
return factoryBean.getValidator();
}
}
}

View File

@@ -0,0 +1,30 @@
package org.hzhub.common.core.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 线程池 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {
/**
* 是否开启线程池
*/
private boolean enabled;
/**
* 队列最大长度
*/
private int queueCapacity;
/**
* 线程池维护线程所允许的空闲时间
*/
private int keepAliveSeconds;
}

View File

@@ -0,0 +1,30 @@
package org.hzhub.common.core.constant;
/**
* 缓存的key 常量
*
* @author Lion Li
*/
public interface CacheConstants {
/**
* 在线用户 redis key
*/
String ONLINE_TOKEN_KEY = "online_tokens:";
/**
* 参数管理 cache key
*/
String SYS_CONFIG_KEY = "sys_config:";
/**
* 字典管理 cache key
*/
String SYS_DICT_KEY = "sys_dict:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

View File

@@ -0,0 +1,89 @@
package org.hzhub.common.core.constant;
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* local 默认开启本地缓存为1 关闭本地缓存为0
* <p>
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
*
* @author Lion Li
*/
public interface CacheNames {
/**
* 演示案例
*/
String DEMO_CACHE = "demo:cache#60s#10m#20";
/**
* 系统配置
*/
String SYS_CONFIG = "sys_config";
/**
* 数据字典
*/
String SYS_DICT = "sys_dict";
/**
* 数据字典类型
*/
String SYS_DICT_TYPE = "sys_dict_type";
/**
* 租户
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/**
* 客户端
*/
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
/**
* 用户账户
*/
String SYS_USER_NAME = "sys_user_name#30d";
/**
* 用户名称
*/
String SYS_NICKNAME = "sys_nickname#30d";
/**
* 部门
*/
String SYS_DEPT = "sys_dept#30d";
/**
* OSS内容
*/
String SYS_OSS = "sys_oss#30d";
/**
* 角色自定义权限
*/
String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
/**
* 部门及以下权限
*/
String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d";
/**
* OSS配置
*/
String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
/**
* 在线用户
*/
String ONLINE_TOKEN = "online_tokens";
}

View File

@@ -0,0 +1,81 @@
package org.hzhub.common.core.constant;
/**
* 通用常量信息
*
* @author ruoyi
*/
public interface Constants {
/**
* UTF-8 字符集
*/
String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
String GBK = "GBK";
/**
* www主域
*/
String WWW = "www.";
/**
* http请求
*/
String HTTP = "http://";
/**
* https请求
*/
String HTTPS = "https://";
/**
* 通用成功标识
*/
String SUCCESS = "0";
/**
* 通用失败标识
*/
String FAIL = "1";
/**
* 登录成功
*/
String LOGIN_SUCCESS = "Success";
/**
* 注销
*/
String LOGOUT = "Logout";
/**
* 注册
*/
String REGISTER = "Register";
/**
* 登录失败
*/
String LOGIN_FAIL = "Error";
/**
* 验证码有效期(分钟)
*/
Integer CAPTCHA_EXPIRATION = 2;
/**
* 顶级父级id
*/
Long TOP_PARENT_ID = 0L;
/**
* 加密头
*/
String ENCRYPT_HEADER = "ENC_";
}

View File

@@ -0,0 +1,34 @@
package org.hzhub.common.core.constant;
/**
* 全局的key常量 (业务无关的key)
*
* @author Lion Li
*/
public interface GlobalConstants {
/**
* 全局 redis key (业务无关的key)
*/
String GLOBAL_REDIS_KEY = "global:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 防重提交 redis key
*/
String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 三方认证 redis key
*/
String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
}

View File

@@ -0,0 +1,93 @@
package org.hzhub.common.core.constant;
/**
* 返回状态码
*
* @author Lion Li
*/
public interface HttpStatus {
/**
* 操作成功
*/
int SUCCESS = 200;
/**
* 对象创建成功
*/
int CREATED = 201;
/**
* 请求已经被接受
*/
int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
int NO_CONTENT = 204;
/**
* 资源已被移除
*/
int MOVED_PERM = 301;
/**
* 重定向
*/
int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
int BAD_REQUEST = 400;
/**
* 未授权
*/
int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
int ERROR = 500;
/**
* 接口未实现
*/
int NOT_IMPLEMENTED = 501;
/**
* 系统警告消息
*/
int WARN = 601;
}

View File

@@ -0,0 +1,59 @@
package org.hzhub.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
* 常用正则表达式字符串
* <p>
* 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
*
* @author Feng
*/
public interface RegexConstants extends RegexPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 权限标识必须符合以下格式:
* 1. 标准格式xxx:yyy:zzz
* - 第一部分xxx只能包含字母、数字和下划线_不能使用 `*`
* - 第二部分yyy可以包含字母、数字、下划线_和 `*`
* - 第三部分zzz可以包含字母、数字、下划线_和 `*`
* 2. 允许空字符串(""),表示没有权限标识
*/
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
/**
* 身份证号码后6位
*/
String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
/**
* QQ号码
*/
String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
/**
* 邮政编码
*/
String POSTAL_CODE = "^[1-9]\\d{5}$";
/**
* 注册账号
*/
String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
/**
* 密码包含至少8个字符包括大写字母、小写字母、数字和特殊字符
*/
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
/**
* 通用状态0表示正常1表示停用
*/
String STATUS = "^[01]$";
}

View File

@@ -0,0 +1,85 @@
package org.hzhub.common.core.constant;
/**
* 系统常量信息
*
* @author Lion Li
*/
public interface SystemConstants {
/**
* 正常状态
*/
String NORMAL = "0";
/**
* 异常状态
*/
String DISABLE = "1";
/**
* 是否为系统默认(是)
*/
String YES = "Y";
/**
* 是否为系统默认(否)
*/
String NO = "N";
/**
* 是否菜单外链(是)
*/
String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 根部门祖级列表
*/
String ROOT_DEPT_ANCESTORS = "0";
/**
* 默认部门 ID
*/
Long DEFAULT_DEPT_ID = 100L;
}

View File

@@ -0,0 +1,35 @@
package org.hzhub.common.core.constant;
/**
* 租户常量信息
*
* @author Lion Li
*/
public interface TenantConstants {
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "admin";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "管理员";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
}

View File

@@ -0,0 +1,110 @@
package org.hzhub.common.core.domain;
import org.hzhub.common.core.constant.HttpStatus;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 响应信息主体
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 成功
*/
public static final int SUCCESS = 200;
/**
* 失败
*/
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> R<T> ok(String msg, T data) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail() {
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg, T data) {
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static <T> R<T> warn(String msg) {
return restResult(null, HttpStatus.WARN, msg);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static <T> R<T> warn(String msg, T data) {
return restResult(data, HttpStatus.WARN, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> r = new R<>();
r.setCode(code);
r.setData(data);
r.setMsg(msg);
return r;
}
public static <T> Boolean isError(R<T> ret) {
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret) {
return R.SUCCESS == ret.getCode();
}
}

View File

@@ -0,0 +1,76 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 办理任务请求对象
*
* @author may
*/
@Data
public class CompleteTaskDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 任务id
*/
private Long taskId;
/**
* 附件id
*/
private String fileId;
/**
* 抄送人员
*/
private List<FlowCopyDTO> flowCopyList;
/**
* 消息类型
*/
private List<String> messageType;
/**
* 办理意见
*/
private String message;
/**
* 消息通知
*/
private String notice;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量
*/
private Map<String, Object> variables;
/**
* 扩展变量(此处为逗号分隔的ossId)
*/
private String ext;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
}

View File

@@ -0,0 +1,36 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 部门
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门ID
*/
private Long deptId;
/**
* 父部门ID
*/
private Long parentId;
/**
* 部门名称
*/
private String deptName;
}

View File

@@ -0,0 +1,41 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典数据DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictDataDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典标签
*/
private String dictLabel;
/**
* 字典键值
*/
private String dictValue;
/**
* 是否默认Y是 N否
*/
private String isDefault;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,41 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 字典类型DTO
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DictTypeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典主键
*/
private Long dictId;
/**
* 字典名称
*/
private String dictName;
/**
* 字典类型
*/
private String dictType;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,30 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 抄送
*
* @author may
*/
@Data
public class FlowCopyDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户id
*/
private Long userId;
/**
* 用户名称
*/
private String userName;
}

View File

@@ -0,0 +1,45 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 流程实例业务扩展对象
*
* @author may
* @date 2025-08-05
*/
@Data
public class FlowInstanceBizExtDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 流程实例ID
*/
private Long instanceId;
/**
* 业务ID
*/
private String businessId;
/**
* 业务编码
*/
private String businessCode;
/**
* 业务标题
*/
private String businessTitle;
}

View File

@@ -0,0 +1,46 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* OSS对象
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class OssDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 对象存储主键
*/
private Long ossId;
/**
* 文件名
*/
private String fileName;
/**
* 原名
*/
private String originalName;
/**
* 文件后缀名
*/
private String fileSuffix;
/**
* URL地址
*/
private String url;
}

View File

@@ -0,0 +1,46 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 岗位
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class PostDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 岗位ID
*/
private Long postId;
/**
* 部门id
*/
private Long deptId;
/**
* 岗位编码
*/
private String postCode;
/**
* 岗位名称
*/
private String postName;
/**
* 岗位类别编码
*/
private String postCategory;
}

View File

@@ -0,0 +1,42 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 角色
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class RoleDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限
*/
private String roleKey;
/**
* 数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限 5仅本人数据权限 6部门及以下或本人数据权限
*/
private String dataScope;
}

View File

@@ -0,0 +1,63 @@
package org.hzhub.common.core.domain.dto;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 启动流程对象
*
* @author may
*/
@Data
public class StartProcessDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务唯一值id
*/
private String businessId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 办理人(可不填 用于覆盖当前节点办理人)
*/
private String handler;
/**
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
*/
private Map<String, Object> variables;
/**
* 流程业务扩展信息
*/
private FlowInstanceBizExtDTO bizExt;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
public FlowInstanceBizExtDTO getBizExt() {
if (ObjectUtil.isNull(bizExt)) {
bizExt = new FlowInstanceBizExtDTO();
}
return bizExt;
}
}

View File

@@ -0,0 +1,30 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 启动流程返回对象
*
* @author Lion Li
*/
@Data
public class StartProcessReturnDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程实例id
*/
private Long processInstanceId;
/**
* 任务id
*/
private Long taskId;
}

View File

@@ -0,0 +1,101 @@
package org.hzhub.common.core.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 任务受让人
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class TaskAssigneeDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 总大小
*/
private Long total = 0L;
/**
*
*/
private List<TaskHandler> list;
public TaskAssigneeDTO(Long total, List<TaskHandler> list) {
this.total = total;
this.list = list;
}
/**
* 将源列表转换为 TaskHandler 列表
*
* @param <T> 通用类型
* @param sourceList 待转换的源列表
* @param storageId 提取 storageId 的函数
* @param handlerCode 提取 handlerCode 的函数
* @param handlerName 提取 handlerName 的函数
* @param groupName 提取 groupName 的函数
* @param createTimeMapper 提取 createTime 的函数
* @return 转换后的 TaskHandler 列表
*/
public static <T> List<TaskHandler> convertToHandlerList(
List<T> sourceList,
Function<T, String> storageId,
Function<T, String> handlerCode,
Function<T, String> handlerName,
Function<T, String> groupName,
Function<T, Date> createTimeMapper) {
return sourceList.stream()
.map(item -> new TaskHandler(
storageId.apply(item),
handlerCode.apply(item),
handlerName.apply(item),
groupName.apply(item),
createTimeMapper.apply(item)
)).collect(Collectors.toList());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TaskHandler {
/**
* 主键
*/
private String storageId;
/**
* 权限编码
*/
private String handlerCode;
/**
* 权限名称
*/
private String handlerName;
/**
* 权限分组
*/
private String groupName;
/**
* 创建时间
*/
private Date createTime;
}
}

View File

@@ -0,0 +1,73 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class UserDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型sys_user系统用户
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别0男 1女 2未知
*/
private String sex;
/**
* 帐号状态0正常 1停用
*/
private String status;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,72 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 当前在线会话
*
* @author ruoyi
*/
@Data
@NoArgsConstructor
public class UserOnlineDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 会话编号
*/
private String tokenId;
/**
* 部门名称
*/
private String deptName;
/**
* 用户名称
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地址
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private Long loginTime;
}

View File

@@ -0,0 +1,28 @@
package org.hzhub.common.core.domain.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hzhub.common.core.domain.model.LoginUser;
import java.io.Serial;
/**
* 小程序登录用户信息
*
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class VisitorLoginUserDto extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
}

View File

@@ -0,0 +1,34 @@
package org.hzhub.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 删除流程监听
*
* @author AprilWind
*/
@Data
public class ProcessDeleteEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 业务id
*/
private String businessId;
}

View File

@@ -0,0 +1,70 @@
package org.hzhub.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 总体流程监听
*
* @author may
*/
@Data
public class ProcessEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/
private String businessId;
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程状态
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
/**
* 当为true时为申请人节点办理
*/
private Boolean submit;
}

View File

@@ -0,0 +1,70 @@
package org.hzhub.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
/**
* 流程任务监听
*
* @author may
*/
@Data
public class ProcessTaskEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 流程定义编码
*/
private String flowCode;
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 任务id
*/
private Long taskId;
/**
* 实例id
*/
private Long instanceId;
/**
* 业务id
*/
private String businessId;
/**
* 流程状态
*/
private String status;
/**
* 办理参数
*/
private Map<String, Object> params;
}

View File

@@ -0,0 +1,31 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 邮件登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class EmailLoginBody extends LoginBody {
/**
* 邮箱
*/
@NotBlank(message = "{user.email.not.blank}")
@Email(message = "{user.email.not.valid}")
private String email;
/**
* 邮箱code
*/
@NotBlank(message = "{email.code.not.blank}")
private String emailCode;
}

View File

@@ -0,0 +1,48 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 用户登录对象
*
* @author Lion Li
*/
@Data
public class LoginBody implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 客户端id
*/
@NotBlank(message = "{auth.clientid.not.blank}")
private String clientId;
/**
* 授权类型
*/
@NotBlank(message = "{auth.grant.type.not.blank}")
private String grantType;
/**
* 租户ID
*/
private String tenantId;
/**
* 验证码
*/
private String code;
/**
* 唯一标识
*/
private String uuid;
}

View File

@@ -0,0 +1,148 @@
package org.hzhub.common.core.domain.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hzhub.common.core.domain.dto.PostDTO;
import org.hzhub.common.core.domain.dto.RoleDTO;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 登录用户身份权限
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门类别编码
*/
private String deptCategory;
/**
* 部门名
*/
private String deptName;
/**
* 用户唯一标识
*/
private String token;
/**
* 用户类型
*/
private String userType;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 菜单权限
*/
private Set<String> menuPermission;
/**
* 角色权限
*/
private Set<String> rolePermission;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 角色对象
*/
private List<RoleDTO> roles;
/**
* 岗位对象
*/
private List<PostDTO> posts;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 获取登录id
*/
public String getLoginId() {
if (userType == null) {
throw new IllegalArgumentException("用户类型不能为空");
}
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userType + ":" + userId;
}
}

View File

@@ -0,0 +1,32 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
}

View File

@@ -0,0 +1,37 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 用户注册对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}

View File

@@ -0,0 +1,29 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 短信登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsLoginBody extends LoginBody {
/**
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
}

View File

@@ -0,0 +1,35 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SocialLoginBody extends LoginBody {
/**
* 第三方登录平台
*/
@NotBlank(message = "{social.source.not.blank}")
private String source;
/**
* 第三方登录code
*/
@NotBlank(message = "{social.code.not.blank}")
private String socialCode;
/**
* 第三方登录socialState
*/
@NotBlank(message = "{social.state.not.blank}")
private String socialState;
}

View File

@@ -0,0 +1,56 @@
package org.hzhub.common.core.domain.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 任务受让人
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class TaskAssigneeBody implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 权限编码
*/
private String handlerCode;
/**
* 权限名称
*/
private String handlerName;
/**
* 权限分组
*/
private String groupId;
/**
* 开始时间
*/
private String beginTime;
/**
* 结束时间
*/
private String endTime;
/**
* 当前页
*/
private Integer pageNum = 1;
/**
* 每页显示条数
*/
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,28 @@
package org.hzhub.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class XcxLoginBody extends LoginBody {
/**
* 小程序id(多个小程序时使用)
*/
private String appid;
/**
* 小程序code
*/
@NotBlank(message = "{xcx.code.not.blank}")
private String xcxCode;
}

View File

@@ -0,0 +1,27 @@
package org.hzhub.common.core.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 小程序登录用户身份权限
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class XcxLoginUser extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
}

View File

@@ -0,0 +1,215 @@
package org.hzhub.common.core.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hzhub.common.core.exception.ServiceException;
import org.hzhub.common.core.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 业务状态枚举
*
* @author may
*/
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
/**
* 已撤销
*/
CANCEL("cancel", "已撤销"),
/**
* 草稿
*/
DRAFT("draft", "草稿"),
/**
* 待审核
*/
WAITING("waiting", "待审核"),
/**
* 已完成
*/
FINISH("finish", "已完成"),
/**
* 已作废
*/
INVALID("invalid", "已作废"),
/**
* 已退回
*/
BACK("back", "已退回"),
/**
* 已终止
*/
TERMINATION("termination", "已终止");
/**
* 状态
*/
private final String status;
/**
* 描述
*/
private final String desc;
private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values())
.collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity()));
/**
* 根据状态获取对应的 BusinessStatusEnum 枚举
*
* @param status 业务状态码
* @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null
*/
public static BusinessStatusEnum getByStatus(String status) {
// 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null
return STATUS_MAP.get(status);
}
/**
* 根据状态获取对应的业务状态描述信息
*
* @param status 业务状态码
* @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串
*/
public static String findByStatus(String status) {
if (StringUtils.isBlank(status)) {
return StrUtil.EMPTY;
}
BusinessStatusEnum statusEnum = STATUS_MAP.get(status);
return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY;
}
/**
* 判断是否为指定的状态之一:草稿、已撤销或已退回
*
* @param status 要检查的状态
* @return 如果状态为草稿、已撤销或已退回之一,则返回 true否则返回 false
*/
public static boolean isDraftOrCancelOrBack(String status) {
return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status);
}
/**
* 判断是否为撤销,退回,作废,终止
*
* @param status status
* @return 结果
*/
public static boolean initialState(String status) {
return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status);
}
/**
* 获取运行中的实例状态列表
*
* @return 包含运行中实例状态的不可变列表
* (包含 DRAFT、WAITING、BACK 和 CANCEL 状态)
*/
public static List<String> runningStatus() {
return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status);
}
/**
* 获取结束实例的状态列表
*
* @return 包含结束实例状态的不可变列表
* (包含 FINISH、INVALID 和 TERMINATION 状态)
*/
public static List<String> finishStatus() {
return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status);
}
/**
* 启动流程校验
*
* @param status 状态
*/
public static void checkStartStatus(String status) {
if (WAITING.getStatus().equals(status)) {
throw new ServiceException("该单据已提交过申请,正在审批中!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 撤销流程校验
*
* @param status 状态
*/
public static void checkCancelStatus(String status) {
if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 驳回流程校验
*
* @param status 状态
*/
public static void checkBackStatus(String status) {
if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 作废,终止流程校验
*
* @param status 状态
*/
public static void checkInvalidStatus(String status) {
if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
}

View File

@@ -0,0 +1,39 @@
package org.hzhub.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 设备类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum DeviceType {
/**
* pc端
*/
PC("pc"),
/**
* app端
*/
APP("app"),
/**
* 小程序端
*/
XCX("xcx"),
/**
* 第三方社交登录平台
*/
SOCIAL("social");
/**
* 设备标识
*/
private final String device;
}

View File

@@ -0,0 +1,146 @@
package org.hzhub.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hzhub.common.core.utils.StringUtils;
/*
* 日期格式
* "yyyy"4位数的年份例如2023年表示为"2023"。
* "yy"2位数的年份例如2023年表示为"23"。
* "MM"2位数的月份取值范围为01到12例如7月表示为"07"。
* "M"不带前导零的月份取值范围为1到12例如7月表示为"7"。
* "dd"2位数的日期取值范围为01到31例如22日表示为"22"。
* "d"不带前导零的日期取值范围为1到31例如22日表示为"22"。
* "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
* "E":星期的缩写,例如:星期三表示为"Wed"。
* "DDD" 或 "D"一年中的第几天取值范围为001到366例如第200天表示为"200"。
* 时间格式
* "HH"24小时制的小时数取值范围为00到23例如下午5点表示为"17"。
* "hh"12小时制的小时数取值范围为01到12例如下午5点表示为"05"。
* "mm"分钟数取值范围为00到59例如30分钟表示为"30"。
* "ss"秒数取值范围为00到59例如45秒表示为"45"。
* "SSS"毫秒数取值范围为000到999例如123毫秒表示为"123"。
*/
/**
* 日期格式与时间格式枚举
*/
@Getter
@AllArgsConstructor
public enum FormatsType {
/**
* 例如2023年表示为"23"
*/
YY("yy"),
/**
* 例如2023年表示为"2023"
*/
YYYY("yyyy"),
/**
* 例例如2023年7月可以表示为 "2023-07"
*/
YYYY_MM("yyyy-MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
*/
YYYY_MM_DD("yyyy-MM-dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
*/
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
/**
* 例如下午3点30分45秒表示为 "15:30:45"
*/
HH_MM_SS("HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023/07"
*/
YYYY_MM_SLASH("yyyy/MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
*/
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023.07"
*/
YYYY_MM_DOT("yyyy.MM"),
/**
* 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
*/
YYYY_MM_DD_DOT("yyyy.MM.dd"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
*/
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
/**
* 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
/**
* 例如2023年7月可以表示为 "202307"
*/
YYYYMM("yyyyMM"),
/**
* 例如2023年7月22日可以表示为 "20230722"
*/
YYYYMMDD("yyyyMMdd"),
/**
* 例如2023年7月22日下午3点可以表示为 "2023072215"
*/
YYYYMMDDHH("yyyyMMddHH"),
/**
* 例如2023年7月22日下午3点30分可以表示为 "202307221530"
*/
YYYYMMDDHHMM("yyyyMMddHHmm"),
/**
* 例如2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
*/
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
/**
* 时间格式
*/
private final String timeFormat;
public static FormatsType getFormatsType(String str) {
for (FormatsType value : values()) {
if (StringUtils.contains(str, value.getTimeFormat())) {
return value;
}
}
throw new RuntimeException("'FormatsType' not found By " + str);
}
}

View File

@@ -0,0 +1,44 @@
package org.hzhub.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 登录类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum LoginType {
/**
* 密码登录
*/
PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
/**
* 短信登录
*/
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
* 邮箱登录
*/
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
/**
* 小程序登录
*/
XCX("", "");
/**
* 登录重试超出限制提示
*/
final String retryLimitExceed;
/**
* 登录重试限制计数提示
*/
final String retryLimitCount;
}

View File

@@ -0,0 +1,30 @@
package org.hzhub.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户状态
*
* @author ruoyi
*/
@Getter
@AllArgsConstructor
public enum UserStatus {
/**
* 正常
*/
OK("0", "正常"),
/**
* 停用
*/
DISABLE("1", "停用"),
/**
* 删除
*/
DELETED("2", "删除");
private final String code;
private final String info;
}

View File

@@ -0,0 +1,39 @@
package org.hzhub.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hzhub.common.core.utils.StringUtils;
/**
* 用户类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum UserType {
/**
* 后台系统用户
*/
SYS_USER("sys_user"),
/**
* 移动客户端用户
*/
APP_USER("app_user");
/**
* 用户类型标识(用于 token、权限识别等
*/
private final String userType;
public static UserType getUserType(String str) {
for (UserType value : values()) {
if (StringUtils.contains(str, value.getUserType())) {
return value;
}
}
throw new RuntimeException("'UserType' not found By " + str);
}
}

View File

@@ -0,0 +1,67 @@
package org.hzhub.common.core.exception;
import cn.hutool.core.text.StrFormatter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 业务异常(支持占位符 {}
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class ServiceException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
public ServiceException(String message) {
this.message = message;
}
public ServiceException(String message, Integer code) {
this.message = message;
this.code = code;
}
public ServiceException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
}
@Override
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}

View File

@@ -0,0 +1,62 @@
package org.hzhub.common.core.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* sse 特制异常
*
* @author LionLi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class SseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
public SseException(String message) {
this.message = message;
}
public SseException(String message, Integer code) {
this.message = message;
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public SseException setMessage(String message) {
this.message = message;
return this;
}
public SseException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}

View File

@@ -0,0 +1,74 @@
package org.hzhub.common.core.exception.base;
import lombok.AllArgsConstructor;
import org.hzhub.common.core.utils.MessageUtils;
import org.hzhub.common.core.utils.StringUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 基础异常
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class BaseException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 所属模块
*/
private String module;
/**
* 错误码
*/
private String code;
/**
* 错误码对应的参数
*/
private Object[] args;
/**
* 错误消息
*/
private String defaultMessage;
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
}
public BaseException(String module, String defaultMessage) {
this(module, null, null, defaultMessage);
}
public BaseException(String code, Object[] args) {
this(null, code, args, null);
}
public BaseException(String defaultMessage) {
this(null, null, null, defaultMessage);
}
@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
message = MessageUtils.message(code, args);
}
if (message == null) {
message = defaultMessage;
}
return message;
}
}

View File

@@ -0,0 +1,21 @@
package org.hzhub.common.core.exception.file;
import org.hzhub.common.core.exception.base.BaseException;
import java.io.Serial;
/**
* 文件信息异常类
*
* @author ruoyi
*/
public class FileException extends BaseException {
@Serial
private static final long serialVersionUID = 1L;
public FileException(String code, Object[] args) {
super("file", code, args, null);
}
}

View File

@@ -0,0 +1,18 @@
package org.hzhub.common.core.exception.file;
import java.io.Serial;
/**
* 文件名称超长限制异常类
*
* @author ruoyi
*/
public class FileNameLengthLimitExceededException extends FileException {
@Serial
private static final long serialVersionUID = 1L;
public FileNameLengthLimitExceededException(int defaultFileNameLength) {
super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
}
}

View File

@@ -0,0 +1,18 @@
package org.hzhub.common.core.exception.file;
import java.io.Serial;
/**
* 文件名大小限制异常类
*
* @author ruoyi
*/
public class FileSizeLimitExceededException extends FileException {
@Serial
private static final long serialVersionUID = 1L;
public FileSizeLimitExceededException(long defaultMaxSize) {
super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
}
}

View File

@@ -0,0 +1,18 @@
package org.hzhub.common.core.exception.user;
import java.io.Serial;
/**
* 验证码错误异常类
*
* @author ruoyi
*/
public class CaptchaException extends UserException {
@Serial
private static final long serialVersionUID = 1L;
public CaptchaException() {
super("user.jcaptcha.error");
}
}

View File

@@ -0,0 +1,18 @@
package org.hzhub.common.core.exception.user;
import java.io.Serial;
/**
* 验证码失效异常类
*
* @author ruoyi
*/
public class CaptchaExpireException extends UserException {
@Serial
private static final long serialVersionUID = 1L;
public CaptchaExpireException() {
super("user.jcaptcha.expire");
}
}

View File

@@ -0,0 +1,20 @@
package org.hzhub.common.core.exception.user;
import org.hzhub.common.core.exception.base.BaseException;
import java.io.Serial;
/**
* 用户信息异常类
*
* @author ruoyi
*/
public class UserException extends BaseException {
@Serial
private static final long serialVersionUID = 1L;
public UserException(String code, Object... args) {
super("user", code, args, null);
}
}

View File

@@ -0,0 +1,52 @@
package org.hzhub.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.hzhub.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
* 正则表达式模式池工厂
* <p>初始化的时候将正则表达式加入缓存池当中</p>
* <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p>
*
* @author 21001
*/
public class RegexPatternPoolFactory extends PatternPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
/**
* 身份证号码后6位
*/
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
/**
* 注册账号
*/
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
/**
* 密码包含至少8个字符包括大写字母、小写字母、数字和特殊字符
*/
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
/**
* 通用状态0表示正常1表示停用
*/
public static final Pattern STATUS = get(RegexConstants.STATUS);
}

View File

@@ -0,0 +1,31 @@
package org.hzhub.common.core.factory;
import org.hzhub.common.core.utils.StringUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
/**
* yml 配置源工厂
*
* @author Lion Li
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}

View File

@@ -0,0 +1,27 @@
package org.hzhub.common.core.service;
/**
* 通用 参数配置服务
*
* @author Lion Li
*/
public interface ConfigService {
/**
* 根据参数 key 获取参数值
*
* @param configKey 参数 key
* @return 参数值
*/
String getConfigValue(String configKey);
/**
* 根据配置类型和配置key获取值
*
* @param category 配置类型
* @param configKey 配置key
* @return 配置属性
*/
String getConfigValue(String category, String configKey);
}

View File

@@ -0,0 +1,46 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.DeptDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 部门服务
*
* @author Lion Li
*/
public interface DeptService {
/**
* 通过部门ID查询部门名称
*
* @param deptIds 部门ID串逗号分隔
* @return 部门名称串逗号分隔
*/
String selectDeptNameByIds(String deptIds);
/**
* 根据部门ID查询部门负责人
*
* @param deptId 部门ID用于指定需要查询的部门
* @return 返回该部门的负责人ID
*/
Long selectDeptLeaderById(Long deptId);
/**
* 查询部门
*
* @return 部门列表
*/
List<DeptDTO> selectDeptsByList();
/**
* 根据部门 ID 列表查询部门名称映射关系
*
* @param deptIds 部门 ID 列表
* @return Map其中 key 为部门 IDvalue 为对应的部门名称
*/
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
}

View File

@@ -0,0 +1,87 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.DictDataDTO;
import org.hzhub.common.core.domain.dto.DictTypeDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 字典服务
*
* @author Lion Li
*/
public interface DictService {
/**
* 分隔符
*/
String SEPARATOR = ",";
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @return 字典标签
*/
default String getDictLabel(String dictType, String dictValue) {
return getDictLabel(dictType, dictValue, SEPARATOR);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @return 字典值
*/
default String getDictValue(String dictType, String dictLabel) {
return getDictValue(dictType, dictLabel, SEPARATOR);
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @param separator 分隔符
* @return 字典标签
*/
String getDictLabel(String dictType, String dictValue, String separator);
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @param separator 分隔符
* @return 字典值
*/
String getDictValue(String dictType, String dictLabel, String separator);
/**
* 获取字典下所有的字典值与标签
*
* @param dictType 字典类型
* @return dictValue为keydictLabel为值组成的Map
*/
Map<String, String> getAllDictByDictType(String dictType);
/**
* 根据字典类型查询详细信息
*
* @param dictType 字典类型
* @return 字典类型详细信息
*/
DictTypeDTO getDictType(String dictType);
/**
* 根据字典类型查询字典数据列表
*
* @param dictType 字典类型
* @return 字典数据列表
*/
List<DictDataDTO> getDictData(String dictType);
}

View File

@@ -0,0 +1,37 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.OssDTO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 通用 OSS服务
*
* @author Lion Li
*/
public interface OssService {
/**
* 通过ossId查询对应的url
*
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
String selectUrlByIds(String ossIds);
/**
* 通过ossId查询列表
*
* @param ossIds ossId串逗号分隔
* @return 列表
*/
List<OssDTO> selectByIds(String ossIds);
/**
* 上传 MultipartFile 到对象存储服务
*/
OssDTO uploadFile(MultipartFile file);
}

View File

@@ -0,0 +1,28 @@
package org.hzhub.common.core.service;
import java.util.Set;
/**
* 用户权限处理
*
* @author Lion Li
*/
public interface PermissionService {
/**
* 获取角色数据权限
*
* @param userId 用户id
* @return 角色权限信息
*/
Set<String> getRolePermission(Long userId);
/**
* 获取菜单数据权限
*
* @param userId 用户id
* @return 菜单权限信息
*/
Set<String> getMenuPermission(Long userId);
}

View File

@@ -0,0 +1,21 @@
package org.hzhub.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 岗位服务
*
* @author AprilWind
*/
public interface PostService {
/**
* 根据岗位 ID 列表查询岗位名称映射关系
*
* @param postIds 岗位 ID 列表
* @return Map其中 key 为岗位 IDvalue 为对应的岗位名称
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
}

View File

@@ -0,0 +1,21 @@
package org.hzhub.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 角色服务
*
* @author AprilWind
*/
public interface RoleService {
/**
* 根据角色 ID 列表查询角色名称映射关系
*
* @param roleIds 角色 ID 列表
* @return Map其中 key 为角色 IDvalue 为对应的角色名称
*/
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
}

View File

@@ -0,0 +1,45 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.TaskAssigneeDTO;
import org.hzhub.common.core.domain.model.TaskAssigneeBody;
/**
* 工作流设计器获取任务执行人
*
* @author Lion Li
*/
public interface TaskAssigneeService {
/**
* 查询角色并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询岗位并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询部门并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery);
/**
* 查询用户并返回任务指派的列表,支持分页
*
* @param taskQuery 查询条件
* @return 办理人
*/
TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery);
}

View File

@@ -0,0 +1,16 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.VisitorLoginUserDto;
public interface UserLoginService {
/**
* 通过openid获取登录信息
*
* @param openid 微信openid
* @param clientId 客户端id
* @return
*/
VisitorLoginUserDto mpLogin(String openid, String clientId);
}

View File

@@ -0,0 +1,104 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.UserDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 用户服务
*
* @author Lion Li
*/
public interface UserService {
/**
* 通过用户ID查询用户账户
*
* @param userId 用户ID
* @return 用户账户
*/
String selectUserNameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userId 用户ID
* @return 用户名称
*/
String selectNicknameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户名称
*/
String selectNicknameByIds(String userIds);
/**
* 通过用户ID查询用户手机号
*
* @param userId 用户id
* @return 用户手机号
*/
String selectPhonenumberById(Long userId);
/**
* 通过用户ID查询用户邮箱
*
* @param userId 用户id
* @return 用户邮箱
*/
String selectEmailById(Long userId);
/**
* 通过用户ID查询用户列表
*
* @param userIds 用户ids
* @return 用户列表
*/
List<UserDTO> selectListByIds(List<Long> userIds);
/**
* 通过角色ID查询用户ID
*
* @param roleIds 角色ids
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
/**
* 通过角色ID查询用户
*
* @param roleIds 角色ids
* @return 用户
*/
List<UserDTO> selectUsersByRoleIds(List<Long> roleIds);
/**
* 通过部门ID查询用户
*
* @param deptIds 部门ids
* @return 用户
*/
List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
/**
* 通过岗位ID查询用户
*
* @param postIds 岗位ids
* @return 用户
*/
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
/**
* 根据用户 ID 列表查询用户名称映射关系
*
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户名称
*/
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
}

View File

@@ -0,0 +1,105 @@
package org.hzhub.common.core.service;
import org.hzhub.common.core.domain.dto.CompleteTaskDTO;
import org.hzhub.common.core.domain.dto.StartProcessDTO;
import org.hzhub.common.core.domain.dto.StartProcessReturnDTO;
import java.util.List;
import java.util.Map;
/**
* 通用 工作流服务
*
* @author may
*/
public interface WorkflowService {
/**
* 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
*
* @param businessIds 业务id
* @return 结果
*/
boolean deleteInstance(List<Long> businessIds);
/**
* 获取当前流程状态
*
* @param taskId 任务id
* @return 状态
*/
String getBusinessStatusByTaskId(Long taskId);
/**
* 获取当前流程状态
*
* @param businessId 业务id
* @return 状态
*/
String getBusinessStatus(String businessId);
/**
* 设置流程变量
*
* @param instanceId 流程实例id
* @param variable 流程变量
*/
void setVariable(Long instanceId, Map<String, Object> variable);
/**
* 获取流程变量
*
* @param instanceId 流程实例id
*/
Map<String, Object> instanceVariable(Long instanceId);
/**
* 按照业务id查询流程实例id
*
* @param businessId 业务id
* @return 结果
*/
Long getInstanceIdByBusinessId(String businessId);
/**
* 新增租户流程定义
*
* @param tenantId 租户id
*/
void syncDef(String tenantId);
/**
* 启动流程
*
* @param startProcess 参数
* @return 结果
*/
StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess);
/**
* 办理任务
* 系统后台发起审批 无用户信息 需要忽略权限
* completeTask.getVariables().put("ignore", true);
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
/**
* 办理任务
*
* @param taskId 任务ID
* @param message 办理意见
* @return 结果
*/
boolean completeTask(Long taskId, String message);
/**
* 启动流程并办理第一个任务
*
* @param startProcess 参数
* @return 结果
*/
boolean startCompleteTask(StartProcessDTO startProcess);
}

View File

@@ -0,0 +1,300 @@
package org.hzhub.common.core.utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.hzhub.common.core.enums.FormatsType;
import org.hzhub.common.core.exception.ServiceException;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 时间工具类
*
* @author ruoyi
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
private static final String[] PARSE_PATTERNS = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
@Deprecated
private DateUtils() {
}
/**
* 获取当前日期和时间
*
* @return 当前日期和时间的 Date 对象表示
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期的字符串表示格式为YYYY-MM-DD
*
* @return 当前日期的字符串表示
*/
public static String getDate() {
return dateTimeNow(FormatsType.YYYY_MM_DD);
}
/**
* 获取当前日期的字符串表示格式为yyyyMMdd
*
* @return 当前日期的字符串表示
*/
public static String getCurrentDate() {
return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
}
/**
* 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd"
*
* @return 当前日期的路径格式字符串
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
}
/**
* 获取当前时间的字符串表示格式为YYYY-MM-DD HH:MM:SS
*
* @return 当前时间的字符串表示
*/
public static String getTime() {
return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
}
/**
* 获取当前时间的字符串表示,格式为 "HH:MM:SS"
*
* @return 当前时间的字符串表示,格式为 "HH:MM:SS"
*/
public static String getTimeWithHourMinuteSecond() {
return dateTimeNow(FormatsType.HH_MM_SS);
}
/**
* 获取当前日期和时间的字符串表示格式为YYYYMMDDHHMMSS
*
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow() {
return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
}
/**
* 获取当前日期和时间的指定格式的字符串表示
*
* @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow(final FormatsType format) {
return parseDateToStr(format, new Date());
}
/**
* 将指定日期格式化为 YYYY-MM-DD 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String formatDate(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD, date);
}
/**
* 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
}
/**
* 将指定日期按照指定格式进行格式化
*
* @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String parseDateToStr(final FormatsType format, final Date date) {
return new SimpleDateFormat(format.getTimeFormat()).format(date);
}
/**
* 将指定格式的日期时间字符串转换为 Date 对象
*
* @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
* @param ts 要解析的日期时间字符串
* @return 解析后的 Date 对象
* @throws RuntimeException 如果解析过程中发生异常
*/
public static Date parseDateTime(final FormatsType format, final String ts) {
try {
return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 将对象转换为日期对象
*
* @param str 要转换的对象,通常是字符串
* @return 转换后的日期对象如果转换失败或输入为null则返回null
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), PARSE_PATTERNS);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*
* @return 服务器启动时间的 Date 对象表示
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算两个时间之间的时间差,并以指定单位返回(绝对值)
*
* @param start 起始时间
* @param end 结束时间
* @param unit 所需返回的时间单位DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS
* @return 时间差的绝对值,以指定单位表示
*/
public static long difference(Date start, Date end, TimeUnit unit) {
// 计算时间差,单位为毫秒,取绝对值避免负数
long diffInMillis = Math.abs(end.getTime() - start.getTime());
// 根据目标单位转换时间差
return switch (unit) {
case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
case MILLISECONDS -> diffInMillis;
case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
};
}
/**
* 计算两个日期之间的时间差,并以天、小时和分钟的格式返回
*
* @param endDate 结束日期
* @param nowDate 当前日期
* @return 表示时间差的字符串,格式为"天 小时 分钟"
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
return String.format("%d天 %d小时 %d分钟", day, hour, min);
}
/**
* 计算两个时间点的差值天、小时、分钟、秒当值为0时不显示该单位
*
* @param endDate 结束时间
* @param nowDate 当前时间
* @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
*/
public static String getTimeDifference(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
// 构建时间差字符串条件是值不为0才显示
StringBuilder result = new StringBuilder();
if (day > 0) {
result.append(String.format("%d天 ", day));
}
if (hour > 0) {
result.append(String.format("%d小时 ", hour));
}
if (min > 0) {
result.append(String.format("%d分钟 ", min));
}
if (sec > 0) {
result.append(String.format("%d秒", sec));
}
return result.length() > 0 ? result.toString().trim() : "0秒";
}
/**
* 将 LocalDateTime 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDateTime 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 将 LocalDate 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDate 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 校验日期范围
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param maxValue 最大时间跨度的限制值
* @param unit 时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES"
*/
public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
// 校验结束日期不能早于开始日期
if (endDate.before(startDate)) {
throw new ServiceException("结束日期不能早于开始日期");
}
// 计算时间跨度
long diffInMillis = endDate.getTime() - startDate.getTime();
// 根据单位转换时间跨度
long diff = switch (unit) {
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
default -> throw new IllegalArgumentException("不支持的时间单位");
};
// 校验时间跨度不超过最大限制
if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
}
}
}

View File

@@ -0,0 +1,93 @@
package org.hzhub.common.core.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import io.github.linpeilie.Converter;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* Mapstruct 工具类
* <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
*
*
* @author Michelle.Chung
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MapstructUtils {
private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
/**
* 将 T 类型对象,转换为 desc 类型的对象并返回
*
* @param source 数据来源实体
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> V convert(T source, Class<V> desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return CONVERTER.convert(source, desc);
}
/**
* 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象
*
* @param source 数据来源实体
* @param desc 转换后的对象
* @return desc
*/
public static <T, V> V convert(T source, V desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return CONVERTER.convert(source, desc);
}
/**
* 将 T 类型的集合,转换为 desc 类型的集合并返回
*
* @param sourceList 数据来源实体列表
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return CONVERTER.convert(sourceList, desc);
}
/**
* 将 Map 转换为 beanClass 类型的集合并返回
*
* @param map 数据来源
* @param beanClass bean类
* @return bean对象
*/
public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
return CONVERTER.convert(map, beanClass);
}
}

View File

@@ -0,0 +1,33 @@
package org.hzhub.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* 获取i18n资源文件
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MessageUtils {
private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
/**
* 根据消息键和参数 获取消息 委托给spring messageSource
*
* @param code 消息键
* @param args 参数
* @return 获取国际化翻译值
*/
public static String message(String code, Object... args) {
try {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
} catch (NoSuchMessageException e) {
return code;
}
}
}

View File

@@ -0,0 +1,84 @@
package org.hzhub.common.core.utils;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.net.NetUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hzhub.common.core.utils.regex.RegexUtils;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 增强网络相关工具类
*
* @author 秋辞未寒
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NetUtils extends NetUtil {
/**
* 判断是否为IPv6地址
*
* @param ip IP地址
* @return 是否为IPv6地址
*/
public static boolean isIPv6(String ip) {
try {
// 判断是否为IPv6地址
return InetAddress.getByName(ip) instanceof Inet6Address;
} catch (UnknownHostException e) {
return false;
}
}
/**
* 判断IPv6地址是否为内网地址
* <br><br>
* 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
* <pre>
* 通配符地址 0:0:0:0:0:0:0:0
* 链路本地地址 fe80::/10
* 唯一本地地址 fec0::/10
* 环回地址 ::1
* </pre>
*
* @param ip IP地址
* @return 是否为内网地址
*/
public static boolean isInnerIPv6(String ip) {
try {
// 判断是否为IPv6地址
if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
// isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
// isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
// isLoopbackAddress 判断是否为环回地址与IPv4的 127.0.0.1 同理,用于表示本机
// isSiteLocalAddress 判断是否为本地站点地址IPv6唯一本地地址Unique Local Addresses简称ULA
if (inet6Address.isAnyLocalAddress()
|| inet6Address.isLinkLocalAddress()
|| inet6Address.isLoopbackAddress()
|| inet6Address.isSiteLocalAddress()) {
return true;
}
}
} catch (UnknownHostException e) {
// 注意isInnerIPv6方法和isIPv6方法的适用范围不同所以此处不能忽略其异常信息。
throw new IllegalArgumentException("Invalid IPv6 address!", e);
}
return false;
}
/**
* 判断是否为IPv4地址
*
* @param ip IP地址
* @return 是否为IPv4地址
*/
public static boolean isIPv4(String ip) {
return RegexUtils.isMatch(PatternPool.IPV4, ip);
}
}

View File

@@ -0,0 +1,60 @@
package org.hzhub.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.function.Function;
/**
* 对象工具类
*
* @author 秋辞未寒
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils extends ObjectUtil {
/**
* 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
*
* @param obj 对象
* @param func 获取方法
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return null;
}
/**
* 如果对象不为空,则获取对象中的某个字段,否则返回默认值
*
* @param obj 对象
* @param func 获取方法
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return defaultValue;
}
/**
* 如果值不为空,则返回值,否则返回默认值
*
* @param obj 对象
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T> T notNull(T obj, T defaultValue) {
if (isNotNull(obj)) {
return obj;
}
return defaultValue;
}
}

View File

@@ -0,0 +1,289 @@
package org.hzhub.common.core.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ServletUtils extends JakartaServletUtil {
/**
* 获取指定名称的 String 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取指定名称的 Integer 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取指定名称的 Boolean 类型的请求参数
*
* @param name 参数名
* @return 参数值
*/
public static Boolean getParameterToBool(String name) {
return Convert.toBool(getRequest().getParameter(name));
}
/**
* 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值
*
* @param name 参数名
* @param defaultValue 默认值
* @return 参数值或默认值
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue) {
return Convert.toBool(getRequest().getParameter(name), defaultValue);
}
/**
* 获取所有请求参数(以 Map 的形式返回)
*
* @param request 请求对象{@link ServletRequest}
* @return 请求参数的 Map键为参数名值为参数值数组
*/
public static Map<String, String[]> getParams(ServletRequest request) {
final Map<String, String[]> map = request.getParameterMap();
return Collections.unmodifiableMap(map);
}
/**
* 获取所有请求参数(以 Map 的形式返回,值为字符串形式的拼接)
*
* @param request 请求对象{@link ServletRequest}
* @return 请求参数的 Map键为参数名值为拼接后的字符串
*/
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
}
return params;
}
/**
* 获取当前 HTTP 请求对象
*
* @return 当前 HTTP 请求对象
*/
public static HttpServletRequest getRequest() {
try {
return getRequestAttributes().getRequest();
} catch (Exception e) {
return null;
}
}
/**
* 获取当前 HTTP 响应对象
*
* @return 当前 HTTP 响应对象
*/
public static HttpServletResponse getResponse() {
try {
return getRequestAttributes().getResponse();
} catch (Exception e) {
return null;
}
}
/**
* 获取当前请求的 HttpSession 对象
* <p>
* 如果当前请求已经关联了一个会话(即已经存在有效的 session ID
* 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。
* <p>
* HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等,
* 可以在多个请求之间共享会话数据
*
* @return 当前请求的 HttpSession 对象
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
/**
* 获取当前请求的请求属性
*
* @return {@link ServletRequestAttributes} 请求属性对象
*/
public static ServletRequestAttributes getRequestAttributes() {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
} catch (Exception e) {
return null;
}
}
/**
* 获取指定请求头的值,如果头部为空则返回空字符串
*
* @param request 请求对象
* @param name 头部名称
* @return 头部值
*/
public static String getHeader(HttpServletRequest request, String name) {
String value = request.getHeader(name);
if (StringUtils.isEmpty(value)) {
return StringUtils.EMPTY;
}
return urlDecode(value);
}
/**
* 获取所有请求头的 Map键为头部名称值为头部值
*
* @param request 请求对象
* @return 请求头的 Map
*/
public static Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
}
/**
* 将字符串渲染到客户端(以 JSON 格式返回)
*
* @param response 渲染对象
* @param string 待渲染的字符串
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(HttpStatus.HTTP_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断当前请求是否为 Ajax 异步请求
*
* @param request 请求对象
* @return 是否为 Ajax 请求
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
// 判断 Accept 头部是否包含 application/json
String accept = request.getHeader("accept");
if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
return true;
}
// 判断 X-Requested-With 头部是否包含 XMLHttpRequest
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
return true;
}
// 判断 URI 后缀是否为 .json 或 .xml
String uri = request.getRequestURI();
if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
return true;
}
// 判断请求参数 __ajax 是否为 json 或 xml
String ajax = request.getParameter("__ajax");
return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
}
/**
* 获取客户端 IP 地址
*
* @return 客户端 IP 地址
*/
public static String getClientIP() {
return getClientIP(getRequest());
}
/**
* 对内容进行 URL 编码
*
* @param str 内容
* @return 编码后的内容
*/
public static String urlEncode(String str) {
return URLEncoder.encode(str, StandardCharsets.UTF_8);
}
/**
* 对内容进行 URL 解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str) {
return URLDecoder.decode(str, StandardCharsets.UTF_8);
}
}

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