作者 钟来

模块整理

正在显示 100 个修改的文件 包含 2550 行增加121 行删除

要显示太多修改。

为保证性能只显示 100 of 100+ 个文件。

<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lh-common-datasource</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<!-- 建议使用最新版本,最新版本请从项目首页查找 -->
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.ruoyi.framework.aspectj;
package com.zhonglai.luhui.datasource.aspectj;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
import com.zhonglai.luhui.datasource.config.DynamicDataSourceContextHolder;
import com.zhonglai.luhui.datasource.enums.DataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
... ... @@ -28,8 +28,8 @@ public class DataSourceAspect
{
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
@Pointcut("@annotation(com.zhonglai.luhui.datasource.enums.DataSource)"
+ "|| @within(com.zhonglai.luhui.datasource.enums.DataSource)")
public void dsPointCut()
{
... ...
package com.ruoyi.framework.config;
package com.zhonglai.luhui.datasource.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType;
import com.zhonglai.luhui.datasource.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
... ...
package com.ruoyi.framework.config.properties;
package com.zhonglai.luhui.datasource.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
... ...
package com.ruoyi.framework.datasource;
package com.zhonglai.luhui.datasource.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
... ...
package com.ruoyi.framework.config;
package com.zhonglai.luhui.datasource.config;
import com.ruoyi.common.utils.StringUtils;
import org.apache.ibatis.io.VFS;
... ...
package com.zhonglai.luhui.datasource.enums;
import java.lang.annotation.*;
/**
* 自定义多数据源切换注解
*
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*
* @author ruoyi
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}
... ...
package com.zhonglai.luhui.datasource.enums;
/**
* 数据源
*
* @author ruoyi
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-common-firewall</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.ruoyi.framework.aspectj;
package com.zhonglai.luhui.firewall.aspectj;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType;
... ...
package com.ruoyi.framework.config;
package com.zhonglai.luhui.firewall.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
... ...
package com.ruoyi.framework.config;
package com.zhonglai.luhui.firewall.config;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
... ...
package com.ruoyi.framework.config;
package com.zhonglai.luhui.firewall.config;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
... ...
package com.zhonglai.luhui.firewall.config;
import com.zhonglai.luhui.firewall.interceptor.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class RepeatSubmitConfig implements WebMvcConfigurer {
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
}
... ...
package com.ruoyi.framework.interceptor;
package com.zhonglai.luhui.firewall.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.RepeatSubmit;
... ...
package com.ruoyi.framework.interceptor.impl;
package com.zhonglai.luhui.firewall.interceptor.impl;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
import com.zhonglai.luhui.firewall.interceptor.RepeatSubmitInterceptor;
import com.zhonglai.luhui.redis.service.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lh-common-log</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
\ No newline at end of file
... ...
//package com.zhonglai.luhui.log.aspectj;
//
//import com.alibaba.fastjson.JSON;
//import com.ruoyi.common.annotation.Log;
//import com.ruoyi.common.core.domain.BaseLoginUser;
//import com.ruoyi.common.enums.BusinessStatus;
//import com.ruoyi.common.enums.HttpMethod;
//import com.ruoyi.common.utils.SecurityUtils;
//import com.ruoyi.common.utils.ServletUtils;
//import com.ruoyi.common.utils.StringUtils;
//import com.ruoyi.common.utils.ip.IpUtils;
//import com.ruoyi.system.domain.sys.SysOperLog;
//import org.aspectj.lang.JoinPoint;
//import org.aspectj.lang.annotation.AfterReturning;
//import org.aspectj.lang.annotation.AfterThrowing;
//import org.aspectj.lang.annotation.Aspect;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.stereotype.Component;
//import org.springframework.validation.BindingResult;
//import org.springframework.web.multipart.MultipartFile;
//import org.springframework.web.servlet.HandlerMapping;
//
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.util.Collection;
//import java.util.Map;
//
///**
// * 操作日志记录处理
// *
// * @author ruoyi
// */
//@Aspect
//@Component
//public class LogAspect
//{
// private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
//
// /**
// * 处理完请求后执行
// *
// * @param joinPoint 切点
// */
// @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
// public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
// {
// handleLog(joinPoint, controllerLog, null, jsonResult);
// }
//
// /**
// * 拦截异常操作
// *
// * @param joinPoint 切点
// * @param e 异常
// */
// @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
// public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
// {
// handleLog(joinPoint, controllerLog, e, null);
// }
//
// protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
// {
// try
// {
// // 获取当前的用户
// BaseLoginUser loginUser = SecurityUtils.getLoginUser();
//
// // *========数据库日志=========*//
// SysOperLog operLog = new SysOperLog();
// operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// // 请求的地址
// String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
// operLog.setOperIp(ip);
// operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
// if (loginUser != null)
// {
// operLog.setOperName(loginUser.getUsername());
// }
//
// if (e != null)
// {
// operLog.setStatus(BusinessStatus.FAIL.ordinal());
// operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
// }
// // 设置方法名称
// String className = joinPoint.getTarget().getClass().getName();
// String methodName = joinPoint.getSignature().getName();
// operLog.setMethod(className + "." + methodName + "()");
// // 设置请求方式
// operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// // 处理设置注解上的参数
// getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// // 保存数据库
// AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
// }
// catch (Exception exp)
// {
// // 记录本地异常日志
// log.error("==前置通知异常==");
// log.error("异常信息:{}", exp.getMessage());
// exp.printStackTrace();
// }
// }
//
// /**
// * 获取注解中对方法的描述信息 用于Controller层注解
// *
// * @param log 日志
// * @param operLog 操作日志
// * @throws Exception
// */
// public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
// {
// // 设置action动作
// operLog.setBusinessType(log.businessType().ordinal());
// // 设置标题
// operLog.setTitle(log.title());
// // 设置操作人类别
// operLog.setOperatorType(log.operatorType().ordinal());
// // 是否需要保存request,参数和值
// if (log.isSaveRequestData())
// {
// // 获取参数的信息,传入到数据库中。
// setRequestValue(joinPoint, operLog);
// }
// // 是否需要保存response,参数和值
// if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
// {
// operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
// }
// }
//
// /**
// * 获取请求的参数,放到log中
// *
// * @param operLog 操作日志
// * @throws Exception 异常
// */
// private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
// {
// String requestMethod = operLog.getRequestMethod();
// if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
// {
// String params = argsArrayToString(joinPoint.getArgs());
// operLog.setOperParam(StringUtils.substring(params, 0, 2000));
// }
// else
// {
// Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
// operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
// }
// }
//
// /**
// * 参数拼装
// */
// private String argsArrayToString(Object[] paramsArray)
// {
// String params = "";
// if (paramsArray != null && paramsArray.length > 0)
// {
// for (Object o : paramsArray)
// {
// if (StringUtils.isNotNull(o) && !isFilterObject(o))
// {
// try
// {
// Object jsonObj = JSON.toJSON(o);
// params += jsonObj.toString() + " ";
// }
// catch (Exception e)
// {
// }
// }
// }
// }
// return params.trim();
// }
//
// /**
// * 判断是否需要过滤的对象。
// *
// * @param o 对象信息。
// * @return 如果是需要过滤的对象,则返回true;否则返回false。
// */
// @SuppressWarnings("rawtypes")
// public boolean isFilterObject(final Object o)
// {
// Class<?> clazz = o.getClass();
// if (clazz.isArray())
// {
// return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
// }
// else if (Collection.class.isAssignableFrom(clazz))
// {
// Collection collection = (Collection) o;
// for (Object value : collection)
// {
// return value instanceof MultipartFile;
// }
// }
// else if (Map.class.isAssignableFrom(clazz))
// {
// Map map = (Map) o;
// for (Object value : map.entrySet())
// {
// Map.Entry entry = (Map.Entry) value;
// return entry.getValue() instanceof MultipartFile;
// }
// }
// return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
// || o instanceof BindingResult;
// }
//}
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-common-swagger</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 文档 -->
<dependency >
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--https://mvnrepository.com/artifact/io.swagger/swagger-models-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>${swagger-models.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--&lt;!&ndash; https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui &ndash;&gt;-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-ui.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
<?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>
<artifactId>lh-common</artifactId>
<groupId>com.zhonglai.luhui</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- 模型-->
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<modelVersion>4.0.0</modelVersion>
<artifactId>lh-domain</artifactId>
</project>
\ No newline at end of file
... ...
package com.ruoyi.system.domain.entity;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ...
package com.ruoyi.system.domain.entity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.Excel.ColumnType;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.tool.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ... @@ -19,11 +18,11 @@ public class SysDictData extends BaseEntity
private static final long serialVersionUID = 1L;
/** 字典编码 */
@Excel(name = "字典编码", cellType = ColumnType.NUMERIC)
@Excel(name = "字典编码", cellType = Excel.ColumnType.NUMERIC)
private Long dictCode;
/** 字典排序 */
@Excel(name = "字典排序", cellType = ColumnType.NUMERIC)
@Excel(name = "字典排序", cellType = Excel.ColumnType.NUMERIC)
private Long dictSort;
/** 字典标签 */
... ...
package com.ruoyi.system.domain.entity; import javax.validation.constraints.NotBlank;import javax.validation.constraints.Size; import com.ruoyi.common.annotation.Excel;import org.apache.commons.lang3.builder.ToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle;import com.ruoyi.common.tool.BaseEntity; /** * 字典类型表 sys_dict_type * * @author ruoyi */public class SysDictType extends BaseEntity{ private static final long serialVersionUID = 1L; /** 字典主键 */ @Excel(name = "字典主键", cellType = Excel.ColumnType.NUMERIC) private Long dictId; /** 字典名称 */ @Excel(name = "字典名称") private String dictName; /** 字典类型 */ @Excel(name = "字典类型") private String dictType; /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; public Long getDictId() { return dictId; } public void setDictId(Long dictId) { this.dictId = dictId; } @NotBlank(message = "字典名称不能为空") @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") public String getDictName() { return dictName; } public void setDictName(String dictName) { this.dictName = dictName; } @NotBlank(message = "字典类型不能为空") @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") public String getDictType() { return dictType; } public void setDictType(String dictType) { this.dictType = dictType; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("dictId", getDictId()) .append("dictName", getDictName()) .append("dictType", getDictType()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); }}
\ No newline at end of file
... ...
package com.ruoyi.system.domain.entity;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ...
package com.ruoyi.system.domain.entity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.Excel.ColumnType;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.tool.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ... @@ -19,7 +18,7 @@ public class SysRole extends BaseEntity
private static final long serialVersionUID = 1L;
/** 角色ID */
@Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
@Excel(name = "角色序号", cellType = Excel.ColumnType.NUMERIC)
private Long roleId;
/** 角色名称 */
... ...
... ... @@ -2,12 +2,12 @@ package com.ruoyi.system.domain.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.Excel.ColumnType;
import com.ruoyi.system.domain.tool.Excel.Type;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.Excels;
import com.ruoyi.system.domain.tool.Xss;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.utils.xss.Xss;
import com.ruoyi.common.tool.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.common.tool.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
/**
* 设备告警对象 iot_alert
*
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.common.tool.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import io.swagger.annotations.ApiModel;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import io.swagger.annotations.ApiModel;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.system.domain.user.UserTerminalGroupRelation;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import io.swagger.annotations.ApiModel;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.annotation.PublicSQLConfig;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import io.swagger.annotations.ApiModel;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.iot;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import com.ruoyi.common.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import java.util.Date;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.system.domain.tool.Xss;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.PublicSQLConfig;
import com.ruoyi.common.utils.xss.Xss;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.Excel;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.sys;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.user;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ... @@ -14,10 +12,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
* @date 2022-11-22
*/
@ApiModel("终端分组")
public class UserTerminalGroup extends BaseEntity
public class UserTerminalGroup
{
@PublicSQLConfig(isSelect=false)
private static final long serialVersionUID = 1L;
/** 创建时间 */
@ApiModelProperty("创建时间")
... ...
package com.ruoyi.system.domain;
package com.ruoyi.system.domain.user;
import com.ruoyi.system.domain.tool.BaseEntity;
import com.ruoyi.system.domain.tool.PublicSQLConfig;
import com.ruoyi.common.tool.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
... ... @@ -16,8 +15,6 @@ import org.apache.commons.lang3.builder.ToStringStyle;
@ApiModel("终端分组关系")
public class UserTerminalGroupRelation extends BaseEntity
{
@PublicSQLConfig(isSelect=false)
private static final long serialVersionUID = 1L;
/** 创建时间 */
@ApiModelProperty("创建时间")
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lh-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>lh-domain</module>
<module>ruoyi-common</module>
<module>ruoyi-system</module>
<module>lh-public-dao</module>
<module>ruoyi-common-core</module>
<module>ruoyi-common-redis</module>
<module>ruoyi-common-security</module>
<module>ruoyi-auth</module>
<module>lh-common-datasource</module>
<module>lh-common-log</module>
<module>lh-quartz</module>
<module>ruoyi-generator</module>
</modules>
<packaging>pom</packaging>
<description>
lh-common通用模块
</description>
</project>
\ No newline at end of file
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-jar-action</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-jar-sys-service</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.zhonglai.luhui;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
\ No newline at end of file
... ...
package com.zhonglai.luhui.action;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
/**
* web层通用数据处理
*
* @author ruoyi
*/
public class BaseController
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}
/**
* 设置请求分页数据
*/
protected void startPage()
{
PageUtils.startPage();
}
/**
* 设置请求排序数据
*/
protected void startOrderBy()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
{
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.orderBy(orderBy);
}
}
/**
* 清理分页的线程变量
*/
protected void clearPage()
{
PageUtils.clearPage();
}
/**
* 响应请求分页数据
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TableDataInfo getDataTable(List<?> list)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(new PageInfo(list).getTotal());
return rspData;
}
/**
* 返回成功
*/
public AjaxResult success()
{
return AjaxResult.success();
}
/**
* 返回失败消息
*/
public AjaxResult error()
{
return AjaxResult.error();
}
/**
* 返回成功消息
*/
public AjaxResult success(String message)
{
return AjaxResult.success(message);
}
/**
* 返回失败消息
*/
public AjaxResult error(String message)
{
return AjaxResult.error(message);
}
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows)
{
return rows > 0 ? AjaxResult.success() : AjaxResult.error();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected AjaxResult toAjax(boolean result)
{
return result ? success() : error();
}
/**
* 页面跳转
*/
public String redirect(String url)
{
return StringUtils.format("redirect:{}", url);
}
}
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-jar-chatgpt</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.unfbx</groupId>
<artifactId>chatgpt-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-common-swagger</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.zhonglai.luhui.chatgpt.config;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUnit;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-03-10
*/
public class LocalCache {
/**
* 缓存时长
*/
public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
/**
* 清理间隔
*/
private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
/**
* 缓存对象
*/
public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
static {
//启动定时任务
CACHE.schedulePrune(CLEAN_TIMEOUT);
}
}
... ...
package com.zhonglai.luhui.chatgpt.config;
import com.unfbx.chatgpt.OpenAiStreamClient;
import com.unfbx.chatgpt.function.KeyRandomStrategy;
import com.unfbx.chatgpt.interceptor.OpenAILogger;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
public class OpenAiStreamClientConfig {
@Value("${chatgpt.token}")
private List<String> apiKey;
@Value("${chatgpt.apiHost}")
private String apiHost;
@Value("${chatgpt.proxy.isProxy:false}")
private Boolean isProxy;
@Value("${chatgpt.proxy.host:127.0.0.1}")
private String host;
@Value("${chatgpt.proxy.port:7890}")
private Integer port;
@Bean
public OpenAiStreamClient openAiStreamClient() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
//!!!!!!测试或者发布到服务器千万不要配置Level == BODY!!!!
//!!!!!!测试或者发布到服务器千万不要配置Level == BODY!!!!
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient.Builder builder = new OkHttpClient
.Builder();
if(isProxy)
{
//本地开发需要配置代理地址
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
builder.proxy(proxy);
}
OkHttpClient okHttpClient = builder.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
return OpenAiStreamClient
.builder()
.apiHost(apiHost)
.apiKey(apiKey)
//自定义key使用策略 默认随机策略
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
}
... ...
package com.zhonglai.luhui.chatgpt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-23
*/
@Configuration
public class WebSocketConfig {
/**
* 这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket,如果你使用外置的tomcat就不需要该配置文件
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
... ...
package com.zhonglai.luhui.chatgpt.controller;
import cn.hutool.core.util.StrUtil;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.exception.BaseException;
import com.unfbx.chatgpt.exception.CommonError;
import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
import com.zhonglai.luhui.chatgpt.service.SseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-03-01
*/
@Controller
@Slf4j
public class ChatController {
private final SseService sseService;
public ChatController(SseService sseService) {
this.sseService = sseService;
}
/**
* 创建sse连接
*
* @param headers
* @return
*/
@CrossOrigin
@GetMapping("/createSse")
public SseEmitter createConnect(@RequestHeader Map<String, String> headers) {
String uid = getUid(headers);
return sseService.createSse(uid);
}
/**
* 聊天接口
*
* @param chatRequest
* @param headers
*/
@CrossOrigin
@PostMapping("/chat")
@ResponseBody
public ChatResponse sseChat(@RequestBody ChatRequest chatRequest, @RequestHeader Map<String, String> headers, HttpServletResponse response) {
String uid = getUid(headers);
return sseService.sseChat(uid, chatRequest, ChatCompletion.Model.GPT_3_5_TURBO_0301,null);
}
/**
* 关闭连接
*
* @param headers
*/
@CrossOrigin
@GetMapping("/closeSse")
public void closeConnect(@RequestHeader Map<String, String> headers) {
String uid = getUid(headers);
sseService.closeSse(uid);
}
@GetMapping("")
public String index() {
return "1.html";
}
@GetMapping("/websocket")
public String websocket() {
return "websocket.html";
}
/**
* 获取uid
*
* @param headers
* @return
*/
private String getUid(Map<String, String> headers) {
String uid = headers.get("uid");
if (StrUtil.isBlank(uid)) {
throw new BaseException(CommonError.SYS_ERROR);
}
return uid;
}
}
... ...
package com.zhonglai.luhui.chatgpt.controller.request;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@ApiModel("chatGPT流请求参数")
@Data
public class ChatRequest {
/**
* 客户端发送的问题参数
*/
@ApiModelProperty("问题(支持传历史问题,来设置上下文)")
private List<String> msg;
@ApiModelProperty("模型")
private ChatCompletion.Model model;
}
... ...
package com.zhonglai.luhui.chatgpt.controller.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class ChatResponse {
/**
* 问题消耗tokens
*/
@JsonProperty("question_tokens")
private long questionTokens = 0;
}
... ...
package com.zhonglai.luhui.chatgpt.entity;
import com.unfbx.chatgpt.entity.chat.Message;
import lombok.Data;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-04-10
*/
@Data
public class Chat {
private String uid;
private List<Message> message;
}
... ...
package com.zhonglai.luhui.chatgpt.event;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-02-28
*/
public class MyEvent implements SseEmitter.SseEventBuilder {
private static final MediaType TEXT_PLAIN = new MediaType("text", "plain", StandardCharsets.UTF_8);
private final Set<ResponseBodyEmitter.DataWithMediaType> dataToSend = new LinkedHashSet<>(4);
@Nullable
private StringBuilder sb;
@Override
public SseEmitter.SseEventBuilder id(String id) {
append("id:").append(id).append('\n');
return this;
}
@Override
public SseEmitter.SseEventBuilder name(String name) {
append("event:").append(name).append('\n');
return this;
}
@Override
public SseEmitter.SseEventBuilder reconnectTime(long reconnectTimeMillis) {
append("retry:").append(String.valueOf(reconnectTimeMillis)).append('\n');
return this;
}
@Override
public SseEmitter.SseEventBuilder comment(String comment) {
append(':').append(comment).append('\n');
return this;
}
@Override
public SseEmitter.SseEventBuilder data(Object object) {
return data(object, null);
}
@Override
public SseEmitter.SseEventBuilder data(Object object, @Nullable MediaType mediaType) {
saveAppendedText();
this.dataToSend.add(new ResponseBodyEmitter.DataWithMediaType(object, mediaType));
return this;
}
MyEvent append(String text) {
if (this.sb == null) {
this.sb = new StringBuilder();
}
this.sb.append(text);
return this;
}
MyEvent append(char ch) {
if (this.sb == null) {
this.sb = new StringBuilder();
}
this.sb.append(ch);
return this;
}
@Override
public Set<ResponseBodyEmitter.DataWithMediaType> build() {
if (!StringUtils.hasLength(this.sb) && this.dataToSend.isEmpty()) {
return Collections.emptySet();
}
append('\n');
saveAppendedText();
return this.dataToSend;
}
private void saveAppendedText() {
if (this.sb != null) {
this.dataToSend.add(new ResponseBodyEmitter.DataWithMediaType(this.sb.toString(), TEXT_PLAIN));
this.sb = null;
}
}
}
... ...
package com.zhonglai.luhui.chatgpt.listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import com.unfbx.chatgpt.entity.chat.Message;
import com.zhonglai.luhui.chatgpt.event.MyEvent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Objects;
/**
* 描述:OpenAIEventSourceListener
*
* @author https:www.unfbx.com
* @date 2023-02-22
*/
@Slf4j
public class OpenAISSEEventSourceListener extends EventSourceListener {
private long tokens;
private SseEmitter sseEmitter;
public OpenAISSEEventSourceListener(SseEmitter sseEmitter) {
this.sseEmitter = sseEmitter;
}
/**
* {@inheritDoc}
*/
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
/**
* {@inheritDoc}
*/
@SneakyThrows
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}:{}",type, data);
tokens += 1;
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
// sseEmitter.send(new MyEvent().data("<br/><br/>tokens:" + tokens(), MediaType.TEXT_EVENT_STREAM));
// sseEmitter.send(SseEmitter.event()
// .id("[TOKENS]")
// .data("<br/><br/>tokens:" + tokens())
// .reconnectTime(3000)
// );
// sseEmitter.send("[DONE]", MediaType.TEXT_EVENT_STREAM);
// sseEmitter.send(SseEmitter.event()
// .id("[DONE]")
// .data("[DONE]")
// .reconnectTime(3000)
// );
// 传输完成后自动关闭sse
sseEmitter.complete();
return;
}
ObjectMapper mapper = new ObjectMapper();
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
try {
Message delta = completionResponse.getChoices().get(0).getDelta();
if(null != delta.getContent())
{
sseEmitter.send(new MyEvent().data(delta.getContent(), MediaType.TEXT_EVENT_STREAM));
}
// sseEmitter.send(SseEmitter.event()
// .id(completionResponse.getId())
// .data(delta.getContent())
// .reconnectTime(3000)
// );
} catch (Exception e) {
log.error("sse信息推送失败!");
eventSource.cancel();
e.printStackTrace();
}
}
@Override
public void onClosed(EventSource eventSource) {
log.info("流式输出返回值总共{}tokens", tokens() - 2);
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if (Objects.isNull(response)) {
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data:{},异常:{}", response, t);
}
eventSource.cancel();
}
/**
* tokens
* @return
*/
public long tokens() {
return tokens;
}
}
... ...
package com.zhonglai.luhui.chatgpt.listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import javax.websocket.Session;
import java.util.Objects;
/**
* 描述:OpenAI流式输出Socket接收
*
* @author https:www.unfbx.com
* @date 2023-03-23
*/
@Slf4j
public class OpenAIWebSocketEventSourceListener extends EventSourceListener {
private Session session;
public OpenAIWebSocketEventSourceListener(Session session) {
this.session = session;
}
/**
* {@inheritDoc}
*/
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
/**
* {@inheritDoc}
*/
@SneakyThrows
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据:{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
session.getBasicRemote().sendText("[DONE]");
return;
}
ObjectMapper mapper = new ObjectMapper();
ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
String delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());
session.getBasicRemote().sendText(delta);
}
@Override
public void onClosed(EventSource eventSource) {
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if (Objects.isNull(response)) {
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data:{},异常:{}", response, t);
}
eventSource.cancel();
}
}
... ...
package com.zhonglai.luhui.chatgpt.service;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.zhonglai.luhui.chatgpt.listener.OpenAISSEEventSourceListener;
public interface CompleteCallback {
void sseChatEnd(ChatCompletion chatCompletion, OpenAISSEEventSourceListener openAISSEEventSourceListener);
}
... ...
package com.zhonglai.luhui.chatgpt.service;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-04-08
*/
public interface SseService {
/**
* 创建SSE
* @param uid
* @return
*/
SseEmitter createSse(String uid);
/**
* 关闭SSE
* @param uid
*/
void closeSse(String uid);
/**
* 客户端发送消息到服务端
* @param uid
* @param chatRequest
*/
ChatResponse sseChat(String uid, ChatRequest chatRequest, ChatCompletion.Model model, CompleteCallback completeCallback);
}
... ...
package com.zhonglai.luhui.chatgpt.service.impl;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.unfbx.chatgpt.OpenAiStreamClient;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.entity.chat.Message;
import com.unfbx.chatgpt.exception.BaseException;
import com.zhonglai.luhui.chatgpt.config.LocalCache;
import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
import com.zhonglai.luhui.chatgpt.listener.OpenAISSEEventSourceListener;
import com.zhonglai.luhui.chatgpt.service.CompleteCallback;
import com.zhonglai.luhui.chatgpt.service.SseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-04-08
*/
@Service
@Slf4j
public class SseServiceImpl implements SseService {
private final OpenAiStreamClient openAiStreamClient;
public SseServiceImpl(OpenAiStreamClient openAiStreamClient) {
this.openAiStreamClient = openAiStreamClient;
}
@Override
public SseEmitter createSse(String uid) {
//默认30秒超时,设置为0L则永不超时
SseEmitter sseEmitter = new SseEmitter(0l);
//完成后回调
sseEmitter.onCompletion(() -> {
log.info("[{}]结束连接...................", uid);
LocalCache.CACHE.remove(uid);
});
//超时回调
sseEmitter.onTimeout(() -> {
log.info("[{}]连接超时...................", uid);
});
//异常回调
sseEmitter.onError(
throwable -> {
try {
log.info("[{}]连接异常,{}", uid, throwable.toString());
sseEmitter.send(SseEmitter.event()
.id(uid)
.name("发生异常!")
.data(Message.builder().content("发生异常请重试!").build())
.reconnectTime(3000));
LocalCache.CACHE.put(uid, sseEmitter);
} catch (IOException e) {
e.printStackTrace();
}
}
);
// try {
// sseEmitter.send(SseEmitter.event().reconnectTime(5000));
// } catch (IOException e) {
// e.printStackTrace();
// }
LocalCache.CACHE.put(uid, sseEmitter);
log.info("[{}]创建sse连接成功!", uid);
return sseEmitter;
}
@Override
public void closeSse(String uid) {
SseEmitter sse = (SseEmitter) LocalCache.CACHE.get(uid);
if (sse != null) {
sse.complete();
//移除
LocalCache.CACHE.remove(uid);
}
}
@Override
public ChatResponse sseChat(String uid, ChatRequest chatRequest, ChatCompletion.Model model, CompleteCallback completeCallback) {
if (ArrayUtil.isEmpty(chatRequest.getMsg())) {
log.info("参数异常,msg为null", uid);
throw new BaseException("参数异常,msg不能为空~");
}
// String messageContext = (String) LocalCache.CACHE.get("msg" + uid);
// if (StrUtil.isNotBlank(messageContext)) {
// messages = JSONUtil.toList(messageContext, Message.class);
// if (messages.size() >= 10) {
// messages = messages.subList(1, 10);
// }
// Message currentMessage = Message.builder().content(chatRequest.getMsg()).role(Message.Role.USER).build();
// messages.add(currentMessage);
// } else {
// Message currentMessage = Message.builder().content(chatRequest.getMsg()).role(Message.Role.USER).build();
// messages.add(currentMessage);
// }
List<Message> messages = new ArrayList<>();
for(String msg:chatRequest.getMsg())
{
Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
messages.add(currentMessage);
}
SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);
if (sseEmitter == null) {
log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);
throw new BaseException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");
}
OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(sseEmitter);
ChatCompletion completion = ChatCompletion
.builder()
.messages(messages)
.model(model.getName())
.build();
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
// LocalCache.CACHE.put("msg" + uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
ChatResponse response = new ChatResponse();
response.setQuestionTokens(completion.tokens());
if(null != completeCallback)
{
completeCallback.sseChatEnd(completion,openAIEventSourceListener);
}
return response;
}
}
... ...
package com.zhonglai.luhui.chatgpt.websocket;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.unfbx.chatgpt.OpenAiStreamClient;
import com.unfbx.chatgpt.entity.chat.Message;
import com.zhonglai.luhui.chatgpt.config.LocalCache;
import com.zhonglai.luhui.chatgpt.listener.OpenAIWebSocketEventSourceListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 描述:websocket 服务端
*
* @author https:www.unfbx.com
* @date 2023-03-23
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{uid}")
public class WebSocketServer {
private static OpenAiStreamClient openAiStreamClient;
@Autowired
public void setOrderService(OpenAiStreamClient openAiStreamClient) {
this.openAiStreamClient = openAiStreamClient;
}
//在线总数
private static int onlineCount;
//当前会话
private Session session;
//用户id -目前是按浏览器随机生成
private String uid;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 用来存放每个客户端对应的WebSocketServer对象
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap();
/**
* 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
*/
private final static List<Session> SESSIONS = Collections.synchronizedList(new ArrayList<>());
/**
* 建立连接
* @param session
* @param uid
*/
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) {
this.session = session;
this.uid = uid;
webSocketSet.add(this);
SESSIONS.add(session);
if (webSocketMap.containsKey(uid)) {
webSocketMap.remove(uid);
webSocketMap.put(uid, this);
} else {
webSocketMap.put(uid, this);
addOnlineCount();
}
log.info("[连接ID:{}] 建立连接, 当前连接数:{}", this.uid, getOnlineCount());
}
/**
* 断开连接
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
if (webSocketMap.containsKey(uid)) {
webSocketMap.remove(uid);
subOnlineCount();
}
log.info("[连接ID:{}] 断开连接, 当前连接数:{}", uid, getOnlineCount());
}
/**
* 发送错误
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("[连接ID:{}] 错误原因:{}", this.uid, error.getMessage());
error.printStackTrace();
}
/**
* 接收到客户端消息
* @param msg
*/
@OnMessage
public void onMessage(String msg) {
log.info("[连接ID:{}] 收到消息:{}", this.uid, msg);
//接受参数
OpenAIWebSocketEventSourceListener eventSourceListener = new OpenAIWebSocketEventSourceListener(this.session);
String messageContext = (String) LocalCache.CACHE.get(uid);
List<Message> messages = new ArrayList<>();
if (StrUtil.isNotBlank(messageContext)) {
messages = JSONUtil.toList(messageContext, Message.class);
if (messages.size() >= 10) {
messages = messages.subList(1, 10);
}
Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
messages.add(currentMessage);
} else {
Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
messages.add(currentMessage);
}
openAiStreamClient.streamChatCompletion(messages, eventSourceListener);
LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
}
/**
* 获取当前连接数
*
* @return
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 当前连接数加一
*/
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
/**
* 当前连接数减一
*/
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
... ...
.markdown-body{color-scheme:dark;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;margin:0;color:#c9d1d9;background-color:#0d1117;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body .octicon{display:inline-block;fill:currentColor;vertical-align:text-bottom}.markdown-body h1:hover .anchor .octicon-link:before,.markdown-body h2:hover .anchor .octicon-link:before,.markdown-body h3:hover .anchor .octicon-link:before,.markdown-body h4:hover .anchor .octicon-link:before,.markdown-body h5:hover .anchor .octicon-link:before,.markdown-body h6:hover .anchor .octicon-link:before{width:16px;height:16px;content:' ';display:inline-block;background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");mask-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>")}.markdown-body details,.markdown-body figcaption,.markdown-body figure{display:block}.markdown-body summary{display:list-item}.markdown-body [hidden]{display:none!important}.markdown-body a{background-color:transparent;color:#58a6ff;text-decoration:none}.markdown-body abbr[title]{border-bottom:none;text-decoration:underline dotted}.markdown-body b,.markdown-body strong{font-weight:600}.markdown-body dfn{font-style:italic}.markdown-body h1{margin:.67em 0;font-weight:600;padding-bottom:.3em;font-size:2em;border-bottom:1px solid #21262d}.markdown-body mark{background-color:rgba(187,128,9,.15);color:#c9d1d9}.markdown-body small{font-size:90%}.markdown-body sub,.markdown-body sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.markdown-body sub{bottom:-.25em}.markdown-body sup{top:-.5em}.markdown-body img{border-style:none;max-width:100%;box-sizing:content-box;background-color:#0d1117}.markdown-body code,.markdown-body kbd,.markdown-body pre,.markdown-body samp{font-family:monospace;font-size:1em}.markdown-body figure{margin:1em 40px}.markdown-body hr{box-sizing:content-box;overflow:hidden;background:0 0;border-bottom:1px solid #21262d;height:.25em;padding:0;margin:24px 0;background-color:#30363d;border:0}.markdown-body input{font:inherit;margin:0;overflow:visible;font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body [type=button],.markdown-body [type=reset],.markdown-body [type=submit]{-webkit-appearance:button}.markdown-body [type=checkbox],.markdown-body [type=radio]{box-sizing:border-box;padding:0}.markdown-body [type=number]::-webkit-inner-spin-button,.markdown-body [type=number]::-webkit-outer-spin-button{height:auto}.markdown-body [type=search]::-webkit-search-cancel-button,.markdown-body [type=search]::-webkit-search-decoration{-webkit-appearance:none}.markdown-body ::-webkit-input-placeholder{color:inherit;opacity:.54}.markdown-body ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.markdown-body a:hover{text-decoration:underline}.markdown-body ::placeholder{color:#6e7681;opacity:1}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse;display:block;width:max-content;max-width:100%;overflow:auto}.markdown-body td,.markdown-body th{padding:0}.markdown-body details summary{cursor:pointer}.markdown-body details:not([open])>:not(summary){display:none!important}.markdown-body [role=button]:focus,.markdown-body a:focus,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=radio]:focus{outline:2px solid #58a6ff;outline-offset:-2px;box-shadow:none}.markdown-body [role=button]:focus:not(:focus-visible),.markdown-body a:focus:not(:focus-visible),.markdown-body input[type=checkbox]:focus:not(:focus-visible),.markdown-body input[type=radio]:focus:not(:focus-visible){outline:solid 1px transparent}.markdown-body [role=button]:focus-visible,.markdown-body a:focus-visible,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus-visible{outline:2px solid #58a6ff;outline-offset:-2px;box-shadow:none}.markdown-body a:not([class]):focus,.markdown-body a:not([class]):focus-visible,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus,.markdown-body input[type=radio]:focus-visible{outline-offset:0}.markdown-body kbd{display:inline-block;padding:3px 5px;font:11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;line-height:10px;color:#c9d1d9;vertical-align:middle;background-color:#161b22;border:solid 1px rgba(110,118,129,.4);border-bottom-color:rgba(110,118,129,.4);border-radius:6px;box-shadow:inset 0 -1px 0 rgba(110,118,129,.4)}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h2{font-weight:600;padding-bottom:.3em;font-size:1.5em;border-bottom:1px solid #21262d}.markdown-body h3{font-weight:600;font-size:1.25em}.markdown-body h4{font-weight:600;font-size:1em}.markdown-body h5{font-weight:600;font-size:.875em}.markdown-body h6{font-weight:600;font-size:.85em;color:#8b949e}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0;padding:0 1em;color:#8b949e;border-left:.25em solid #30363d}.markdown-body ol,.markdown-body ul{margin-top:0;margin-bottom:0;padding-left:2em}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code,.markdown-body samp,.markdown-body tt{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px}.markdown-body pre{margin-top:0;margin-bottom:0;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:12px;word-wrap:normal}.markdown-body .octicon{display:inline-block;overflow:visible!important;vertical-align:text-bottom;fill:currentColor}.markdown-body input::-webkit-inner-spin-button,.markdown-body input::-webkit-outer-spin-button{margin:0;-webkit-appearance:none;appearance:none}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:#f85149}.markdown-body .anchor{float:left;padding-right:4px;margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:0}.markdown-body blockquote,.markdown-body details,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:16px}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#c9d1d9;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 code,.markdown-body h1 tt,.markdown-body h2 code,.markdown-body h2 tt,.markdown-body h3 code,.markdown-body h3 tt,.markdown-body h4 code,.markdown-body h4 tt,.markdown-body h5 code,.markdown-body h5 tt,.markdown-body h6 code,.markdown-body h6 tt{padding:0 .2em;font-size:inherit}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{padding-bottom:0;border-bottom:0}.markdown-body ol.no-list,.markdown-body ul.no-list{padding:0;list-style-type:none}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=A]{list-style-type:upper-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body ol[type=I]{list-style-type:upper-roman}.markdown-body ol[type="1"]{list-style-type:decimal}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table th{font-weight:600}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid #30363d}.markdown-body table tr{background-color:#0d1117;border-top:1px solid #21262d}.markdown-body table tr:nth-child(2n){background-color:#161b22}.markdown-body table img{background-color:transparent}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:transparent}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #30363d}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:#c9d1d9}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:.2em .4em;margin:0;font-size:85%;white-space:break-spaces;background-color:rgba(110,118,129,.4);border-radius:6px}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre code{font-size:100%}.markdown-body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:0 0;border:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#161b22;border-radius:6px}.markdown-body pre code,.markdown-body pre tt{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#0d1117;border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:600;background:#161b22;border-top:0}.markdown-body [data-footnote-ref]::before{content:"["}.markdown-body [data-footnote-ref]::after{content:"]"}.markdown-body .footnotes{font-size:12px;color:#8b949e;border-top:1px solid #30363d}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes ol ul{display:inline-block;padding-left:16px;margin-top:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target::before{position:absolute;top:-8px;right:-8px;bottom:-8px;left:-24px;pointer-events:none;content:"";border:2px solid #1f6feb;border-radius:6px}.markdown-body .footnotes li:target{color:#c9d1d9}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body .pl-c{color:#8b949e}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#79c0ff}.markdown-body .pl-e,.markdown-body .pl-en{color:#d2a8ff}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:#c9d1d9}.markdown-body .pl-ent{color:#7ee787}.markdown-body .pl-k{color:#ff7b72}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:#a5d6ff}.markdown-body .pl-smw,.markdown-body .pl-v{color:#ffa657}.markdown-body .pl-bu{color:#f85149}.markdown-body .pl-ii{color:#f0f6fc;background-color:#8e1519}.markdown-body .pl-c2{color:#f0f6fc;background-color:#b62324}.markdown-body .pl-sr .pl-cce{font-weight:700;color:#7ee787}.markdown-body .pl-ml{color:#f2cc60}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{font-weight:700;color:#1f6feb}.markdown-body .pl-mi{font-style:italic;color:#c9d1d9}.markdown-body .pl-mb{font-weight:700;color:#c9d1d9}.markdown-body .pl-md{color:#ffdcd7;background-color:#67060c}.markdown-body .pl-mi1{color:#aff5b4;background-color:#033a16}.markdown-body .pl-mc{color:#ffdfb6;background-color:#5a1e02}.markdown-body .pl-mi2{color:#c9d1d9;background-color:#1158c7}.markdown-body .pl-mdr{font-weight:700;color:#d2a8ff}.markdown-body .pl-ba{color:#8b949e}.markdown-body .pl-sg{color:#484f58}.markdown-body .pl-corl{text-decoration:underline;color:#a5d6ff}.markdown-body g-emoji{display:inline-block;min-width:1ch;font-family:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1em;font-style:normal!important;font-weight:400;line-height:1;vertical-align:-.075em}.markdown-body g-emoji img{width:1em;height:1em}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item label{font-weight:400}.markdown-body .task-list-item.enabled label{cursor:pointer}.markdown-body .task-list-item+.task-list-item{margin-top:4px}.markdown-body .task-list-item .handle{display:none}.markdown-body .task-list-item-checkbox{margin:0 .2em .25em -1.4em;vertical-align:middle}.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}.markdown-body .contains-task-list{position:relative}.markdown-body .contains-task-list:focus-within .task-list-item-convert-container,.markdown-body .contains-task-list:hover .task-list-item-convert-container{display:block;width:auto;height:24px;overflow:visible;clip:auto}.markdown-body ::-webkit-calendar-picker-indicator{filter:invert(50%)}
\ No newline at end of file
... ...
/** @license
* eventsource.js
* Available under MIT License (MIT)
* https://github.com/Yaffle/EventSource/
*/
!function(e){"use strict";var r,H=e.setTimeout,N=e.clearTimeout,j=e.XMLHttpRequest,o=e.XDomainRequest,t=e.ActiveXObject,n=e.EventSource,i=e.document,w=e.Promise,d=e.fetch,a=e.Response,h=e.TextDecoder,s=e.TextEncoder,p=e.AbortController;function c(){this.bitsNeeded=0,this.codePoint=0}"undefined"==typeof window||void 0===i||"readyState"in i||null!=i.body||(i.readyState="loading",window.addEventListener("load",function(e){i.readyState="complete"},!1)),null==j&&null!=t&&(j=function(){return new t("Microsoft.XMLHTTP")}),null==Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),Date.now||(Date.now=function(){return(new Date).getTime()}),null==p&&(r=d,d=function(e,t){var n=t.signal;return r(e,{headers:t.headers,credentials:t.credentials,cache:t.cache}).then(function(e){var t=e.body.getReader();return n._reader=t,n._aborted&&n._reader.cancel(),{status:e.status,statusText:e.statusText,headers:e.headers,body:{getReader:function(){return t}}}})},p=function(){this.signal={_reader:null,_aborted:!1},this.abort=function(){null!=this.signal._reader&&this.signal._reader.cancel(),this.signal._aborted=!0}}),c.prototype.decode=function(e){function t(e,t,n){if(1===n)return 128>>t<=e&&e<<t<=2047;if(2===n)return 2048>>t<=e&&e<<t<=55295||57344>>t<=e&&e<<t<=65535;if(3===n)return 65536>>t<=e&&e<<t<=1114111;throw new Error}function n(e,t){if(6===e)return 15<t>>6?3:31<t?2:1;if(12===e)return 15<t?3:2;if(18===e)return 3;throw new Error}for(var r="",o=this.bitsNeeded,i=this.codePoint,a=0;a<e.length;a+=1){var s=e[a];0!==o&&(s<128||191<s||!t(i<<6|63&s,o-6,n(o,i)))&&(o=0,i=65533,r+=String.fromCharCode(i)),0===o?(i=0<=s&&s<=127?(o=0,s):192<=s&&s<=223?(o=6,31&s):224<=s&&s<=239?(o=12,15&s):240<=s&&s<=247?(o=18,7&s):(o=0,65533),0===o||t(i,o,n(o,i))||(o=0,i=65533)):(o-=6,i=i<<6|63&s),0===o&&(i<=65535?r+=String.fromCharCode(i):r=(r+=String.fromCharCode(55296+(i-65535-1>>10)))+String.fromCharCode(56320+(i-65535-1&1023)))}return this.bitsNeeded=o,this.codePoint=i,r};function u(){}null!=h&&null!=s&&function(){try{return"test"===(new h).decode((new s).encode("test"),{stream:!0})}catch(e){}return!1}()||(h=c);function I(e){this.withCredentials=!1,this.readyState=0,this.status=0,this.statusText="",this.responseText="",this.onprogress=u,this.onload=u,this.onerror=u,this.onreadystatechange=u,this._contentType="",this._xhr=e,this._sendTimeout=0,this._abort=u}function l(e){return e.replace(/[A-Z]/g,function(e){return String.fromCharCode(e.charCodeAt(0)+32)})}function f(e){for(var t=Object.create(null),n=e.split("\r\n"),r=0;r<n.length;r+=1){var o=n[r].split(": "),i=o.shift(),o=o.join(": ");t[l(i)]=o}this._map=t}function P(){}function y(e){this._headers=e}function L(){}function M(){this._listeners=Object.create(null)}function b(e){H(function(){throw e},0)}function v(e){this.type=e,this.target=void 0}function $(e,t){v.call(this,e),this.data=t.data,this.lastEventId=t.lastEventId}function q(e,t){v.call(this,e),this.status=t.status,this.statusText=t.statusText,this.headers=t.headers}function F(e,t){v.call(this,e),this.error=t.error}I.prototype.open=function(e,t){this._abort(!0);var o=this,i=this._xhr,a=1,n=0,r=(this._abort=function(e){0!==o._sendTimeout&&(N(o._sendTimeout),o._sendTimeout=0),1!==a&&2!==a&&3!==a||(a=4,i.onload=u,i.onerror=u,i.onabort=u,i.onprogress=u,i.onreadystatechange=u,i.abort(),0!==n&&(N(n),n=0),e||(o.readyState=4,o.onabort(null),o.onreadystatechange())),a=0},function(){if(1===a){var t=0,n="",r=void 0;if("contentType"in i)t=200,n="OK",r=i.contentType;else try{t=i.status,n=i.statusText,r=i.getResponseHeader("Content-Type")}catch(e){n="",r=void(t=0)}0!==t&&(a=2,o.readyState=2,o.status=t,o.statusText=n,o._contentType=r,o.onreadystatechange())}}),s=function(){if(r(),2===a||3===a){a=3;var e="";try{e=i.responseText}catch(e){}o.readyState=3,o.responseText=e,o.onprogress()}},c=function(e,t){if(null!=t&&null!=t.preventDefault||(t={preventDefault:u}),s(),1===a||2===a||3===a){if(a=4,0!==n&&(N(n),n=0),o.readyState=4,"load"===e)o.onload(t);else if("error"===e)o.onerror(t);else{if("abort"!==e)throw new TypeError;o.onabort(t)}o.onreadystatechange()}},l=function(){n=H(function(){l()},500),3===i.readyState&&s()};"onload"in i&&(i.onload=function(e){c("load",e)}),"onerror"in i&&(i.onerror=function(e){c("error",e)}),"onabort"in i&&(i.onabort=function(e){c("abort",e)}),"onprogress"in i&&(i.onprogress=s),"onreadystatechange"in i&&(i.onreadystatechange=function(e){e=e,null!=i&&(4===i.readyState?"onload"in i&&"onerror"in i&&"onabort"in i||c(""===i.responseText?"error":"load",e):3===i.readyState?"onprogress"in i||s():2===i.readyState&&r())}),!("contentType"in i)&&"ontimeout"in j.prototype||(t+=(-1===t.indexOf("?")?"?":"&")+"padding=true"),i.open(e,t,!0),"readyState"in i&&(n=H(function(){l()},0))},I.prototype.abort=function(){this._abort(!1)},I.prototype.getResponseHeader=function(e){return this._contentType},I.prototype.setRequestHeader=function(e,t){var n=this._xhr;"setRequestHeader"in n&&n.setRequestHeader(e,t)},I.prototype.getAllResponseHeaders=function(){return null!=this._xhr.getAllResponseHeaders&&this._xhr.getAllResponseHeaders()||""},I.prototype.send=function(){var e;if("ontimeout"in j.prototype&&("sendAsBinary"in j.prototype||"mozAnon"in j.prototype)||null==i||null==i.readyState||"complete"===i.readyState){var t=this._xhr;"withCredentials"in t&&(t.withCredentials=this.withCredentials);try{t.send(void 0)}catch(e){throw e}}else(e=this)._sendTimeout=H(function(){e._sendTimeout=0,e.send()},4)},f.prototype.get=function(e){return this._map[l(e)]},null!=j&&null==j.HEADERS_RECEIVED&&(j.HEADERS_RECEIVED=2),P.prototype.open=function(o,i,t,n,e,r,a){o.open("GET",e);var s,c=0;for(s in o.onprogress=function(){var e=o.responseText.slice(c);c+=e.length,t(e)},o.onerror=function(e){e.preventDefault(),n(new Error("NetworkError"))},o.onload=function(){n(null)},o.onabort=function(){n(null)},o.onreadystatechange=function(){var e,t,n,r;o.readyState===j.HEADERS_RECEIVED&&(e=o.status,t=o.statusText,n=o.getResponseHeader("Content-Type"),r=o.getAllResponseHeaders(),i(e,t,n,new f(r)))},o.withCredentials=r,a)Object.prototype.hasOwnProperty.call(a,s)&&o.setRequestHeader(s,a[s]);return o.send(),o},y.prototype.get=function(e){return this._headers.get(e)},L.prototype.open=function(e,t,o,n,r,i,a){var s=null,c=new p,l=c.signal,u=new h;return d(r,{headers:a,credentials:i?"include":"same-origin",signal:l,cache:"no-store"}).then(function(e){return s=e.body.getReader(),t(e.status,e.statusText,e.headers.get("Content-Type"),new y(e.headers)),new w(function(t,n){function r(){s.read().then(function(e){e.done?t(void 0):(e=u.decode(e.value,{stream:!0}),o(e),r())}).catch(function(e){n(e)})}r()})}).catch(function(e){if("AbortError"!==e.name)return e}).then(function(e){n(e)}),{abort:function(){null!=s&&s.cancel(),c.abort()}}},M.prototype.dispatchEvent=function(e){var t=(e.target=this)._listeners[e.type];if(null!=t)for(var n=t.length,r=0;r<n;r+=1){var o=t[r];try{"function"==typeof o.handleEvent?o.handleEvent(e):o.call(this,e)}catch(e){b(e)}}},M.prototype.addEventListener=function(e,t){e=String(e);for(var n=this._listeners,r=n[e],o=(null==r&&(n[e]=r=[]),!1),i=0;i<r.length;i+=1)r[i]===t&&(o=!0);o||r.push(t)},M.prototype.removeEventListener=function(e,t){e=String(e);var n=this._listeners,r=n[e];if(null!=r){for(var o=[],i=0;i<r.length;i+=1)r[i]!==t&&o.push(r[i]);0===o.length?delete n[e]:n[e]=o}},$.prototype=Object.create(v.prototype),q.prototype=Object.create(v.prototype),F.prototype=Object.create(v.prototype);var X=-1,G=0,V=1,B=2,k=-1,z=0,K=1,J=2,W=3,Y=/^text\/event\-stream(;.*)?$/i,E=1e3,m=18e6,Q=function(e,t){e=null==e?t:parseInt(e,10);return U(e=e!=e?t:e)},U=function(e){return Math.min(Math.max(e,E),m)},Z=function(e,t,n){try{"function"==typeof t&&t.call(e,n)}catch(e){b(e)}};function g(e,t){function i(e,t,n,r){var o;m===G&&(200===e&&null!=n&&Y.test(n)?(m=V,y=Date.now(),f=d,c.readyState=V,o=new q("open",{status:e,statusText:t,headers:r}),c.dispatchEvent(o),Z(c,c.onopen,o)):(200!==e?t=t&&t.replace(/\s+/g," "):null!=n&&n.replace(/\s+/g," "),O(),o=new q("error",{status:e,statusText:t,headers:r}),c.dispatchEvent(o),Z(c,c.onerror,o)))}function a(e){if(m===V){for(var t=-1,n=0;n<e.length;n+=1)(a=e.charCodeAt(n))!=="\n".charCodeAt(0)&&a!=="\r".charCodeAt(0)||(t=n);var r=(-1!==t?S:"")+e.slice(0,t+1);S=(-1===t?S:"")+e.slice(t+1),""!==e&&(y=Date.now(),v+=e.length);for(var o=0;o<r.length;o+=1){var i,a=r.charCodeAt(o);if(x===k&&a==="\n".charCodeAt(0))x=z;else if(x===k&&(x=z),a==="\r".charCodeAt(0)||a==="\n".charCodeAt(0)){if(x!==z&&(x===K&&(R=o+1),s=r.slice(A,R-1),i=r.slice(R+(R<o&&r.charCodeAt(R)===" ".charCodeAt(0)?1:0),o),"data"===s?C=C+"\n"+i:"id"===s?T=i:"event"===s?_=i:"retry"===s?(d=Q(i,d),f=d):"heartbeatTimeout"===s&&(h=Q(i,h),0!==E&&(N(E),E=H(function(){D()},h)))),x===z){if(""!==C){p=T;var s=new $(_=""===_?"message":_,{data:C.slice(1),lastEventId:T});if(c.dispatchEvent(s),"open"===_?Z(c,c.onopen,s):"message"===_?Z(c,c.onmessage,s):"error"===_&&Z(c,c.onerror,s),m===B)return}_=C=""}x=a==="\r".charCodeAt(0)?k:z}else x===z&&(A=o,x=K),x===K?a===":".charCodeAt(0)&&(R=o+1,x=J):x===J&&(x=W)}}}function s(e){m!==V&&m!==G||(m=X,0!==E&&(N(E),E=0),E=H(function(){D()},f),f=U(Math.min(16*d,2*f)),c.readyState=G,e=new F("error",{error:e}),c.dispatchEvent(e),Z(c,c.onerror,e))}var c,l,u,d,h,p,f,y,v,n,g,w,b,E,m,C,T,_,S,x,A,R,O,D;M.call(this),t=t||{},this.onopen=void 0,this.onmessage=void 0,this.onerror=void 0,this.url=void 0,this.readyState=void 0,this.withCredentials=void 0,this.headers=void 0,this._close=void 0,c=this,l=e,e=t,l=String(l),t=Boolean(e.withCredentials),u=e.lastEventIdQueryParameterName||"lastEventId",d=U(1e3),h=Q(e.heartbeatTimeout,45e3),p="",f=d,y=!1,v=0,n=e.headers||{},e=e.Transport,g=ee&&null==e?void 0:new I(new(null!=e?e:null!=j&&"withCredentials"in j.prototype||null==o?j:o)),w=new(null!=e&&"string"!=typeof e?e:null==g?L:P),b=void 0,m=X,S=_=T=C="",x=z,R=A=E=0,O=function(){m=B,null!=b&&(b.abort(),b=void 0),0!==E&&(N(E),E=0),c.readyState=B},D=function(){if(E=0,m!==X)y||null==b?(e=Math.max((y||Date.now())+h-Date.now(),1),y=!1,E=H(function(){D()},e)):(s(new Error("No activity within "+h+" milliseconds. "+(m===G?"No response received.":v+" chars received.")+" Reconnecting.")),null!=b&&(b.abort(),b=void 0));else{y=!1,v=0,E=H(function(){D()},h),m=G,T=p,S=_=C="",R=A=0,x=z;var e=l,t=("data:"!==l.slice(0,5)&&"blob:"!==l.slice(0,5)&&""!==p&&(e=-1===(t=l.indexOf("?"))?l:l.slice(0,t+1)+l.slice(t+1).replace(/(?:^|&)([^=&]*)(?:=[^&]*)?/g,function(e,t){return t===u?"":e}),e+=(-1===l.indexOf("?")?"?":"&")+u+"="+encodeURIComponent(p)),c.withCredentials),n={Accept:"text/event-stream"},r=c.headers;if(null!=r)for(var o in r)Object.prototype.hasOwnProperty.call(r,o)&&(n[o]=r[o]);try{b=w.open(g,i,a,s,e,t,n)}catch(e){throw O(),e}}},c.url=l,c.readyState=G,c.withCredentials=t,c.headers=n,c._close=O,D()}var ee=null!=d&&null!=a&&"body"in a.prototype;(g.prototype=Object.create(M.prototype)).CONNECTING=G,g.prototype.OPEN=V,g.prototype.CLOSED=B,g.prototype.close=function(){this._close()},g.CONNECTING=G,g.OPEN=V,g.CLOSED=B,g.prototype.withCredentials=void 0;var C=n;null==j||null!=n&&"withCredentials"in n.prototype||(C=g),a=function(e){e.EventSourcePolyfill=g,e.NativeEventSource=n,e.EventSource=C},"object"==typeof module&&"object"==typeof module.exports?a(exports):"function"==typeof define&&define.amd?define(["exports"],a):a(e)}("undefined"==typeof globalThis?"undefined"!=typeof window?window:"undefined"!=typeof self?self:this:globalThis);
\ No newline at end of file
... ...
/**
* marked - a markdown parser
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*/
(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",/<!--[\s\S]*?-->/)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[2]==="="?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"hr"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"blockquote_start"});cap=cap[0].replace(/^ *> ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\d+\.) +/,"");if(~item.indexOf("\n ")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp("^ {1,"+space+"}","gm"),""):item.replace(/^ {1,4}/gm,"")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\| *| *\| *$/g,"").split(/ *\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:"paragraph",text:cap[1].charAt(cap[1].length-1)==="\n"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"text",text:cap[0]});continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};inline._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;inline._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"鈥�").replace(/--/g,"鈥�").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1鈥�").replace(/'/g,"鈥�").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1鈥�").replace(/"/g,"鈥�").replace(/\.{3}/g,"鈥�")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"<pre><code>"+(escaped?code:escape(code,true))+"\n</code></pre>"}return'<pre><code class="'+this.options.langPrefix+escape(lang,true)+'">'+(escaped?code:escape(code,true))+"\n</code></pre>\n"};Renderer.prototype.blockquote=function(quote){return"<blockquote>\n"+quote+"</blockquote>\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"<h"+level+' id="'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\w]+/g,"-")+'">'+text+"</h"+level+">\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"</"+type+">\n"};Renderer.prototype.listitem=function(text){return"<li>"+text+"</li>\n"};Renderer.prototype.paragraph=function(text){return"<p>"+text+"</p>\n"};Renderer.prototype.table=function(header,body){return"<table>\n"+"<thead>\n"+header+"</thead>\n"+"<tbody>\n"+body+"</tbody>\n"+"</table>\n"};Renderer.prototype.tablerow=function(content){return"<tr>\n"+content+"</tr>\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"</"+type+">\n"};Renderer.prototype.strong=function(text){return"<strong>"+text+"</strong>"};Renderer.prototype.em=function(text){return"<em>"+text+"</em>"};Renderer.prototype.codespan=function(text){return"<code>"+text+"</code>"};Renderer.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"};Renderer.prototype.del=function(text){return"<del>"+text+"</del>"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='<a href="'+href+'"';if(title){out+=' title="'+title+'"'}out+=">"+text+"</a>";return out};Renderer.prototype.image=function(href,title,text){var out='<img src="'+href+'" alt="'+text+'"';if(title){out+=' title="'+title+'"'}out+=this.options.xhtml?"/>":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell="";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case"blockquote_start":{var body="";while(this.next().type!=="blockquote_end"){body+=this.tok()}return this.renderer.blockquote(body)}case"list_start":{var body="",ordered=this.token.ordered;while(this.next().type!=="list_end"){body+=this.tok()}return this.renderer.list(body,ordered)}case"list_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.token.type==="text"?this.parseText():this.tok()}return this.renderer.listitem(body)}case"loose_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.tok()}return this.renderer.listitem(body)}case"html":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case"paragraph":{return this.renderer.paragraph(this.inline.output(this.token.text))}case"text":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\w+;)/g:/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt==="function"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!=="code"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+="\nPlease report this to https://github.com/chjj/marked.";if((opt||marked.defaults).silent){return"<p>An error occured:</p><pre>"+escape(e.message+"",true)+"</pre>"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}());
\ No newline at end of file
... ...
<!DOCTYPE html>
<html lang="en">
<link href="css/markdown.css" rel="stylesheet" type="text/css"/>
<head>
<meta charset="UTF-8">
<title>战损版ChatGPT-SSE实现流式输出</title>
<link rel="icon" type="image/png" sizes="32x32" href="image/favicon-32x32.png">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="js/markdown.min.js"></script>
<script src="js/eventsource.min.js"></script>
<script>
function setText(text, uuid_str) {
let content = document.getElementById(uuid_str)
content.innerHTML = marked(text);
}
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
window.onload = function () {
let disconnectBtn = document.getElementById("disconnectSSE");
let messageElement = document.getElementById("message");
let chat = document.getElementById("chat");
let sse;
let uid = window.localStorage.getItem("uid");
if (uid == null || uid == '' || uid == 'null') {
uid = uuid();
}
let text = '';
let uuid_str;
// 设置本地存储
window.localStorage.setItem("uid", uid);
// 回车事件
messageElement.onkeydown = function () {
if (window.event.keyCode === 13) {
if (!messageElement.value) {
return;
}
uuid_str = uuid();
//创建sse
const eventSource = new EventSourcePolyfill('/api/createSse', {
headers: {
'uid': uid
}
});
eventSource.onopen = (event) => {
console.log("开始输出后端返回值");
sse = event.target;
};
eventSource.onmessage = (event) => {
if (event.lastEventId == "[TOKENS]") {
text = text + event.data;
setText(text, uuid_str)
text = ''
return;
}
if (event.data == "[DONE]") {
if (sse) {
sse.close();
}
return;
}
let json_data = JSON.parse(event.data)
if (json_data.content == null || json_data.content == 'null') {
return;
}
text = text + json_data.content;
setText(text, uuid_str)
};
eventSource.onerror = (event) => {
console.log("onerror", event);
alert("服务异常请重试并联系开发者!")
if (event.readyState === EventSource.CLOSED) {
console.log('connection is closed');
} else {
console.log("Error occured", event);
}
event.target.close();
};
eventSource.addEventListener("customEventName", (event) => {
console.log("Message id is " + event.lastEventId);
});
eventSource.addEventListener("customEventName", (event) => {
console.log("Message id is " + event.lastEventId);
});
$.ajax({
type: 'post',
url: '/api/chat',
data: JSON.stringify({
'msg': messageElement.value
}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
headers: {
"uid": uid,
},
beforeSend: function (request) {
},
success: function (result) {
//新增问题框
chat.innerHTML += '<tr><td style="height: 30px;">' + messageElement.value + '<br/><br/> tokens:' + result.question_tokens + '</td></tr>';
messageElement.value = null
//新增答案框
chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
},
complete: function () {
},
error: function () {
console.info("发送问题失败!");
}
})
}
};
disconnectBtn.onclick = function () {
if (sse) {
sse.close();
}
};
};
</script>
</head>
<body>
<div class="float-card">
<div class="float-card-item" id="disconnectSSE">
<a rel="noopener noreferrer">停止输出</a>
</div>
</div>
<div class="input-card">
<div class="input-card-item">
<input id="message" placeholder="输入你的问题,回车结束......" type="text">
</div>
</div>
<div class="container" >
<table border="1">
<tbody id="chat">
</tbody>
</table>
</div>
</body>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
input {
height: 50px;
width: 500px;
font-size: 20px;
background: no-repeat;
color: #d0838e;
}
.container {
width: 980px;
border: 1px solid black;
display: flex;
flex-direction: column;
margin-left: 150px;
margin-top: 40px;
}
.input-card {
position: fixed;
display: inline-block;
right: 37%;
top: 80%;
}
.input-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 16px;
}
.float-card {
position: fixed;
display: inline-block;
right: 120px;
top: 100px;
}
.float-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #ccccd6;
margin-bottom: 16px;
}
.float-card-item:last-child {
margin-bottom: 0px;
background-color: #d0838e;
}
.float-card-item a {
text-decoration: none;
color: #594649;
font-size: 13px;
}
</style>
</html>
\ No newline at end of file
... ...
<!DOCTYPE html>
<html lang="en">
<link href="../static/css/markdown.css" rel="stylesheet" type="text/css"/>
<head>
<meta charset="UTF-8">
<title>战损版ChatGPT</title>
<link rel="icon" type="image/png" sizes="32x32" href="../static/image/favicon-32x32.png">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="../static/js/markdown.min.js"></script>
<script>
function setText(text, uuid_str) {
let content = document.getElementById(uuid_str)
content.innerHTML = marked(text);
}
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
window.onload = function () {
let disconnectBtn = document.getElementById("disconnectSSE");
let messageElement = document.getElementById("message");
let chat = document.getElementById("chat");
let sse;
// 回车事件
messageElement.onkeydown = function () {
if (window.event.keyCode === 13) {
if (!messageElement.value) {
return;
}
let text = '';
let uuid_str = uuid();
const eventSource = new EventSource('http://localhost:8000/chat?message=' + messageElement.value);
eventSource.onopen = (event) => {
console.log("onopen", event.readyState, event.target);
sse = event.target;
//新增问题框
chat.innerHTML += '<tr><td style="height: 50px;">' + messageElement.value + '</td></tr>';
messageElement.value = null
//新增答案框
chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
};
eventSource.onmessage = (event) => {
if (event.data == "[DONE]") {
text = '';
if (sse) {
sse.close();
}
return;
}
let json_data = JSON.parse(event.data)
if (json_data.content == null || json_data.content == 'null') {
text = '';
return;
}
text = text + json_data.content;
setText(text, uuid_str)
};
eventSource.onerror = (event) => {
console.log("onerror", event);
alert("服务异常请重试并联系开发者!")
if (event.readyState === EventSource.CLOSED) {
console.log('connection is closed');
} else {
console.log("Error occured", event);
}
event.target.close();
};
eventSource.addEventListener("customEventName", (event) => {
console.log("Message id is " + event.lastEventId);
});
}
};
disconnectBtn.onclick = function () {
if (sse) {
sse.close();
}
};
};
</script>
</head>
<body>
<!--<div class="float-card-item send-btn">-->
<!-- <a id="connectSSE" rel="noopener noreferrer">发送请求</a>-->
<!--</div>-->
<!--<div class="float-card-item dis-btn">-->
<!-- <a id="disconnectSSE" rel="noopener noreferrer">断开连接</a>-->
<!--</div>-->
<div class="float-card">
<div class="float-card-item">
<a href="https://www.unfbx.com" target="_blank" rel="noopener noreferrer">Website</a>
</div>
<div class="float-card-item">
<a href="https://github.com/Grt1228" target="_blank" rel="noopener noreferrer">Github</a>
</div>
<div class="float-card-item">
<a id="disconnectSSE" rel="noopener noreferrer">停止输出</a>
</div>
</div>
<div class="input-card">
<div class="input-card-item">
<input id="message" placeholder="输入你的问题,回车结束......" type="text">
</div>
</div>
<div class="container">
<table border="1">
<tbody id="chat">
<tr>
<td>
<pre style="font-size: 15px">
帮忙点个star吧<br/>
1、依赖ChatGPT开源Java SDK:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-java"
target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-java</a><br/>
2、本项目免费开源地址:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-steam-output"
target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-steam-output</a><br/>
3、默认保持连接5分钟,默认上下文保持10个,5分钟无请求上下文会话销毁
</pre>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
input {
height: 50px;
width: 500px;
font-size: 20px;
background: url(10) no-repeat;
color: #d0838e;
}
.container {
width: 980px;
border: 1px solid black;
display: flex;
flex-direction: column;
margin-left: 150px;
margin-top: 40px;
}
.input-card {
position: fixed;
display: inline-block;
right: 37%;
top: 80%;
}
.input-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 16px;
}
.float-card {
position: fixed;
display: inline-block;
right: 120px;
top: 100px;
}
.float-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #ccccd6;
margin-bottom: 16px;
}
.float-card-item:last-child {
margin-bottom: 0px;
background-color: #d0838e;
}
.float-card-item a {
text-decoration: none;
color: #594649;
font-size: 13px;
}
</style>
</html>
\ No newline at end of file
... ...
<!DOCTYPE html>
<html lang="en">
<link href="css/markdown.css" rel="stylesheet" type="text/css"/>
<head>
<meta charset="UTF-8">
<title>战损版ChatGPT-WebSocket实现流式输出</title>
<link rel="icon" type="image/png" sizes="32x32" href="image/favicon-32x32.png">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="js/markdown.min.js"></script>
<script>
function setText(text, uuid_str) {
let content = document.getElementById(uuid_str)
content.innerHTML = marked(text);
}
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
window.onload = function () {
var socket
let uuid_str = '';
let text = '';
let disconnectBtn = document.getElementById("disconnectSSE");
let messageElement = document.getElementById("message");
let chat = document.getElementById("chat");
let uid = window.localStorage.getItem("uid");
if (uid == null || uid == '' || uid == 'null') {
uid = uuid();
}
// 设置本地存储
window.localStorage.setItem("uid", uid);
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象
//指定要连接的服务器地址与端口建立连接
//注意ws、wss使用不同的端口。我使用自签名的证书测试,
//无法使用wss,浏览器打开WebSocket时报错
//ws对应http、wss对应https。
socket = new WebSocket("ws://localhost:8000/websocket/"+uid);
//连接打开事件
socket.onopen = function () {
console.log("Socket 已打开");
};
//收到消息事件
socket.onmessage = function (msg) {
if (msg.data == "[DONE]") {
text = '';
return;
}
let json_data = JSON.parse(msg.data)
if (json_data.content == null || json_data.content == 'null') {
text = '';
return;
}
text = text + json_data.content;
setText(text, uuid_str)
};
//连接关闭事件
socket.onclose = function () {
console.log("Socket已关闭");
};
//发生了错误事件
socket.onerror = function () {
alert("服务异常请重试并联系开发者!")
}
//窗口关闭时,关闭连接
window.unload = function () {
socket.close();
};
}
// 回车事件
messageElement.onkeydown = function () {
if (window.event.keyCode === 13) {
if (!messageElement.value) {
return;
}
uuid_str = uuid();
socket.send(messageElement.value);
//新增问题框
chat.innerHTML += '<tr><td style="height: 50px;">' + messageElement.value + '</td></tr>';
messageElement.value = null
//新增答案框
chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
}
};
disconnectBtn.onclick = function () {
if (socket) {
socket.close();
}
};
};
</script>
</head>
<body>
<!--<div class="float-card-item send-btn">-->
<!-- <a id="connectSSE" rel="noopener noreferrer">发送请求</a>-->
<!--</div>-->
<!--<div class="float-card-item dis-btn">-->
<!-- <a id="disconnectSSE" rel="noopener noreferrer">断开连接</a>-->
<!--</div>-->
<div class="float-card">
<div class="float-card-item">
<a href="https://www.unfbx.com" target="_blank" rel="noopener noreferrer">Website</a>
</div>
<div class="float-card-item">
<a href="https://github.com/Grt1228" target="_blank" rel="noopener noreferrer">Github</a>
</div>
<div class="float-card-item">
<a id="disconnectSSE" rel="noopener noreferrer">停止输出</a>
</div>
</div>
<div class="input-card">
<div class="input-card-item">
<input id="message" placeholder="输入你的问题,回车结束......" type="text">
</div>
</div>
<div class="container">
<table border="1">
<tbody id="chat">
<tr>
<td>
<pre style="font-size: 15px">
帮忙点个star吧<br/>
1、依赖ChatGPT开源Java SDK:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-java"
target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-java</a><br/>
2、本项目免费开源地址:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-steam-output"
target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-steam-output</a><br/>
3、默认保持连接5分钟,默认上下文保持10个,5分钟无请求上下文会话销毁。
</pre>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
input {
height: 50px;
width: 500px;
font-size: 20px;
background: url(10) no-repeat;
color: #d0838e;
}
.container {
width: 980px;
border: 1px solid black;
display: flex;
flex-direction: column;
margin-left: 150px;
margin-top: 40px;
}
.input-card {
position: fixed;
display: inline-block;
right: 37%;
top: 80%;
}
.input-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 16px;
}
.float-card {
position: fixed;
display: inline-block;
right: 120px;
top: 100px;
}
.float-card-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #ccccd6;
margin-bottom: 16px;
}
.float-card-item:last-child {
margin-bottom: 0px;
background-color: #d0838e;
}
.float-card-item a {
text-decoration: none;
color: #594649;
font-size: 13px;
}
</style>
</html>
\ No newline at end of file
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-jar-rocketmq</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.ruoyi.system.rocketmq;
package com.zhonglai.luhui.rocketmq.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.domain.DeviceCommandApi;
import com.ruoyi.common.core.domain.Message;
import com.ruoyi.common.core.domain.MessageCode;
import com.ruoyi.common.core.domain.MessageCodeType;
import com.ruoyi.system.dto.DeviceCommandApi;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.exception.RequestTimeoutException;
... ...
<?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>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>Luhui</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>lh-jar-sys-service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-common-datasource</artifactId>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-public-dao</artifactId>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-domain</artifactId>
</dependency>
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.domain.sys.SysConfig;
import java.util.List;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.entity.SysDept;
import org.apache.ibatis.annotations.Param;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.entity.SysDictData;
import org.apache.ibatis.annotations.Param;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.entity.SysDictType;
import org.apache.ibatis.annotations.Mapper;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.sys.SysLogininfor;
import java.util.List;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.entity.SysMenu;
import org.apache.ibatis.annotations.Param;
... ...
package com.ruoyi.system.mapper;
package com.zhonglai.luhui.sys.mapper;
import com.ruoyi.system.domain.SysNotice;
import com.ruoyi.system.domain.sys.SysNotice;
import java.util.List;
... ...