作者 钟来

模块整理

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

要显示太多修改。

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

  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>lh-common</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + </parent>
  11 +
  12 + <artifactId>lh-common-datasource</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>8</maven.compiler.source>
  16 + <maven.compiler.target>8</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 +
  20 + <dependencies>
  21 + <dependency>
  22 + <groupId>com.zhonglai.luhui</groupId>
  23 + <artifactId>ruoyi-common</artifactId>
  24 + </dependency>
  25 +
  26 + <dependency>
  27 + <groupId>org.aspectj</groupId>
  28 + <artifactId>aspectjrt</artifactId>
  29 + </dependency>
  30 +
  31 + <!-- 阿里数据库连接池 -->
  32 + <dependency>
  33 + <groupId>com.alibaba</groupId>
  34 + <artifactId>druid-spring-boot-starter</artifactId>
  35 + </dependency>
  36 +
  37 + <dependency>
  38 + <groupId>tk.mybatis</groupId>
  39 + <artifactId>mapper</artifactId>
  40 + <!-- 建议使用最新版本,最新版本请从项目首页查找 -->
  41 + </dependency>
  42 + <dependency>
  43 + <groupId>tk.mybatis</groupId>
  44 + <artifactId>mapper-spring-boot-starter</artifactId>
  45 + </dependency>
  46 +
  47 + <!-- Mysql驱动包 -->
  48 + <dependency>
  49 + <groupId>mysql</groupId>
  50 + <artifactId>mysql-connector-java</artifactId>
  51 + </dependency>
  52 + </dependencies>
  53 +</project>
1 -package com.ruoyi.framework.aspectj; 1 +package com.zhonglai.luhui.datasource.aspectj;
2 2
3 -import com.ruoyi.common.annotation.DataSource;  
4 import com.ruoyi.common.utils.StringUtils; 3 import com.ruoyi.common.utils.StringUtils;
5 -import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; 4 +import com.zhonglai.luhui.datasource.config.DynamicDataSourceContextHolder;
  5 +import com.zhonglai.luhui.datasource.enums.DataSource;
6 import org.aspectj.lang.ProceedingJoinPoint; 6 import org.aspectj.lang.ProceedingJoinPoint;
7 import org.aspectj.lang.annotation.Around; 7 import org.aspectj.lang.annotation.Around;
8 import org.aspectj.lang.annotation.Aspect; 8 import org.aspectj.lang.annotation.Aspect;
@@ -28,8 +28,8 @@ public class DataSourceAspect @@ -28,8 +28,8 @@ public class DataSourceAspect
28 { 28 {
29 protected Logger logger = LoggerFactory.getLogger(getClass()); 29 protected Logger logger = LoggerFactory.getLogger(getClass());
30 30
31 - @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"  
32 - + "|| @within(com.ruoyi.common.annotation.DataSource)") 31 + @Pointcut("@annotation(com.zhonglai.luhui.datasource.enums.DataSource)"
  32 + + "|| @within(com.zhonglai.luhui.datasource.enums.DataSource)")
33 public void dsPointCut() 33 public void dsPointCut()
34 { 34 {
35 35
1 -package com.ruoyi.framework.config; 1 +package com.zhonglai.luhui.datasource.config;
2 2
3 import com.alibaba.druid.pool.DruidDataSource; 3 import com.alibaba.druid.pool.DruidDataSource;
4 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; 4 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
5 import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; 5 import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
6 import com.alibaba.druid.util.Utils; 6 import com.alibaba.druid.util.Utils;
7 -import com.ruoyi.common.enums.DataSourceType; 7 +import com.zhonglai.luhui.datasource.enums.DataSourceType;
8 import com.ruoyi.common.utils.spring.SpringUtils; 8 import com.ruoyi.common.utils.spring.SpringUtils;
9 -import com.ruoyi.framework.config.properties.DruidProperties;  
10 -import com.ruoyi.framework.datasource.DynamicDataSource;  
11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 import org.springframework.boot.context.properties.ConfigurationProperties; 10 import org.springframework.boot.context.properties.ConfigurationProperties;
13 import org.springframework.boot.web.servlet.FilterRegistrationBean; 11 import org.springframework.boot.web.servlet.FilterRegistrationBean;
1 -package com.ruoyi.framework.config.properties; 1 +package com.zhonglai.luhui.datasource.config;
2 2
3 import com.alibaba.druid.pool.DruidDataSource; 3 import com.alibaba.druid.pool.DruidDataSource;
4 import org.springframework.beans.factory.annotation.Value; 4 import org.springframework.beans.factory.annotation.Value;
1 -package com.ruoyi.framework.datasource; 1 +package com.zhonglai.luhui.datasource.config;
2 2
3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
4 4
1 -package com.ruoyi.framework.datasource; 1 +package com.zhonglai.luhui.datasource.config;
2 2
3 import org.slf4j.Logger; 3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory; 4 import org.slf4j.LoggerFactory;
1 -package com.ruoyi.framework.config; 1 +package com.zhonglai.luhui.datasource.config;
2 2
3 import com.ruoyi.common.utils.StringUtils; 3 import com.ruoyi.common.utils.StringUtils;
4 import org.apache.ibatis.io.VFS; 4 import org.apache.ibatis.io.VFS;
  1 +package com.zhonglai.luhui.datasource.enums;
  2 +
  3 +import java.lang.annotation.*;
  4 +
  5 +/**
  6 + * 自定义多数据源切换注解
  7 + *
  8 + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
  9 + *
  10 + * @author ruoyi
  11 + */
  12 +@Target({ ElementType.METHOD, ElementType.TYPE })
  13 +@Retention(RetentionPolicy.RUNTIME)
  14 +@Documented
  15 +@Inherited
  16 +public @interface DataSource
  17 +{
  18 + /**
  19 + * 切换数据源名称
  20 + */
  21 + public DataSourceType value() default DataSourceType.MASTER;
  22 +}
  1 +package com.zhonglai.luhui.datasource.enums;
  2 +
  3 +/**
  4 + * 数据源
  5 + *
  6 + * @author ruoyi
  7 + */
  8 +public enum DataSourceType
  9 +{
  10 + /**
  11 + * 主库
  12 + */
  13 + MASTER,
  14 +
  15 + /**
  16 + * 从库
  17 + */
  18 + SLAVE
  19 +}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-common-firewall</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <!-- SpringBoot Web容器 -->
  23 + <dependency>
  24 + <groupId>org.springframework.boot</groupId>
  25 + <artifactId>spring-boot-starter-web</artifactId>
  26 + </dependency>
  27 +
  28 + <!-- 验证码 -->
  29 + <dependency>
  30 + <groupId>com.github.penggle</groupId>
  31 + <artifactId>kaptcha</artifactId>
  32 + <exclusions>
  33 + <exclusion>
  34 + <artifactId>javax.servlet-api</artifactId>
  35 + <groupId>javax.servlet</groupId>
  36 + </exclusion>
  37 + </exclusions>
  38 + </dependency>
  39 +
  40 + <dependency>
  41 + <groupId>com.zhonglai.luhui</groupId>
  42 + <artifactId>ruoyi-common-redis</artifactId>
  43 + </dependency>
  44 +
  45 + <!-- SpringBoot 拦截器 -->
  46 + <dependency>
  47 + <groupId>org.springframework.boot</groupId>
  48 + <artifactId>spring-boot-starter-aop</artifactId>
  49 + </dependency>
  50 +
  51 + </dependencies>
  52 +</project>
1 -package com.ruoyi.framework.aspectj; 1 +package com.zhonglai.luhui.firewall.aspectj;
2 2
3 import com.ruoyi.common.annotation.RateLimiter; 3 import com.ruoyi.common.annotation.RateLimiter;
4 import com.ruoyi.common.enums.LimitType; 4 import com.ruoyi.common.enums.LimitType;
1 -package com.ruoyi.framework.config; 1 +package com.zhonglai.luhui.firewall.config;
2 2
3 import com.google.code.kaptcha.impl.DefaultKaptcha; 3 import com.google.code.kaptcha.impl.DefaultKaptcha;
4 import com.google.code.kaptcha.util.Config; 4 import com.google.code.kaptcha.util.Config;
1 -package com.ruoyi.framework.config; 1 +package com.zhonglai.luhui.firewall.config;
2 2
3 import com.ruoyi.common.filter.RepeatableFilter; 3 import com.ruoyi.common.filter.RepeatableFilter;
4 import com.ruoyi.common.filter.XssFilter; 4 import com.ruoyi.common.filter.XssFilter;
1 -package com.ruoyi.framework.config; 1 +package com.zhonglai.luhui.firewall.config;
2 2
3 import com.google.code.kaptcha.text.impl.DefaultTextCreator; 3 import com.google.code.kaptcha.text.impl.DefaultTextCreator;
4 4
  1 +package com.zhonglai.luhui.firewall.config;
  2 +
  3 +import com.zhonglai.luhui.firewall.interceptor.RepeatSubmitInterceptor;
  4 +import org.springframework.beans.factory.annotation.Autowired;
  5 +import org.springframework.context.annotation.Configuration;
  6 +import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  7 +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8 +
  9 +@Configuration
  10 +public class RepeatSubmitConfig implements WebMvcConfigurer {
  11 + @Autowired
  12 + private RepeatSubmitInterceptor repeatSubmitInterceptor;
  13 +
  14 + /**
  15 + * 自定义拦截规则
  16 + */
  17 + @Override
  18 + public void addInterceptors(InterceptorRegistry registry)
  19 + {
  20 + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
  21 + }
  22 +}
1 -package com.ruoyi.framework.interceptor; 1 +package com.zhonglai.luhui.firewall.interceptor;
2 2
3 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
4 import com.ruoyi.common.annotation.RepeatSubmit; 4 import com.ruoyi.common.annotation.RepeatSubmit;
1 -package com.ruoyi.framework.interceptor.impl; 1 +package com.zhonglai.luhui.firewall.interceptor.impl;
2 2
3 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
4 import com.ruoyi.common.annotation.RepeatSubmit; 4 import com.ruoyi.common.annotation.RepeatSubmit;
5 import com.ruoyi.common.constant.Constants; 5 import com.ruoyi.common.constant.Constants;
6 -import com.ruoyi.common.core.redis.RedisCache;  
7 import com.ruoyi.common.filter.RepeatedlyRequestWrapper; 6 import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
8 import com.ruoyi.common.utils.StringUtils; 7 import com.ruoyi.common.utils.StringUtils;
9 import com.ruoyi.common.utils.http.HttpHelper; 8 import com.ruoyi.common.utils.http.HttpHelper;
10 -import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; 9 +import com.zhonglai.luhui.firewall.interceptor.RepeatSubmitInterceptor;
  10 +import com.zhonglai.luhui.redis.service.RedisCache;
11 import org.springframework.beans.factory.annotation.Autowired; 11 import org.springframework.beans.factory.annotation.Autowired;
12 import org.springframework.beans.factory.annotation.Value; 12 import org.springframework.beans.factory.annotation.Value;
13 import org.springframework.stereotype.Component; 13 import org.springframework.stereotype.Component;
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>lh-common</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + </parent>
  11 +
  12 + <artifactId>lh-common-log</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>8</maven.compiler.source>
  16 + <maven.compiler.target>8</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 +
  20 +</project>
  1 +//package com.zhonglai.luhui.log.aspectj;
  2 +//
  3 +//import com.alibaba.fastjson.JSON;
  4 +//import com.ruoyi.common.annotation.Log;
  5 +//import com.ruoyi.common.core.domain.BaseLoginUser;
  6 +//import com.ruoyi.common.enums.BusinessStatus;
  7 +//import com.ruoyi.common.enums.HttpMethod;
  8 +//import com.ruoyi.common.utils.SecurityUtils;
  9 +//import com.ruoyi.common.utils.ServletUtils;
  10 +//import com.ruoyi.common.utils.StringUtils;
  11 +//import com.ruoyi.common.utils.ip.IpUtils;
  12 +//import com.ruoyi.system.domain.sys.SysOperLog;
  13 +//import org.aspectj.lang.JoinPoint;
  14 +//import org.aspectj.lang.annotation.AfterReturning;
  15 +//import org.aspectj.lang.annotation.AfterThrowing;
  16 +//import org.aspectj.lang.annotation.Aspect;
  17 +//import org.slf4j.Logger;
  18 +//import org.slf4j.LoggerFactory;
  19 +//import org.springframework.stereotype.Component;
  20 +//import org.springframework.validation.BindingResult;
  21 +//import org.springframework.web.multipart.MultipartFile;
  22 +//import org.springframework.web.servlet.HandlerMapping;
  23 +//
  24 +//import javax.servlet.http.HttpServletRequest;
  25 +//import javax.servlet.http.HttpServletResponse;
  26 +//import java.util.Collection;
  27 +//import java.util.Map;
  28 +//
  29 +///**
  30 +// * 操作日志记录处理
  31 +// *
  32 +// * @author ruoyi
  33 +// */
  34 +//@Aspect
  35 +//@Component
  36 +//public class LogAspect
  37 +//{
  38 +// private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
  39 +//
  40 +// /**
  41 +// * 处理完请求后执行
  42 +// *
  43 +// * @param joinPoint 切点
  44 +// */
  45 +// @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
  46 +// public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
  47 +// {
  48 +// handleLog(joinPoint, controllerLog, null, jsonResult);
  49 +// }
  50 +//
  51 +// /**
  52 +// * 拦截异常操作
  53 +// *
  54 +// * @param joinPoint 切点
  55 +// * @param e 异常
  56 +// */
  57 +// @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
  58 +// public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
  59 +// {
  60 +// handleLog(joinPoint, controllerLog, e, null);
  61 +// }
  62 +//
  63 +// protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
  64 +// {
  65 +// try
  66 +// {
  67 +// // 获取当前的用户
  68 +// BaseLoginUser loginUser = SecurityUtils.getLoginUser();
  69 +//
  70 +// // *========数据库日志=========*//
  71 +// SysOperLog operLog = new SysOperLog();
  72 +// operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
  73 +// // 请求的地址
  74 +// String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
  75 +// operLog.setOperIp(ip);
  76 +// operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
  77 +// if (loginUser != null)
  78 +// {
  79 +// operLog.setOperName(loginUser.getUsername());
  80 +// }
  81 +//
  82 +// if (e != null)
  83 +// {
  84 +// operLog.setStatus(BusinessStatus.FAIL.ordinal());
  85 +// operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
  86 +// }
  87 +// // 设置方法名称
  88 +// String className = joinPoint.getTarget().getClass().getName();
  89 +// String methodName = joinPoint.getSignature().getName();
  90 +// operLog.setMethod(className + "." + methodName + "()");
  91 +// // 设置请求方式
  92 +// operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
  93 +// // 处理设置注解上的参数
  94 +// getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
  95 +// // 保存数据库
  96 +// AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
  97 +// }
  98 +// catch (Exception exp)
  99 +// {
  100 +// // 记录本地异常日志
  101 +// log.error("==前置通知异常==");
  102 +// log.error("异常信息:{}", exp.getMessage());
  103 +// exp.printStackTrace();
  104 +// }
  105 +// }
  106 +//
  107 +// /**
  108 +// * 获取注解中对方法的描述信息 用于Controller层注解
  109 +// *
  110 +// * @param log 日志
  111 +// * @param operLog 操作日志
  112 +// * @throws Exception
  113 +// */
  114 +// public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
  115 +// {
  116 +// // 设置action动作
  117 +// operLog.setBusinessType(log.businessType().ordinal());
  118 +// // 设置标题
  119 +// operLog.setTitle(log.title());
  120 +// // 设置操作人类别
  121 +// operLog.setOperatorType(log.operatorType().ordinal());
  122 +// // 是否需要保存request,参数和值
  123 +// if (log.isSaveRequestData())
  124 +// {
  125 +// // 获取参数的信息,传入到数据库中。
  126 +// setRequestValue(joinPoint, operLog);
  127 +// }
  128 +// // 是否需要保存response,参数和值
  129 +// if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
  130 +// {
  131 +// operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
  132 +// }
  133 +// }
  134 +//
  135 +// /**
  136 +// * 获取请求的参数,放到log中
  137 +// *
  138 +// * @param operLog 操作日志
  139 +// * @throws Exception 异常
  140 +// */
  141 +// private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
  142 +// {
  143 +// String requestMethod = operLog.getRequestMethod();
  144 +// if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
  145 +// {
  146 +// String params = argsArrayToString(joinPoint.getArgs());
  147 +// operLog.setOperParam(StringUtils.substring(params, 0, 2000));
  148 +// }
  149 +// else
  150 +// {
  151 +// Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
  152 +// operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
  153 +// }
  154 +// }
  155 +//
  156 +// /**
  157 +// * 参数拼装
  158 +// */
  159 +// private String argsArrayToString(Object[] paramsArray)
  160 +// {
  161 +// String params = "";
  162 +// if (paramsArray != null && paramsArray.length > 0)
  163 +// {
  164 +// for (Object o : paramsArray)
  165 +// {
  166 +// if (StringUtils.isNotNull(o) && !isFilterObject(o))
  167 +// {
  168 +// try
  169 +// {
  170 +// Object jsonObj = JSON.toJSON(o);
  171 +// params += jsonObj.toString() + " ";
  172 +// }
  173 +// catch (Exception e)
  174 +// {
  175 +// }
  176 +// }
  177 +// }
  178 +// }
  179 +// return params.trim();
  180 +// }
  181 +//
  182 +// /**
  183 +// * 判断是否需要过滤的对象。
  184 +// *
  185 +// * @param o 对象信息。
  186 +// * @return 如果是需要过滤的对象,则返回true;否则返回false。
  187 +// */
  188 +// @SuppressWarnings("rawtypes")
  189 +// public boolean isFilterObject(final Object o)
  190 +// {
  191 +// Class<?> clazz = o.getClass();
  192 +// if (clazz.isArray())
  193 +// {
  194 +// return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
  195 +// }
  196 +// else if (Collection.class.isAssignableFrom(clazz))
  197 +// {
  198 +// Collection collection = (Collection) o;
  199 +// for (Object value : collection)
  200 +// {
  201 +// return value instanceof MultipartFile;
  202 +// }
  203 +// }
  204 +// else if (Map.class.isAssignableFrom(clazz))
  205 +// {
  206 +// Map map = (Map) o;
  207 +// for (Object value : map.entrySet())
  208 +// {
  209 +// Map.Entry entry = (Map.Entry) value;
  210 +// return entry.getValue() instanceof MultipartFile;
  211 +// }
  212 +// }
  213 +// return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
  214 +// || o instanceof BindingResult;
  215 +// }
  216 +//}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-common-swagger</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <!-- 文档 -->
  23 + <dependency >
  24 + <groupId>io.springfox</groupId>
  25 + <artifactId>springfox-swagger2</artifactId>
  26 + <version>${swagger.version}</version>
  27 + <exclusions>
  28 + <exclusion>
  29 + <groupId>io.swagger</groupId>
  30 + <artifactId>swagger-models</artifactId>
  31 + </exclusion>
  32 + <exclusion>
  33 + <groupId>com.google.guava</groupId>
  34 + <artifactId>guava</artifactId>
  35 + </exclusion>
  36 + </exclusions>
  37 + </dependency>
  38 + <!--https://mvnrepository.com/artifact/io.swagger/swagger-models-->
  39 + <dependency>
  40 + <groupId>io.swagger</groupId>
  41 + <artifactId>swagger-models</artifactId>
  42 + <version>${swagger-models.version}</version>
  43 + </dependency>
  44 + <dependency>
  45 + <groupId>io.springfox</groupId>
  46 + <artifactId>springfox-swagger-ui</artifactId>
  47 + <version>${swagger.version}</version>
  48 + </dependency>
  49 + <!--&lt;!&ndash; https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui &ndash;&gt;-->
  50 + <dependency>
  51 + <groupId>com.github.xiaoymin</groupId>
  52 + <artifactId>swagger-bootstrap-ui</artifactId>
  53 + <version>${swagger-ui.version}</version>
  54 + </dependency>
  55 +
  56 + </dependencies>
  57 +
  58 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <artifactId>lh-common</artifactId>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <version>1.0-SNAPSHOT</version>
  9 + </parent>
  10 + <dependencies>
  11 + <!-- 模型-->
  12 + <dependency>
  13 + <groupId>com.zhonglai.luhui</groupId>
  14 + <artifactId>ruoyi-common</artifactId>
  15 + </dependency>
  16 + <dependency>
  17 + <groupId>io.swagger</groupId>
  18 + <artifactId>swagger-annotations</artifactId>
  19 + <version>1.6.2</version>
  20 + <scope>compile</scope>
  21 + </dependency>
  22 + </dependencies>
  23 + <modelVersion>4.0.0</modelVersion>
  24 +
  25 + <artifactId>lh-domain</artifactId>
  26 +
  27 +
  28 +</project>
1 package com.ruoyi.system.domain.entity; 1 package com.ruoyi.system.domain.entity;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
6 import org.apache.commons.lang3.builder.ToStringStyle; 6 import org.apache.commons.lang3.builder.ToStringStyle;
7 7
1 package com.ruoyi.system.domain.entity; 1 package com.ruoyi.system.domain.entity;
2 2
3 -import com.ruoyi.system.domain.tool.Excel;  
4 -import com.ruoyi.system.domain.tool.Excel.ColumnType;  
5 -import com.ruoyi.system.domain.tool.BaseEntity; 3 +import com.ruoyi.common.annotation.Excel;
  4 +import com.ruoyi.common.tool.BaseEntity;
6 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
7 import org.apache.commons.lang3.builder.ToStringStyle; 6 import org.apache.commons.lang3.builder.ToStringStyle;
8 7
@@ -19,11 +18,11 @@ public class SysDictData extends BaseEntity @@ -19,11 +18,11 @@ public class SysDictData extends BaseEntity
19 private static final long serialVersionUID = 1L; 18 private static final long serialVersionUID = 1L;
20 19
21 /** 字典编码 */ 20 /** 字典编码 */
22 - @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) 21 + @Excel(name = "字典编码", cellType = Excel.ColumnType.NUMERIC)
23 private Long dictCode; 22 private Long dictCode;
24 23
25 /** 字典排序 */ 24 /** 字典排序 */
26 - @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) 25 + @Excel(name = "字典排序", cellType = Excel.ColumnType.NUMERIC)
27 private Long dictSort; 26 private Long dictSort;
28 27
29 /** 字典标签 */ 28 /** 字典标签 */
  1 +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(); }}
1 package com.ruoyi.system.domain.entity; 1 package com.ruoyi.system.domain.entity;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
6 import org.apache.commons.lang3.builder.ToStringStyle; 6 import org.apache.commons.lang3.builder.ToStringStyle;
7 7
1 package com.ruoyi.system.domain.entity; 1 package com.ruoyi.system.domain.entity;
2 2
3 -import com.ruoyi.system.domain.tool.Excel;  
4 -import com.ruoyi.system.domain.tool.Excel.ColumnType;  
5 -import com.ruoyi.system.domain.tool.BaseEntity; 3 +import com.ruoyi.common.annotation.Excel;
  4 +import com.ruoyi.common.tool.BaseEntity;
6 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
7 import org.apache.commons.lang3.builder.ToStringStyle; 6 import org.apache.commons.lang3.builder.ToStringStyle;
8 7
@@ -19,7 +18,7 @@ public class SysRole extends BaseEntity @@ -19,7 +18,7 @@ public class SysRole extends BaseEntity
19 private static final long serialVersionUID = 1L; 18 private static final long serialVersionUID = 1L;
20 19
21 /** 角色ID */ 20 /** 角色ID */
22 - @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) 21 + @Excel(name = "角色序号", cellType = Excel.ColumnType.NUMERIC)
23 private Long roleId; 22 private Long roleId;
24 23
25 /** 角色名称 */ 24 /** 角色名称 */
@@ -2,12 +2,12 @@ package com.ruoyi.system.domain.entity; @@ -2,12 +2,12 @@ package com.ruoyi.system.domain.entity;
2 2
3 import com.fasterxml.jackson.annotation.JsonIgnore; 3 import com.fasterxml.jackson.annotation.JsonIgnore;
4 import com.fasterxml.jackson.annotation.JsonProperty; 4 import com.fasterxml.jackson.annotation.JsonProperty;
5 -import com.ruoyi.system.domain.tool.Excel;  
6 -import com.ruoyi.system.domain.tool.Excel.ColumnType;  
7 -import com.ruoyi.system.domain.tool.Excel.Type;  
8 -import com.ruoyi.system.domain.tool.BaseEntity;  
9 -import com.ruoyi.system.domain.tool.Excels;  
10 -import com.ruoyi.system.domain.tool.Xss; 5 +import com.ruoyi.common.annotation.Excel;
  6 +import com.ruoyi.common.annotation.Excel.ColumnType;
  7 +import com.ruoyi.common.annotation.Excel.Type;
  8 +import com.ruoyi.common.annotation.Excels;
  9 +import com.ruoyi.common.utils.xss.Xss;
  10 +import com.ruoyi.common.tool.BaseEntity;
11 import org.apache.commons.lang3.builder.ToStringBuilder; 11 import org.apache.commons.lang3.builder.ToStringBuilder;
12 import org.apache.commons.lang3.builder.ToStringStyle; 12 import org.apache.commons.lang3.builder.ToStringStyle;
13 13
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity; 3 +import com.ruoyi.common.tool.BaseEntity;
4 import org.apache.commons.lang3.builder.ToStringBuilder; 4 import org.apache.commons.lang3.builder.ToStringBuilder;
5 import org.apache.commons.lang3.builder.ToStringStyle; 5 import org.apache.commons.lang3.builder.ToStringStyle;
6 6
7 -import java.io.Serializable;  
8 -  
9 /** 7 /**
10 * 设备告警对象 iot_alert 8 * 设备告警对象 iot_alert
11 * 9 *
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity; 3 +import com.ruoyi.common.tool.BaseEntity;
4 import org.apache.commons.lang3.builder.ToStringBuilder; 4 import org.apache.commons.lang3.builder.ToStringBuilder;
5 import org.apache.commons.lang3.builder.ToStringStyle; 5 import org.apache.commons.lang3.builder.ToStringStyle;
6 6
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.PublicSQLConfig;  
4 -import org.apache.commons.lang3.builder.ToStringBuilder;  
5 -import org.apache.commons.lang3.builder.ToStringStyle; 3 +import com.ruoyi.common.annotation.PublicSQLConfig;
6 import io.swagger.annotations.ApiModel; 4 import io.swagger.annotations.ApiModel;
7 import io.swagger.annotations.ApiModelProperty; 5 import io.swagger.annotations.ApiModelProperty;
8 6
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig;  
5 -import org.apache.commons.lang3.builder.ToStringBuilder;  
6 -import org.apache.commons.lang3.builder.ToStringStyle; 3 +import com.ruoyi.common.annotation.PublicSQLConfig;
7 import io.swagger.annotations.ApiModel; 4 import io.swagger.annotations.ApiModel;
8 import io.swagger.annotations.ApiModelProperty; 5 import io.swagger.annotations.ApiModelProperty;
  6 +import org.apache.commons.lang3.builder.ToStringBuilder;
  7 +import org.apache.commons.lang3.builder.ToStringStyle;
9 8
10 import java.io.Serializable; 9 import java.io.Serializable;
11 10
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import org.apache.commons.lang3.builder.ToStringBuilder; 4 import org.apache.commons.lang3.builder.ToStringBuilder;
6 import org.apache.commons.lang3.builder.ToStringStyle; 5 import org.apache.commons.lang3.builder.ToStringStyle;
7 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import io.swagger.annotations.ApiModel; 5 import io.swagger.annotations.ApiModel;
6 import io.swagger.annotations.ApiModelProperty; 6 import io.swagger.annotations.ApiModelProperty;
7 import org.apache.commons.lang3.builder.ToStringBuilder; 7 import org.apache.commons.lang3.builder.ToStringBuilder;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import org.apache.commons.lang3.builder.ToStringBuilder; 4 import org.apache.commons.lang3.builder.ToStringBuilder;
6 import org.apache.commons.lang3.builder.ToStringStyle; 5 import org.apache.commons.lang3.builder.ToStringStyle;
7 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.system.domain.user.UserTerminalGroupRelation;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
4 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
5 import org.apache.commons.lang3.builder.ToStringStyle; 6 import org.apache.commons.lang3.builder.ToStringStyle;
6 import io.swagger.annotations.ApiModel; 7 import io.swagger.annotations.ApiModel;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import org.apache.commons.lang3.builder.ToStringBuilder; 4 import org.apache.commons.lang3.builder.ToStringBuilder;
6 import org.apache.commons.lang3.builder.ToStringStyle; 5 import org.apache.commons.lang3.builder.ToStringStyle;
7 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.iot;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
5 import io.swagger.annotations.ApiModel; 5 import io.swagger.annotations.ApiModel;
6 import io.swagger.annotations.ApiModelProperty; 6 import io.swagger.annotations.ApiModelProperty;
7 import org.apache.commons.lang3.builder.ToStringBuilder; 7 import org.apache.commons.lang3.builder.ToStringBuilder;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.Excel;  
5 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
  5 +import com.ruoyi.common.annotation.Excel;
6 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
7 import io.swagger.annotations.ApiModelProperty; 7 import io.swagger.annotations.ApiModelProperty;
8 import org.apache.commons.lang3.builder.ToStringBuilder; 8 import org.apache.commons.lang3.builder.ToStringBuilder;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
4 -import com.ruoyi.system.domain.tool.BaseEntity;  
5 -import com.ruoyi.system.domain.tool.Excel;  
6 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 4 +import com.ruoyi.common.annotation.Excel;
  5 +import com.ruoyi.common.tool.BaseEntity;
  6 +import com.ruoyi.common.annotation.PublicSQLConfig;
7 7
8 import java.util.Date; 8 import java.util.Date;
9 9
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig;  
5 -import com.ruoyi.system.domain.tool.Xss; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.PublicSQLConfig;
  5 +import com.ruoyi.common.utils.xss.Xss;
6 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
7 import io.swagger.annotations.ApiModelProperty; 7 import io.swagger.annotations.ApiModelProperty;
8 import org.apache.commons.lang3.builder.ToStringBuilder; 8 import org.apache.commons.lang3.builder.ToStringBuilder;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import com.fasterxml.jackson.annotation.JsonFormat; 3 import com.fasterxml.jackson.annotation.JsonFormat;
4 -import com.ruoyi.system.domain.tool.BaseEntity;  
5 -import com.ruoyi.system.domain.tool.Excel;  
6 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 4 +import com.ruoyi.common.tool.BaseEntity;
  5 +import com.ruoyi.common.annotation.Excel;
  6 +import com.ruoyi.common.annotation.PublicSQLConfig;
7 import io.swagger.annotations.ApiModel; 7 import io.swagger.annotations.ApiModel;
8 import io.swagger.annotations.ApiModelProperty; 8 import io.swagger.annotations.ApiModelProperty;
9 9
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.Excel;  
5 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
  4 +import com.ruoyi.common.annotation.Excel;
  5 +import com.ruoyi.common.annotation.PublicSQLConfig;
6 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
7 import io.swagger.annotations.ApiModelProperty; 7 import io.swagger.annotations.ApiModelProperty;
8 import org.apache.commons.lang3.builder.ToStringBuilder; 8 import org.apache.commons.lang3.builder.ToStringBuilder;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.sys;
2 2
3 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
4 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.user;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig;  
5 import io.swagger.annotations.ApiModel; 3 import io.swagger.annotations.ApiModel;
6 import io.swagger.annotations.ApiModelProperty; 4 import io.swagger.annotations.ApiModelProperty;
7 import org.apache.commons.lang3.builder.ToStringBuilder; 5 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -14,10 +12,8 @@ import org.apache.commons.lang3.builder.ToStringStyle; @@ -14,10 +12,8 @@ import org.apache.commons.lang3.builder.ToStringStyle;
14 * @date 2022-11-22 12 * @date 2022-11-22
15 */ 13 */
16 @ApiModel("终端分组") 14 @ApiModel("终端分组")
17 -public class UserTerminalGroup extends BaseEntity 15 +public class UserTerminalGroup
18 { 16 {
19 - @PublicSQLConfig(isSelect=false)  
20 - private static final long serialVersionUID = 1L;  
21 17
22 /** 创建时间 */ 18 /** 创建时间 */
23 @ApiModelProperty("创建时间") 19 @ApiModelProperty("创建时间")
1 -package com.ruoyi.system.domain; 1 +package com.ruoyi.system.domain.user;
2 2
3 -import com.ruoyi.system.domain.tool.BaseEntity;  
4 -import com.ruoyi.system.domain.tool.PublicSQLConfig; 3 +import com.ruoyi.common.tool.BaseEntity;
5 import io.swagger.annotations.ApiModel; 4 import io.swagger.annotations.ApiModel;
6 import io.swagger.annotations.ApiModelProperty; 5 import io.swagger.annotations.ApiModelProperty;
7 import org.apache.commons.lang3.builder.ToStringBuilder; 6 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -16,8 +15,6 @@ import org.apache.commons.lang3.builder.ToStringStyle; @@ -16,8 +15,6 @@ import org.apache.commons.lang3.builder.ToStringStyle;
16 @ApiModel("终端分组关系") 15 @ApiModel("终端分组关系")
17 public class UserTerminalGroupRelation extends BaseEntity 16 public class UserTerminalGroupRelation extends BaseEntity
18 { 17 {
19 - @PublicSQLConfig(isSelect=false)  
20 - private static final long serialVersionUID = 1L;  
21 18
22 /** 创建时间 */ 19 /** 创建时间 */
23 @ApiModelProperty("创建时间") 20 @ApiModelProperty("创建时间")
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + </parent>
  11 +
  12 + <artifactId>lh-common</artifactId>
  13 +
  14 + <properties>
  15 + <maven.compiler.source>8</maven.compiler.source>
  16 + <maven.compiler.target>8</maven.compiler.target>
  17 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18 + </properties>
  19 +
  20 + <modules>
  21 + <module>lh-domain</module>
  22 + <module>ruoyi-common</module>
  23 + <module>ruoyi-system</module>
  24 + <module>lh-public-dao</module>
  25 + <module>ruoyi-common-core</module>
  26 + <module>ruoyi-common-redis</module>
  27 + <module>ruoyi-common-security</module>
  28 + <module>ruoyi-auth</module>
  29 + <module>lh-common-datasource</module>
  30 + <module>lh-common-log</module>
  31 + <module>lh-quartz</module>
  32 + <module>ruoyi-generator</module>
  33 + </modules>
  34 +
  35 + <packaging>pom</packaging>
  36 + <description>
  37 + lh-common通用模块
  38 + </description>
  39 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-jar-action</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <dependency>
  23 + <groupId>com.zhonglai.luhui</groupId>
  24 + <artifactId>lh-jar-sys-service</artifactId>
  25 + </dependency>
  26 + </dependencies>
  27 +</project>
  1 +package com.zhonglai.luhui;
  2 +
  3 +public class Main {
  4 + public static void main(String[] args) {
  5 + System.out.println("Hello world!");
  6 + }
  7 +}
  1 +package com.zhonglai.luhui.action;
  2 +
  3 +import com.github.pagehelper.PageHelper;
  4 +import com.github.pagehelper.PageInfo;
  5 +import com.ruoyi.common.constant.HttpStatus;
  6 +import com.ruoyi.common.core.domain.AjaxResult;
  7 +import com.ruoyi.common.core.page.PageDomain;
  8 +import com.ruoyi.common.core.page.TableDataInfo;
  9 +import com.ruoyi.common.core.page.TableSupport;
  10 +import com.ruoyi.common.utils.DateUtils;
  11 +import com.ruoyi.common.utils.PageUtils;
  12 +import com.ruoyi.common.utils.StringUtils;
  13 +import com.ruoyi.common.utils.sql.SqlUtil;
  14 +import org.slf4j.Logger;
  15 +import org.slf4j.LoggerFactory;
  16 +import org.springframework.web.bind.WebDataBinder;
  17 +import org.springframework.web.bind.annotation.InitBinder;
  18 +
  19 +import java.beans.PropertyEditorSupport;
  20 +import java.util.Date;
  21 +import java.util.List;
  22 +
  23 +/**
  24 + * web层通用数据处理
  25 + *
  26 + * @author ruoyi
  27 + */
  28 +public class BaseController
  29 +{
  30 + protected final Logger logger = LoggerFactory.getLogger(this.getClass());
  31 +
  32 + /**
  33 + * 将前台传递过来的日期格式的字符串,自动转化为Date类型
  34 + */
  35 + @InitBinder
  36 + public void initBinder(WebDataBinder binder)
  37 + {
  38 + // Date 类型转换
  39 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
  40 + {
  41 + @Override
  42 + public void setAsText(String text)
  43 + {
  44 + setValue(DateUtils.parseDate(text));
  45 + }
  46 + });
  47 + }
  48 +
  49 + /**
  50 + * 设置请求分页数据
  51 + */
  52 + protected void startPage()
  53 + {
  54 + PageUtils.startPage();
  55 + }
  56 +
  57 + /**
  58 + * 设置请求排序数据
  59 + */
  60 + protected void startOrderBy()
  61 + {
  62 + PageDomain pageDomain = TableSupport.buildPageRequest();
  63 + if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
  64 + {
  65 + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
  66 + PageHelper.orderBy(orderBy);
  67 + }
  68 + }
  69 +
  70 + /**
  71 + * 清理分页的线程变量
  72 + */
  73 + protected void clearPage()
  74 + {
  75 + PageUtils.clearPage();
  76 + }
  77 +
  78 + /**
  79 + * 响应请求分页数据
  80 + */
  81 + @SuppressWarnings({ "rawtypes", "unchecked" })
  82 + protected TableDataInfo getDataTable(List<?> list)
  83 + {
  84 + TableDataInfo rspData = new TableDataInfo();
  85 + rspData.setCode(HttpStatus.SUCCESS);
  86 + rspData.setMsg("查询成功");
  87 + rspData.setRows(list);
  88 + rspData.setTotal(new PageInfo(list).getTotal());
  89 + return rspData;
  90 + }
  91 +
  92 + /**
  93 + * 返回成功
  94 + */
  95 + public AjaxResult success()
  96 + {
  97 + return AjaxResult.success();
  98 + }
  99 +
  100 + /**
  101 + * 返回失败消息
  102 + */
  103 + public AjaxResult error()
  104 + {
  105 + return AjaxResult.error();
  106 + }
  107 +
  108 + /**
  109 + * 返回成功消息
  110 + */
  111 + public AjaxResult success(String message)
  112 + {
  113 + return AjaxResult.success(message);
  114 + }
  115 +
  116 + /**
  117 + * 返回失败消息
  118 + */
  119 + public AjaxResult error(String message)
  120 + {
  121 + return AjaxResult.error(message);
  122 + }
  123 +
  124 + /**
  125 + * 响应返回结果
  126 + *
  127 + * @param rows 影响行数
  128 + * @return 操作结果
  129 + */
  130 + protected AjaxResult toAjax(int rows)
  131 + {
  132 + return rows > 0 ? AjaxResult.success() : AjaxResult.error();
  133 + }
  134 +
  135 + /**
  136 + * 响应返回结果
  137 + *
  138 + * @param result 结果
  139 + * @return 操作结果
  140 + */
  141 + protected AjaxResult toAjax(boolean result)
  142 + {
  143 + return result ? success() : error();
  144 + }
  145 +
  146 + /**
  147 + * 页面跳转
  148 + */
  149 + public String redirect(String url)
  150 + {
  151 + return StringUtils.format("redirect:{}", url);
  152 + }
  153 +
  154 +}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-jar-chatgpt</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <dependency>
  23 + <groupId>org.springframework.boot</groupId>
  24 + <artifactId>spring-boot-starter-web</artifactId>
  25 + </dependency>
  26 +
  27 + <dependency>
  28 + <groupId>org.projectlombok</groupId>
  29 + <artifactId>lombok</artifactId>
  30 + <optional>true</optional>
  31 + </dependency>
  32 + <dependency>
  33 + <groupId>org.springframework.boot</groupId>
  34 + <artifactId>spring-boot-starter-test</artifactId>
  35 + <scope>test</scope>
  36 + </dependency>
  37 + <dependency>
  38 + <groupId>com.unfbx</groupId>
  39 + <artifactId>chatgpt-java</artifactId>
  40 + </dependency>
  41 + <dependency>
  42 + <groupId>org.springframework.boot</groupId>
  43 + <artifactId>spring-boot-starter-websocket</artifactId>
  44 + </dependency>
  45 + <dependency>
  46 + <groupId>com.zhonglai.luhui</groupId>
  47 + <artifactId>lh-common-swagger</artifactId>
  48 + </dependency>
  49 + </dependencies>
  50 +</project>
  1 +package com.zhonglai.luhui.chatgpt.config;
  2 +
  3 +import cn.hutool.cache.CacheUtil;
  4 +import cn.hutool.cache.impl.TimedCache;
  5 +import cn.hutool.core.date.DateUnit;
  6 +
  7 +/**
  8 + * 描述:
  9 + *
  10 + * @author https:www.unfbx.com
  11 + * @date 2023-03-10
  12 + */
  13 +public class LocalCache {
  14 + /**
  15 + * 缓存时长
  16 + */
  17 + public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
  18 + /**
  19 + * 清理间隔
  20 + */
  21 + private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
  22 + /**
  23 + * 缓存对象
  24 + */
  25 + public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
  26 +
  27 + static {
  28 + //启动定时任务
  29 + CACHE.schedulePrune(CLEAN_TIMEOUT);
  30 + }
  31 +}
  1 +package com.zhonglai.luhui.chatgpt.config;
  2 +
  3 +import com.unfbx.chatgpt.OpenAiStreamClient;
  4 +import com.unfbx.chatgpt.function.KeyRandomStrategy;
  5 +import com.unfbx.chatgpt.interceptor.OpenAILogger;
  6 +import okhttp3.OkHttpClient;
  7 +import okhttp3.logging.HttpLoggingInterceptor;
  8 +import org.springframework.beans.factory.annotation.Value;
  9 +import org.springframework.context.annotation.Bean;
  10 +import org.springframework.context.annotation.Configuration;
  11 +
  12 +import java.net.InetSocketAddress;
  13 +import java.net.Proxy;
  14 +import java.util.List;
  15 +import java.util.concurrent.TimeUnit;
  16 +
  17 +
  18 +@Configuration
  19 +public class OpenAiStreamClientConfig {
  20 + @Value("${chatgpt.token}")
  21 + private List<String> apiKey;
  22 + @Value("${chatgpt.apiHost}")
  23 + private String apiHost;
  24 +
  25 + @Value("${chatgpt.proxy.isProxy:false}")
  26 + private Boolean isProxy;
  27 + @Value("${chatgpt.proxy.host:127.0.0.1}")
  28 + private String host;
  29 + @Value("${chatgpt.proxy.port:7890}")
  30 + private Integer port;
  31 + @Bean
  32 + public OpenAiStreamClient openAiStreamClient() {
  33 + HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
  34 + //!!!!!!测试或者发布到服务器千万不要配置Level == BODY!!!!
  35 + //!!!!!!测试或者发布到服务器千万不要配置Level == BODY!!!!
  36 + httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
  37 + OkHttpClient.Builder builder = new OkHttpClient
  38 + .Builder();
  39 + if(isProxy)
  40 + {
  41 + //本地开发需要配置代理地址
  42 + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
  43 + builder.proxy(proxy);
  44 + }
  45 +
  46 + OkHttpClient okHttpClient = builder.addInterceptor(httpLoggingInterceptor)
  47 + .connectTimeout(30, TimeUnit.SECONDS)
  48 + .writeTimeout(600, TimeUnit.SECONDS)
  49 + .readTimeout(600, TimeUnit.SECONDS)
  50 + .build();
  51 + return OpenAiStreamClient
  52 + .builder()
  53 + .apiHost(apiHost)
  54 + .apiKey(apiKey)
  55 + //自定义key使用策略 默认随机策略
  56 + .keyStrategy(new KeyRandomStrategy())
  57 + .okHttpClient(okHttpClient)
  58 + .build();
  59 + }
  60 +}
  1 +package com.zhonglai.luhui.chatgpt.config;
  2 +
  3 +import org.springframework.context.annotation.Bean;
  4 +import org.springframework.context.annotation.Configuration;
  5 +import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  6 +
  7 +
  8 +/**
  9 + * 描述:
  10 + *
  11 + * @author https:www.unfbx.com
  12 + * @since 2023-03-23
  13 + */
  14 +@Configuration
  15 +public class WebSocketConfig {
  16 + /**
  17 + * 这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket,如果你使用外置的tomcat就不需要该配置文件
  18 + */
  19 + @Bean
  20 + public ServerEndpointExporter serverEndpointExporter() {
  21 + return new ServerEndpointExporter();
  22 + }
  23 +}
  1 +package com.zhonglai.luhui.chatgpt.controller;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import com.unfbx.chatgpt.entity.chat.ChatCompletion;
  5 +import com.unfbx.chatgpt.exception.BaseException;
  6 +import com.unfbx.chatgpt.exception.CommonError;
  7 +import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
  8 +import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
  9 +import com.zhonglai.luhui.chatgpt.service.SseService;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.stereotype.Controller;
  12 +import org.springframework.web.bind.annotation.*;
  13 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  14 +
  15 +import javax.servlet.http.HttpServletResponse;
  16 +import java.util.Map;
  17 +
  18 +/**
  19 + * 描述:
  20 + *
  21 + * @author https:www.unfbx.com
  22 + * @date 2023-03-01
  23 + */
  24 +@Controller
  25 +@Slf4j
  26 +public class ChatController {
  27 +
  28 + private final SseService sseService;
  29 +
  30 + public ChatController(SseService sseService) {
  31 + this.sseService = sseService;
  32 + }
  33 +
  34 + /**
  35 + * 创建sse连接
  36 + *
  37 + * @param headers
  38 + * @return
  39 + */
  40 + @CrossOrigin
  41 + @GetMapping("/createSse")
  42 + public SseEmitter createConnect(@RequestHeader Map<String, String> headers) {
  43 + String uid = getUid(headers);
  44 + return sseService.createSse(uid);
  45 + }
  46 +
  47 + /**
  48 + * 聊天接口
  49 + *
  50 + * @param chatRequest
  51 + * @param headers
  52 + */
  53 + @CrossOrigin
  54 + @PostMapping("/chat")
  55 + @ResponseBody
  56 + public ChatResponse sseChat(@RequestBody ChatRequest chatRequest, @RequestHeader Map<String, String> headers, HttpServletResponse response) {
  57 + String uid = getUid(headers);
  58 + return sseService.sseChat(uid, chatRequest, ChatCompletion.Model.GPT_3_5_TURBO_0301,null);
  59 + }
  60 +
  61 + /**
  62 + * 关闭连接
  63 + *
  64 + * @param headers
  65 + */
  66 + @CrossOrigin
  67 + @GetMapping("/closeSse")
  68 + public void closeConnect(@RequestHeader Map<String, String> headers) {
  69 + String uid = getUid(headers);
  70 + sseService.closeSse(uid);
  71 + }
  72 +
  73 + @GetMapping("")
  74 + public String index() {
  75 + return "1.html";
  76 + }
  77 +
  78 + @GetMapping("/websocket")
  79 + public String websocket() {
  80 + return "websocket.html";
  81 + }
  82 +
  83 + /**
  84 + * 获取uid
  85 + *
  86 + * @param headers
  87 + * @return
  88 + */
  89 + private String getUid(Map<String, String> headers) {
  90 + String uid = headers.get("uid");
  91 + if (StrUtil.isBlank(uid)) {
  92 + throw new BaseException(CommonError.SYS_ERROR);
  93 + }
  94 + return uid;
  95 + }
  96 +
  97 +
  98 +}
  1 +package com.zhonglai.luhui.chatgpt.controller.request;
  2 +
  3 +import com.unfbx.chatgpt.entity.chat.ChatCompletion;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 描述:
  12 + *
  13 + * @author https:www.unfbx.com
  14 + * @sine 2023-04-08
  15 + */
  16 +@ApiModel("chatGPT流请求参数")
  17 +@Data
  18 +public class ChatRequest {
  19 + /**
  20 + * 客户端发送的问题参数
  21 + */
  22 + @ApiModelProperty("问题(支持传历史问题,来设置上下文)")
  23 + private List<String> msg;
  24 + @ApiModelProperty("模型")
  25 + private ChatCompletion.Model model;
  26 +}
  1 +package com.zhonglai.luhui.chatgpt.controller.response;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 描述:
  8 + *
  9 + * @author https:www.unfbx.com
  10 + * @sine 2023-04-08
  11 + */
  12 +@Data
  13 +public class ChatResponse {
  14 + /**
  15 + * 问题消耗tokens
  16 + */
  17 + @JsonProperty("question_tokens")
  18 + private long questionTokens = 0;
  19 +}
  1 +package com.zhonglai.luhui.chatgpt.entity;
  2 +
  3 +import com.unfbx.chatgpt.entity.chat.Message;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +/**
  8 + * 描述:
  9 + *
  10 + * @author https:www.unfbx.com
  11 + * @date 2023-04-10
  12 + */
  13 +@Data
  14 +public class Chat {
  15 +
  16 + private String uid;
  17 +
  18 + private List<Message> message;
  19 +}
  1 +package com.zhonglai.luhui.chatgpt.event;
  2 +
  3 +import org.springframework.http.MediaType;
  4 +import org.springframework.lang.Nullable;
  5 +import org.springframework.util.StringUtils;
  6 +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
  7 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  8 +
  9 +import java.nio.charset.StandardCharsets;
  10 +import java.util.Collections;
  11 +import java.util.LinkedHashSet;
  12 +import java.util.Set;
  13 +
  14 +/**
  15 + * 描述:
  16 + *
  17 + * @author https:www.unfbx.com
  18 + * @date 2023-02-28
  19 + */
  20 +public class MyEvent implements SseEmitter.SseEventBuilder {
  21 + private static final MediaType TEXT_PLAIN = new MediaType("text", "plain", StandardCharsets.UTF_8);
  22 +
  23 + private final Set<ResponseBodyEmitter.DataWithMediaType> dataToSend = new LinkedHashSet<>(4);
  24 +
  25 + @Nullable
  26 + private StringBuilder sb;
  27 +
  28 + @Override
  29 + public SseEmitter.SseEventBuilder id(String id) {
  30 + append("id:").append(id).append('\n');
  31 + return this;
  32 + }
  33 +
  34 + @Override
  35 + public SseEmitter.SseEventBuilder name(String name) {
  36 + append("event:").append(name).append('\n');
  37 + return this;
  38 + }
  39 +
  40 + @Override
  41 + public SseEmitter.SseEventBuilder reconnectTime(long reconnectTimeMillis) {
  42 + append("retry:").append(String.valueOf(reconnectTimeMillis)).append('\n');
  43 + return this;
  44 + }
  45 +
  46 + @Override
  47 + public SseEmitter.SseEventBuilder comment(String comment) {
  48 + append(':').append(comment).append('\n');
  49 + return this;
  50 + }
  51 +
  52 + @Override
  53 + public SseEmitter.SseEventBuilder data(Object object) {
  54 + return data(object, null);
  55 + }
  56 +
  57 + @Override
  58 + public SseEmitter.SseEventBuilder data(Object object, @Nullable MediaType mediaType) {
  59 + saveAppendedText();
  60 + this.dataToSend.add(new ResponseBodyEmitter.DataWithMediaType(object, mediaType));
  61 + return this;
  62 + }
  63 +
  64 + MyEvent append(String text) {
  65 + if (this.sb == null) {
  66 + this.sb = new StringBuilder();
  67 + }
  68 + this.sb.append(text);
  69 + return this;
  70 + }
  71 +
  72 + MyEvent append(char ch) {
  73 + if (this.sb == null) {
  74 + this.sb = new StringBuilder();
  75 + }
  76 + this.sb.append(ch);
  77 + return this;
  78 + }
  79 +
  80 + @Override
  81 + public Set<ResponseBodyEmitter.DataWithMediaType> build() {
  82 + if (!StringUtils.hasLength(this.sb) && this.dataToSend.isEmpty()) {
  83 + return Collections.emptySet();
  84 + }
  85 + append('\n');
  86 + saveAppendedText();
  87 + return this.dataToSend;
  88 + }
  89 +
  90 + private void saveAppendedText() {
  91 + if (this.sb != null) {
  92 + this.dataToSend.add(new ResponseBodyEmitter.DataWithMediaType(this.sb.toString(), TEXT_PLAIN));
  93 + this.sb = null;
  94 + }
  95 + }
  96 +}
  1 +package com.zhonglai.luhui.chatgpt.listener;
  2 +
  3 +import com.fasterxml.jackson.databind.ObjectMapper;
  4 +import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
  5 +import com.unfbx.chatgpt.entity.chat.Message;
  6 +import com.zhonglai.luhui.chatgpt.event.MyEvent;
  7 +import lombok.SneakyThrows;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import okhttp3.Response;
  10 +import okhttp3.ResponseBody;
  11 +import okhttp3.sse.EventSource;
  12 +import okhttp3.sse.EventSourceListener;
  13 +import org.springframework.http.MediaType;
  14 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  15 +
  16 +import java.util.Objects;
  17 +
  18 +/**
  19 + * 描述:OpenAIEventSourceListener
  20 + *
  21 + * @author https:www.unfbx.com
  22 + * @date 2023-02-22
  23 + */
  24 +@Slf4j
  25 +public class OpenAISSEEventSourceListener extends EventSourceListener {
  26 +
  27 + private long tokens;
  28 +
  29 + private SseEmitter sseEmitter;
  30 +
  31 + public OpenAISSEEventSourceListener(SseEmitter sseEmitter) {
  32 + this.sseEmitter = sseEmitter;
  33 + }
  34 +
  35 + /**
  36 + * {@inheritDoc}
  37 + */
  38 + @Override
  39 + public void onOpen(EventSource eventSource, Response response) {
  40 + log.info("OpenAI建立sse连接...");
  41 + }
  42 +
  43 + /**
  44 + * {@inheritDoc}
  45 + */
  46 + @SneakyThrows
  47 + @Override
  48 + public void onEvent(EventSource eventSource, String id, String type, String data) {
  49 + log.info("OpenAI返回数据{}:{}",type, data);
  50 + tokens += 1;
  51 + if (data.equals("[DONE]")) {
  52 + log.info("OpenAI返回数据结束了");
  53 +// sseEmitter.send(new MyEvent().data("<br/><br/>tokens:" + tokens(), MediaType.TEXT_EVENT_STREAM));
  54 +// sseEmitter.send(SseEmitter.event()
  55 +// .id("[TOKENS]")
  56 +// .data("<br/><br/>tokens:" + tokens())
  57 +// .reconnectTime(3000)
  58 +// );
  59 +// sseEmitter.send("[DONE]", MediaType.TEXT_EVENT_STREAM);
  60 +// sseEmitter.send(SseEmitter.event()
  61 +// .id("[DONE]")
  62 +// .data("[DONE]")
  63 +// .reconnectTime(3000)
  64 +// );
  65 + // 传输完成后自动关闭sse
  66 + sseEmitter.complete();
  67 + return;
  68 + }
  69 + ObjectMapper mapper = new ObjectMapper();
  70 + ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
  71 +
  72 + try {
  73 + Message delta = completionResponse.getChoices().get(0).getDelta();
  74 + if(null != delta.getContent())
  75 + {
  76 + sseEmitter.send(new MyEvent().data(delta.getContent(), MediaType.TEXT_EVENT_STREAM));
  77 + }
  78 +// sseEmitter.send(SseEmitter.event()
  79 +// .id(completionResponse.getId())
  80 +// .data(delta.getContent())
  81 +// .reconnectTime(3000)
  82 +// );
  83 + } catch (Exception e) {
  84 + log.error("sse信息推送失败!");
  85 + eventSource.cancel();
  86 + e.printStackTrace();
  87 + }
  88 + }
  89 +
  90 +
  91 + @Override
  92 + public void onClosed(EventSource eventSource) {
  93 + log.info("流式输出返回值总共{}tokens", tokens() - 2);
  94 + log.info("OpenAI关闭sse连接...");
  95 + }
  96 +
  97 +
  98 + @SneakyThrows
  99 + @Override
  100 + public void onFailure(EventSource eventSource, Throwable t, Response response) {
  101 + if (Objects.isNull(response)) {
  102 + return;
  103 + }
  104 + ResponseBody body = response.body();
  105 + if (Objects.nonNull(body)) {
  106 + log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t);
  107 + } else {
  108 + log.error("OpenAI sse连接异常data:{},异常:{}", response, t);
  109 + }
  110 + eventSource.cancel();
  111 + }
  112 +
  113 + /**
  114 + * tokens
  115 + * @return
  116 + */
  117 + public long tokens() {
  118 + return tokens;
  119 + }
  120 +}
  1 +package com.zhonglai.luhui.chatgpt.listener;
  2 +
  3 +import com.fasterxml.jackson.databind.ObjectMapper;
  4 +import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
  5 +import lombok.SneakyThrows;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import okhttp3.Response;
  8 +import okhttp3.ResponseBody;
  9 +import okhttp3.sse.EventSource;
  10 +import okhttp3.sse.EventSourceListener;
  11 +
  12 +import javax.websocket.Session;
  13 +import java.util.Objects;
  14 +
  15 +/**
  16 + * 描述:OpenAI流式输出Socket接收
  17 + *
  18 + * @author https:www.unfbx.com
  19 + * @date 2023-03-23
  20 + */
  21 +@Slf4j
  22 +public class OpenAIWebSocketEventSourceListener extends EventSourceListener {
  23 +
  24 + private Session session;
  25 +
  26 + public OpenAIWebSocketEventSourceListener(Session session) {
  27 + this.session = session;
  28 + }
  29 +
  30 + /**
  31 + * {@inheritDoc}
  32 + */
  33 + @Override
  34 + public void onOpen(EventSource eventSource, Response response) {
  35 + log.info("OpenAI建立sse连接...");
  36 + }
  37 +
  38 + /**
  39 + * {@inheritDoc}
  40 + */
  41 + @SneakyThrows
  42 + @Override
  43 + public void onEvent(EventSource eventSource, String id, String type, String data) {
  44 + log.info("OpenAI返回数据:{}", data);
  45 + if (data.equals("[DONE]")) {
  46 + log.info("OpenAI返回数据结束了");
  47 + session.getBasicRemote().sendText("[DONE]");
  48 + return;
  49 + }
  50 + ObjectMapper mapper = new ObjectMapper();
  51 + ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
  52 + String delta = mapper.writeValueAsString(completionResponse.getChoices().get(0).getDelta());
  53 + session.getBasicRemote().sendText(delta);
  54 + }
  55 +
  56 +
  57 + @Override
  58 + public void onClosed(EventSource eventSource) {
  59 + log.info("OpenAI关闭sse连接...");
  60 + }
  61 +
  62 +
  63 + @SneakyThrows
  64 + @Override
  65 + public void onFailure(EventSource eventSource, Throwable t, Response response) {
  66 + if (Objects.isNull(response)) {
  67 + return;
  68 + }
  69 + ResponseBody body = response.body();
  70 + if (Objects.nonNull(body)) {
  71 + log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), t);
  72 + } else {
  73 + log.error("OpenAI sse连接异常data:{},异常:{}", response, t);
  74 + }
  75 + eventSource.cancel();
  76 + }
  77 +}
  1 +package com.zhonglai.luhui.chatgpt.service;
  2 +
  3 +import com.unfbx.chatgpt.entity.chat.ChatCompletion;
  4 +import com.zhonglai.luhui.chatgpt.listener.OpenAISSEEventSourceListener;
  5 +
  6 +public interface CompleteCallback {
  7 + void sseChatEnd(ChatCompletion chatCompletion, OpenAISSEEventSourceListener openAISSEEventSourceListener);
  8 +}
  1 +package com.zhonglai.luhui.chatgpt.service;
  2 +
  3 +import com.unfbx.chatgpt.entity.chat.ChatCompletion;
  4 +import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
  5 +import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
  6 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  7 +
  8 +/**
  9 + * 描述:
  10 + *
  11 + * @author https:www.unfbx.com
  12 + * @date 2023-04-08
  13 + */
  14 +public interface SseService {
  15 + /**
  16 + * 创建SSE
  17 + * @param uid
  18 + * @return
  19 + */
  20 + SseEmitter createSse(String uid);
  21 +
  22 + /**
  23 + * 关闭SSE
  24 + * @param uid
  25 + */
  26 + void closeSse(String uid);
  27 +
  28 + /**
  29 + * 客户端发送消息到服务端
  30 + * @param uid
  31 + * @param chatRequest
  32 + */
  33 + ChatResponse sseChat(String uid, ChatRequest chatRequest, ChatCompletion.Model model, CompleteCallback completeCallback);
  34 +}
  1 +package com.zhonglai.luhui.chatgpt.service.impl;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.hutool.json.JSONUtil;
  6 +import com.unfbx.chatgpt.OpenAiStreamClient;
  7 +import com.unfbx.chatgpt.entity.chat.ChatCompletion;
  8 +import com.unfbx.chatgpt.entity.chat.Message;
  9 +import com.unfbx.chatgpt.exception.BaseException;
  10 +import com.zhonglai.luhui.chatgpt.config.LocalCache;
  11 +import com.zhonglai.luhui.chatgpt.controller.request.ChatRequest;
  12 +import com.zhonglai.luhui.chatgpt.controller.response.ChatResponse;
  13 +import com.zhonglai.luhui.chatgpt.listener.OpenAISSEEventSourceListener;
  14 +import com.zhonglai.luhui.chatgpt.service.CompleteCallback;
  15 +import com.zhonglai.luhui.chatgpt.service.SseService;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.stereotype.Service;
  18 +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  19 +
  20 +import java.io.IOException;
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +
  24 +/**
  25 + * 描述:
  26 + *
  27 + * @author https:www.unfbx.com
  28 + * @date 2023-04-08
  29 + */
  30 +@Service
  31 +@Slf4j
  32 +public class SseServiceImpl implements SseService {
  33 +
  34 + private final OpenAiStreamClient openAiStreamClient;
  35 +
  36 + public SseServiceImpl(OpenAiStreamClient openAiStreamClient) {
  37 + this.openAiStreamClient = openAiStreamClient;
  38 + }
  39 +
  40 + @Override
  41 + public SseEmitter createSse(String uid) {
  42 + //默认30秒超时,设置为0L则永不超时
  43 + SseEmitter sseEmitter = new SseEmitter(0l);
  44 + //完成后回调
  45 + sseEmitter.onCompletion(() -> {
  46 + log.info("[{}]结束连接...................", uid);
  47 + LocalCache.CACHE.remove(uid);
  48 + });
  49 + //超时回调
  50 + sseEmitter.onTimeout(() -> {
  51 + log.info("[{}]连接超时...................", uid);
  52 + });
  53 + //异常回调
  54 + sseEmitter.onError(
  55 + throwable -> {
  56 + try {
  57 + log.info("[{}]连接异常,{}", uid, throwable.toString());
  58 + sseEmitter.send(SseEmitter.event()
  59 + .id(uid)
  60 + .name("发生异常!")
  61 + .data(Message.builder().content("发生异常请重试!").build())
  62 + .reconnectTime(3000));
  63 + LocalCache.CACHE.put(uid, sseEmitter);
  64 + } catch (IOException e) {
  65 + e.printStackTrace();
  66 + }
  67 + }
  68 + );
  69 +// try {
  70 +// sseEmitter.send(SseEmitter.event().reconnectTime(5000));
  71 +// } catch (IOException e) {
  72 +// e.printStackTrace();
  73 +// }
  74 + LocalCache.CACHE.put(uid, sseEmitter);
  75 + log.info("[{}]创建sse连接成功!", uid);
  76 + return sseEmitter;
  77 + }
  78 +
  79 + @Override
  80 + public void closeSse(String uid) {
  81 + SseEmitter sse = (SseEmitter) LocalCache.CACHE.get(uid);
  82 + if (sse != null) {
  83 + sse.complete();
  84 + //移除
  85 + LocalCache.CACHE.remove(uid);
  86 + }
  87 + }
  88 +
  89 + @Override
  90 + public ChatResponse sseChat(String uid, ChatRequest chatRequest, ChatCompletion.Model model, CompleteCallback completeCallback) {
  91 + if (ArrayUtil.isEmpty(chatRequest.getMsg())) {
  92 + log.info("参数异常,msg为null", uid);
  93 + throw new BaseException("参数异常,msg不能为空~");
  94 + }
  95 +// String messageContext = (String) LocalCache.CACHE.get("msg" + uid);
  96 +
  97 +// if (StrUtil.isNotBlank(messageContext)) {
  98 +// messages = JSONUtil.toList(messageContext, Message.class);
  99 +// if (messages.size() >= 10) {
  100 +// messages = messages.subList(1, 10);
  101 +// }
  102 +// Message currentMessage = Message.builder().content(chatRequest.getMsg()).role(Message.Role.USER).build();
  103 +// messages.add(currentMessage);
  104 +// } else {
  105 +// Message currentMessage = Message.builder().content(chatRequest.getMsg()).role(Message.Role.USER).build();
  106 +// messages.add(currentMessage);
  107 +// }
  108 +
  109 + List<Message> messages = new ArrayList<>();
  110 + for(String msg:chatRequest.getMsg())
  111 + {
  112 + Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
  113 + messages.add(currentMessage);
  114 + }
  115 +
  116 +
  117 + SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);
  118 +
  119 + if (sseEmitter == null) {
  120 + log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);
  121 + throw new BaseException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");
  122 + }
  123 + OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(sseEmitter);
  124 + ChatCompletion completion = ChatCompletion
  125 + .builder()
  126 + .messages(messages)
  127 + .model(model.getName())
  128 + .build();
  129 + openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
  130 +// LocalCache.CACHE.put("msg" + uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
  131 + ChatResponse response = new ChatResponse();
  132 + response.setQuestionTokens(completion.tokens());
  133 + if(null != completeCallback)
  134 + {
  135 + completeCallback.sseChatEnd(completion,openAIEventSourceListener);
  136 + }
  137 + return response;
  138 + }
  139 +}
  1 +package com.zhonglai.luhui.chatgpt.websocket;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import com.unfbx.chatgpt.OpenAiStreamClient;
  6 +import com.unfbx.chatgpt.entity.chat.Message;
  7 +import com.zhonglai.luhui.chatgpt.config.LocalCache;
  8 +import com.zhonglai.luhui.chatgpt.listener.OpenAIWebSocketEventSourceListener;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.beans.factory.annotation.Autowired;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.websocket.*;
  14 +import javax.websocket.server.PathParam;
  15 +import javax.websocket.server.ServerEndpoint;
  16 +import java.util.ArrayList;
  17 +import java.util.Collections;
  18 +import java.util.List;
  19 +import java.util.concurrent.ConcurrentHashMap;
  20 +import java.util.concurrent.CopyOnWriteArraySet;
  21 +
  22 +/**
  23 + * 描述:websocket 服务端
  24 + *
  25 + * @author https:www.unfbx.com
  26 + * @date 2023-03-23
  27 + */
  28 +@Slf4j
  29 +@Component
  30 +@ServerEndpoint("/websocket/{uid}")
  31 +public class WebSocketServer {
  32 +
  33 + private static OpenAiStreamClient openAiStreamClient;
  34 +
  35 + @Autowired
  36 + public void setOrderService(OpenAiStreamClient openAiStreamClient) {
  37 + this.openAiStreamClient = openAiStreamClient;
  38 + }
  39 +
  40 + //在线总数
  41 + private static int onlineCount;
  42 + //当前会话
  43 + private Session session;
  44 + //用户id -目前是按浏览器随机生成
  45 + private String uid;
  46 +
  47 + private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
  48 +
  49 + /**
  50 + * 用来存放每个客户端对应的WebSocketServer对象
  51 + */
  52 + private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap();
  53 +
  54 + /**
  55 + * 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
  56 + */
  57 + private final static List<Session> SESSIONS = Collections.synchronizedList(new ArrayList<>());
  58 +
  59 +
  60 + /**
  61 + * 建立连接
  62 + * @param session
  63 + * @param uid
  64 + */
  65 + @OnOpen
  66 + public void onOpen(Session session, @PathParam("uid") String uid) {
  67 + this.session = session;
  68 + this.uid = uid;
  69 + webSocketSet.add(this);
  70 + SESSIONS.add(session);
  71 + if (webSocketMap.containsKey(uid)) {
  72 + webSocketMap.remove(uid);
  73 + webSocketMap.put(uid, this);
  74 + } else {
  75 + webSocketMap.put(uid, this);
  76 + addOnlineCount();
  77 + }
  78 + log.info("[连接ID:{}] 建立连接, 当前连接数:{}", this.uid, getOnlineCount());
  79 + }
  80 +
  81 + /**
  82 + * 断开连接
  83 + */
  84 + @OnClose
  85 + public void onClose() {
  86 + webSocketSet.remove(this);
  87 + if (webSocketMap.containsKey(uid)) {
  88 + webSocketMap.remove(uid);
  89 + subOnlineCount();
  90 + }
  91 + log.info("[连接ID:{}] 断开连接, 当前连接数:{}", uid, getOnlineCount());
  92 + }
  93 +
  94 + /**
  95 + * 发送错误
  96 + * @param session
  97 + * @param error
  98 + */
  99 + @OnError
  100 + public void onError(Session session, Throwable error) {
  101 + log.info("[连接ID:{}] 错误原因:{}", this.uid, error.getMessage());
  102 + error.printStackTrace();
  103 + }
  104 +
  105 + /**
  106 + * 接收到客户端消息
  107 + * @param msg
  108 + */
  109 + @OnMessage
  110 + public void onMessage(String msg) {
  111 + log.info("[连接ID:{}] 收到消息:{}", this.uid, msg);
  112 + //接受参数
  113 + OpenAIWebSocketEventSourceListener eventSourceListener = new OpenAIWebSocketEventSourceListener(this.session);
  114 + String messageContext = (String) LocalCache.CACHE.get(uid);
  115 + List<Message> messages = new ArrayList<>();
  116 + if (StrUtil.isNotBlank(messageContext)) {
  117 + messages = JSONUtil.toList(messageContext, Message.class);
  118 + if (messages.size() >= 10) {
  119 + messages = messages.subList(1, 10);
  120 + }
  121 + Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
  122 + messages.add(currentMessage);
  123 + } else {
  124 + Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
  125 + messages.add(currentMessage);
  126 + }
  127 + openAiStreamClient.streamChatCompletion(messages, eventSourceListener);
  128 + LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
  129 + }
  130 +
  131 +
  132 + /**
  133 + * 获取当前连接数
  134 + *
  135 + * @return
  136 + */
  137 + public static synchronized int getOnlineCount() {
  138 + return onlineCount;
  139 + }
  140 +
  141 + /**
  142 + * 当前连接数加一
  143 + */
  144 + public static synchronized void addOnlineCount() {
  145 + WebSocketServer.onlineCount++;
  146 + }
  147 +
  148 + /**
  149 + * 当前连接数减一
  150 + */
  151 + public static synchronized void subOnlineCount() {
  152 + WebSocketServer.onlineCount--;
  153 + }
  154 +
  155 +}
  156 +
  1 +.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%)}
  1 +/** @license
  2 + * eventsource.js
  3 + * Available under MIT License (MIT)
  4 + * https://github.com/Yaffle/EventSource/
  5 + */
  6 +!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);
  1 +/**
  2 + * marked - a markdown parser
  3 + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
  4 + * https://github.com/chjj/marked
  5 + */
  6 +(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)}());
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<link href="css/markdown.css" rel="stylesheet" type="text/css"/>
  4 +
  5 +<head>
  6 + <meta charset="UTF-8">
  7 + <title>战损版ChatGPT-SSE实现流式输出</title>
  8 + <link rel="icon" type="image/png" sizes="32x32" href="image/favicon-32x32.png">
  9 + <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  10 + <script src="js/markdown.min.js"></script>
  11 + <script src="js/eventsource.min.js"></script>
  12 + <script>
  13 + function setText(text, uuid_str) {
  14 + let content = document.getElementById(uuid_str)
  15 + content.innerHTML = marked(text);
  16 + }
  17 +
  18 + function uuid() {
  19 + var s = [];
  20 + var hexDigits = "0123456789abcdef";
  21 + for (var i = 0; i < 36; i++) {
  22 + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  23 + }
  24 + s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
  25 + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
  26 + s[8] = s[13] = s[18] = s[23] = "-";
  27 +
  28 + var uuid = s.join("");
  29 + return uuid;
  30 + }
  31 +
  32 + window.onload = function () {
  33 + let disconnectBtn = document.getElementById("disconnectSSE");
  34 + let messageElement = document.getElementById("message");
  35 + let chat = document.getElementById("chat");
  36 + let sse;
  37 + let uid = window.localStorage.getItem("uid");
  38 + if (uid == null || uid == '' || uid == 'null') {
  39 + uid = uuid();
  40 + }
  41 + let text = '';
  42 + let uuid_str;
  43 + // 设置本地存储
  44 + window.localStorage.setItem("uid", uid);
  45 + // 回车事件
  46 + messageElement.onkeydown = function () {
  47 + if (window.event.keyCode === 13) {
  48 + if (!messageElement.value) {
  49 + return;
  50 + }
  51 + uuid_str = uuid();
  52 +
  53 + //创建sse
  54 + const eventSource = new EventSourcePolyfill('/api/createSse', {
  55 + headers: {
  56 + 'uid': uid
  57 + }
  58 + });
  59 +
  60 + eventSource.onopen = (event) => {
  61 + console.log("开始输出后端返回值");
  62 + sse = event.target;
  63 + };
  64 + eventSource.onmessage = (event) => {
  65 + if (event.lastEventId == "[TOKENS]") {
  66 + text = text + event.data;
  67 + setText(text, uuid_str)
  68 + text = ''
  69 + return;
  70 + }
  71 + if (event.data == "[DONE]") {
  72 + if (sse) {
  73 + sse.close();
  74 + }
  75 + return;
  76 + }
  77 + let json_data = JSON.parse(event.data)
  78 + if (json_data.content == null || json_data.content == 'null') {
  79 + return;
  80 + }
  81 + text = text + json_data.content;
  82 + setText(text, uuid_str)
  83 + };
  84 + eventSource.onerror = (event) => {
  85 + console.log("onerror", event);
  86 + alert("服务异常请重试并联系开发者!")
  87 + if (event.readyState === EventSource.CLOSED) {
  88 + console.log('connection is closed');
  89 + } else {
  90 + console.log("Error occured", event);
  91 + }
  92 + event.target.close();
  93 + };
  94 + eventSource.addEventListener("customEventName", (event) => {
  95 + console.log("Message id is " + event.lastEventId);
  96 + });
  97 + eventSource.addEventListener("customEventName", (event) => {
  98 + console.log("Message id is " + event.lastEventId);
  99 + });
  100 + $.ajax({
  101 + type: 'post',
  102 + url: '/api/chat',
  103 + data: JSON.stringify({
  104 + 'msg': messageElement.value
  105 + }),
  106 + contentType: "application/json;charset=UTF-8",
  107 + dataType: "json",
  108 + headers: {
  109 + "uid": uid,
  110 + },
  111 + beforeSend: function (request) {
  112 +
  113 + },
  114 + success: function (result) {
  115 + //新增问题框
  116 + chat.innerHTML += '<tr><td style="height: 30px;">' + messageElement.value + '<br/><br/> tokens:' + result.question_tokens + '</td></tr>';
  117 + messageElement.value = null
  118 + //新增答案框
  119 + chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
  120 + },
  121 + complete: function () {
  122 + },
  123 + error: function () {
  124 + console.info("发送问题失败!");
  125 + }
  126 + })
  127 + }
  128 + };
  129 +
  130 + disconnectBtn.onclick = function () {
  131 + if (sse) {
  132 + sse.close();
  133 + }
  134 + };
  135 +
  136 + };
  137 + </script>
  138 +</head>
  139 +
  140 +<body>
  141 +<div class="float-card">
  142 + <div class="float-card-item" id="disconnectSSE">
  143 + <a rel="noopener noreferrer">停止输出</a>
  144 + </div>
  145 +</div>
  146 +<div class="input-card">
  147 + <div class="input-card-item">
  148 + <input id="message" placeholder="输入你的问题,回车结束......" type="text">
  149 + </div>
  150 +</div>
  151 +<div class="container" >
  152 + <table border="1">
  153 + <tbody id="chat">
  154 + </tbody>
  155 + </table>
  156 +</div>
  157 +</body>
  158 +<style>
  159 + .markdown-body {
  160 + box-sizing: border-box;
  161 + min-width: 200px;
  162 + max-width: 980px;
  163 + margin: 0 auto;
  164 + padding: 45px;
  165 + }
  166 +
  167 + @media (max-width: 767px) {
  168 + .markdown-body {
  169 + padding: 15px;
  170 + }
  171 + }
  172 +
  173 + input {
  174 + height: 50px;
  175 + width: 500px;
  176 + font-size: 20px;
  177 + background: no-repeat;
  178 + color: #d0838e;
  179 + }
  180 +
  181 + .container {
  182 + width: 980px;
  183 + border: 1px solid black;
  184 + display: flex;
  185 + flex-direction: column;
  186 + margin-left: 150px;
  187 + margin-top: 40px;
  188 + }
  189 +
  190 + .input-card {
  191 + position: fixed;
  192 + display: inline-block;
  193 + right: 37%;
  194 + top: 80%;
  195 + }
  196 +
  197 + .input-card-item {
  198 + display: flex;
  199 + flex-direction: column;
  200 + justify-content: center;
  201 + align-items: center;
  202 + margin-bottom: 16px;
  203 + }
  204 +
  205 + .float-card {
  206 + position: fixed;
  207 + display: inline-block;
  208 + right: 120px;
  209 + top: 100px;
  210 + }
  211 +
  212 + .float-card-item {
  213 + display: flex;
  214 + flex-direction: column;
  215 + justify-content: center;
  216 + align-items: center;
  217 + width: 60px;
  218 + height: 60px;
  219 + border-radius: 50%;
  220 + background-color: #ccccd6;
  221 + margin-bottom: 16px;
  222 + }
  223 +
  224 + .float-card-item:last-child {
  225 + margin-bottom: 0px;
  226 + background-color: #d0838e;
  227 + }
  228 +
  229 + .float-card-item a {
  230 + text-decoration: none;
  231 + color: #594649;
  232 + font-size: 13px;
  233 + }
  234 +</style>
  235 +</html>
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<link href="../static/css/markdown.css" rel="stylesheet" type="text/css"/>
  4 +
  5 +<head>
  6 + <meta charset="UTF-8">
  7 + <title>战损版ChatGPT</title>
  8 + <link rel="icon" type="image/png" sizes="32x32" href="../static/image/favicon-32x32.png">
  9 + <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  10 + <script src="../static/js/markdown.min.js"></script>
  11 + <script>
  12 + function setText(text, uuid_str) {
  13 + let content = document.getElementById(uuid_str)
  14 + content.innerHTML = marked(text);
  15 + }
  16 +
  17 + function uuid() {
  18 + var s = [];
  19 + var hexDigits = "0123456789abcdef";
  20 + for (var i = 0; i < 36; i++) {
  21 + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  22 + }
  23 + s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
  24 + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
  25 + s[8] = s[13] = s[18] = s[23] = "-";
  26 +
  27 + var uuid = s.join("");
  28 + return uuid;
  29 + }
  30 +
  31 +
  32 + window.onload = function () {
  33 + let disconnectBtn = document.getElementById("disconnectSSE");
  34 + let messageElement = document.getElementById("message");
  35 + let chat = document.getElementById("chat");
  36 + let sse;
  37 + // 回车事件
  38 + messageElement.onkeydown = function () {
  39 + if (window.event.keyCode === 13) {
  40 + if (!messageElement.value) {
  41 + return;
  42 + }
  43 + let text = '';
  44 + let uuid_str = uuid();
  45 + const eventSource = new EventSource('http://localhost:8000/chat?message=' + messageElement.value);
  46 +
  47 + eventSource.onopen = (event) => {
  48 + console.log("onopen", event.readyState, event.target);
  49 + sse = event.target;
  50 + //新增问题框
  51 + chat.innerHTML += '<tr><td style="height: 50px;">' + messageElement.value + '</td></tr>';
  52 + messageElement.value = null
  53 + //新增答案框
  54 + chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
  55 + };
  56 + eventSource.onmessage = (event) => {
  57 + if (event.data == "[DONE]") {
  58 + text = '';
  59 + if (sse) {
  60 + sse.close();
  61 + }
  62 + return;
  63 + }
  64 + let json_data = JSON.parse(event.data)
  65 + if (json_data.content == null || json_data.content == 'null') {
  66 + text = '';
  67 + return;
  68 + }
  69 + text = text + json_data.content;
  70 + setText(text, uuid_str)
  71 +
  72 + };
  73 + eventSource.onerror = (event) => {
  74 + console.log("onerror", event);
  75 + alert("服务异常请重试并联系开发者!")
  76 + if (event.readyState === EventSource.CLOSED) {
  77 + console.log('connection is closed');
  78 + } else {
  79 + console.log("Error occured", event);
  80 + }
  81 + event.target.close();
  82 + };
  83 + eventSource.addEventListener("customEventName", (event) => {
  84 + console.log("Message id is " + event.lastEventId);
  85 + });
  86 + }
  87 + };
  88 +
  89 + disconnectBtn.onclick = function () {
  90 + if (sse) {
  91 + sse.close();
  92 + }
  93 + };
  94 +
  95 + };
  96 + </script>
  97 +</head>
  98 +
  99 +<body>
  100 +<!--<div class="float-card-item send-btn">-->
  101 +<!-- <a id="connectSSE" rel="noopener noreferrer">发送请求</a>-->
  102 +<!--</div>-->
  103 +<!--<div class="float-card-item dis-btn">-->
  104 +<!-- <a id="disconnectSSE" rel="noopener noreferrer">断开连接</a>-->
  105 +<!--</div>-->
  106 +<div class="float-card">
  107 + <div class="float-card-item">
  108 + <a href="https://www.unfbx.com" target="_blank" rel="noopener noreferrer">Website</a>
  109 + </div>
  110 + <div class="float-card-item">
  111 + <a href="https://github.com/Grt1228" target="_blank" rel="noopener noreferrer">Github</a>
  112 + </div>
  113 + <div class="float-card-item">
  114 + <a id="disconnectSSE" rel="noopener noreferrer">停止输出</a>
  115 + </div>
  116 +</div>
  117 +<div class="input-card">
  118 + <div class="input-card-item">
  119 + <input id="message" placeholder="输入你的问题,回车结束......" type="text">
  120 + </div>
  121 +</div>
  122 +<div class="container">
  123 + <table border="1">
  124 + <tbody id="chat">
  125 + <tr>
  126 + <td>
  127 + <pre style="font-size: 15px">
  128 + 帮忙点个star吧<br/>
  129 + 1、依赖ChatGPT开源Java SDK:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-java"
  130 + target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-java</a><br/>
  131 + 2、本项目免费开源地址:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-steam-output"
  132 + target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-steam-output</a><br/>
  133 + 3、默认保持连接5分钟,默认上下文保持10个,5分钟无请求上下文会话销毁
  134 + </pre>
  135 + </td>
  136 + </tr>
  137 + </tbody>
  138 + </table>
  139 +</div>
  140 +
  141 +
  142 +</body>
  143 +<style>
  144 + .markdown-body {
  145 + box-sizing: border-box;
  146 + min-width: 200px;
  147 + max-width: 980px;
  148 + margin: 0 auto;
  149 + padding: 45px;
  150 + }
  151 +
  152 + @media (max-width: 767px) {
  153 + .markdown-body {
  154 + padding: 15px;
  155 + }
  156 + }
  157 +
  158 + input {
  159 + height: 50px;
  160 + width: 500px;
  161 + font-size: 20px;
  162 + background: url(10) no-repeat;
  163 + color: #d0838e;
  164 + }
  165 +
  166 + .container {
  167 + width: 980px;
  168 + border: 1px solid black;
  169 + display: flex;
  170 + flex-direction: column;
  171 + margin-left: 150px;
  172 + margin-top: 40px;
  173 + }
  174 +
  175 + .input-card {
  176 + position: fixed;
  177 + display: inline-block;
  178 + right: 37%;
  179 + top: 80%;
  180 + }
  181 +
  182 + .input-card-item {
  183 + display: flex;
  184 + flex-direction: column;
  185 + justify-content: center;
  186 + align-items: center;
  187 + margin-bottom: 16px;
  188 + }
  189 +
  190 + .float-card {
  191 + position: fixed;
  192 + display: inline-block;
  193 + right: 120px;
  194 + top: 100px;
  195 + }
  196 +
  197 + .float-card-item {
  198 + display: flex;
  199 + flex-direction: column;
  200 + justify-content: center;
  201 + align-items: center;
  202 + width: 60px;
  203 + height: 60px;
  204 + border-radius: 50%;
  205 + background-color: #ccccd6;
  206 + margin-bottom: 16px;
  207 + }
  208 +
  209 + .float-card-item:last-child {
  210 + margin-bottom: 0px;
  211 + background-color: #d0838e;
  212 + }
  213 +
  214 + .float-card-item a {
  215 + text-decoration: none;
  216 + color: #594649;
  217 + font-size: 13px;
  218 + }
  219 +</style>
  220 +</html>
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 +<link href="css/markdown.css" rel="stylesheet" type="text/css"/>
  4 +
  5 +<head>
  6 + <meta charset="UTF-8">
  7 + <title>战损版ChatGPT-WebSocket实现流式输出</title>
  8 + <link rel="icon" type="image/png" sizes="32x32" href="image/favicon-32x32.png">
  9 + <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  10 + <script src="js/markdown.min.js"></script>
  11 +
  12 +
  13 + <script>
  14 + function setText(text, uuid_str) {
  15 + let content = document.getElementById(uuid_str)
  16 + content.innerHTML = marked(text);
  17 + }
  18 +
  19 + function uuid() {
  20 + var s = [];
  21 + var hexDigits = "0123456789abcdef";
  22 + for (var i = 0; i < 36; i++) {
  23 + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  24 + }
  25 + s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
  26 + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
  27 + s[8] = s[13] = s[18] = s[23] = "-";
  28 +
  29 + var uuid = s.join("");
  30 + return uuid;
  31 + }
  32 +
  33 +
  34 + window.onload = function () {
  35 + var socket
  36 + let uuid_str = '';
  37 + let text = '';
  38 + let disconnectBtn = document.getElementById("disconnectSSE");
  39 + let messageElement = document.getElementById("message");
  40 + let chat = document.getElementById("chat");
  41 + let uid = window.localStorage.getItem("uid");
  42 + if (uid == null || uid == '' || uid == 'null') {
  43 + uid = uuid();
  44 + }
  45 + // 设置本地存储
  46 + window.localStorage.setItem("uid", uid);
  47 + if (typeof (WebSocket) == "undefined") {
  48 + console.log("您的浏览器不支持WebSocket");
  49 + } else {
  50 + console.log("您的浏览器支持WebSocket");
  51 + //实现化WebSocket对象
  52 + //指定要连接的服务器地址与端口建立连接
  53 + //注意ws、wss使用不同的端口。我使用自签名的证书测试,
  54 + //无法使用wss,浏览器打开WebSocket时报错
  55 + //ws对应http、wss对应https。
  56 + socket = new WebSocket("ws://localhost:8000/websocket/"+uid);
  57 + //连接打开事件
  58 + socket.onopen = function () {
  59 + console.log("Socket 已打开");
  60 + };
  61 + //收到消息事件
  62 + socket.onmessage = function (msg) {
  63 + if (msg.data == "[DONE]") {
  64 + text = '';
  65 + return;
  66 + }
  67 + let json_data = JSON.parse(msg.data)
  68 + if (json_data.content == null || json_data.content == 'null') {
  69 + text = '';
  70 + return;
  71 + }
  72 + text = text + json_data.content;
  73 + setText(text, uuid_str)
  74 + };
  75 + //连接关闭事件
  76 + socket.onclose = function () {
  77 + console.log("Socket已关闭");
  78 + };
  79 + //发生了错误事件
  80 + socket.onerror = function () {
  81 + alert("服务异常请重试并联系开发者!")
  82 + }
  83 + //窗口关闭时,关闭连接
  84 + window.unload = function () {
  85 + socket.close();
  86 + };
  87 + }
  88 + // 回车事件
  89 + messageElement.onkeydown = function () {
  90 + if (window.event.keyCode === 13) {
  91 + if (!messageElement.value) {
  92 + return;
  93 + }
  94 + uuid_str = uuid();
  95 + socket.send(messageElement.value);
  96 + //新增问题框
  97 + chat.innerHTML += '<tr><td style="height: 50px;">' + messageElement.value + '</td></tr>';
  98 + messageElement.value = null
  99 + //新增答案框
  100 + chat.innerHTML += '<tr><td><article id="' + uuid_str + '" class="markdown-body"></article></td></tr>';
  101 + }
  102 + };
  103 +
  104 + disconnectBtn.onclick = function () {
  105 + if (socket) {
  106 + socket.close();
  107 + }
  108 + };
  109 +
  110 + };
  111 + </script>
  112 +</head>
  113 +
  114 +<body>
  115 +<!--<div class="float-card-item send-btn">-->
  116 +<!-- <a id="connectSSE" rel="noopener noreferrer">发送请求</a>-->
  117 +<!--</div>-->
  118 +<!--<div class="float-card-item dis-btn">-->
  119 +<!-- <a id="disconnectSSE" rel="noopener noreferrer">断开连接</a>-->
  120 +<!--</div>-->
  121 +<div class="float-card">
  122 + <div class="float-card-item">
  123 + <a href="https://www.unfbx.com" target="_blank" rel="noopener noreferrer">Website</a>
  124 + </div>
  125 + <div class="float-card-item">
  126 + <a href="https://github.com/Grt1228" target="_blank" rel="noopener noreferrer">Github</a>
  127 + </div>
  128 + <div class="float-card-item">
  129 + <a id="disconnectSSE" rel="noopener noreferrer">停止输出</a>
  130 + </div>
  131 +</div>
  132 +<div class="input-card">
  133 + <div class="input-card-item">
  134 + <input id="message" placeholder="输入你的问题,回车结束......" type="text">
  135 + </div>
  136 +</div>
  137 +<div class="container">
  138 + <table border="1">
  139 + <tbody id="chat">
  140 + <tr>
  141 + <td>
  142 + <pre style="font-size: 15px">
  143 + 帮忙点个star吧<br/>
  144 + 1、依赖ChatGPT开源Java SDK:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-java"
  145 + target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-java</a><br/>
  146 + 2、本项目免费开源地址:<a rel="noopener noreferrer" href="https://github.com/Grt1228/chatgpt-steam-output"
  147 + target="_blank" style="font-size: 15px">https://github.com/Grt1228/chatgpt-steam-output</a><br/>
  148 + 3、默认保持连接5分钟,默认上下文保持10个,5分钟无请求上下文会话销毁。
  149 + </pre>
  150 + </td>
  151 + </tr>
  152 + </tbody>
  153 + </table>
  154 +</div>
  155 +
  156 +
  157 +</body>
  158 +<style>
  159 + .markdown-body {
  160 + box-sizing: border-box;
  161 + min-width: 200px;
  162 + max-width: 980px;
  163 + margin: 0 auto;
  164 + padding: 45px;
  165 + }
  166 +
  167 + @media (max-width: 767px) {
  168 + .markdown-body {
  169 + padding: 15px;
  170 + }
  171 + }
  172 +
  173 + input {
  174 + height: 50px;
  175 + width: 500px;
  176 + font-size: 20px;
  177 + background: url(10) no-repeat;
  178 + color: #d0838e;
  179 + }
  180 +
  181 + .container {
  182 + width: 980px;
  183 + border: 1px solid black;
  184 + display: flex;
  185 + flex-direction: column;
  186 + margin-left: 150px;
  187 + margin-top: 40px;
  188 + }
  189 +
  190 + .input-card {
  191 + position: fixed;
  192 + display: inline-block;
  193 + right: 37%;
  194 + top: 80%;
  195 + }
  196 +
  197 + .input-card-item {
  198 + display: flex;
  199 + flex-direction: column;
  200 + justify-content: center;
  201 + align-items: center;
  202 + margin-bottom: 16px;
  203 + }
  204 +
  205 + .float-card {
  206 + position: fixed;
  207 + display: inline-block;
  208 + right: 120px;
  209 + top: 100px;
  210 + }
  211 +
  212 + .float-card-item {
  213 + display: flex;
  214 + flex-direction: column;
  215 + justify-content: center;
  216 + align-items: center;
  217 + width: 60px;
  218 + height: 60px;
  219 + border-radius: 50%;
  220 + background-color: #ccccd6;
  221 + margin-bottom: 16px;
  222 + }
  223 +
  224 + .float-card-item:last-child {
  225 + margin-bottom: 0px;
  226 + background-color: #d0838e;
  227 + }
  228 +
  229 + .float-card-item a {
  230 + text-decoration: none;
  231 + color: #594649;
  232 + font-size: 13px;
  233 + }
  234 +</style>
  235 +</html>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-jar-rocketmq</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <dependency>
  23 + <groupId>org.apache.rocketmq</groupId>
  24 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  25 + </dependency>
  26 + <dependency>
  27 + <groupId>com.zhonglai.luhui</groupId>
  28 + <artifactId>ruoyi-common</artifactId>
  29 + </dependency>
  30 + </dependencies>
  31 +
  32 +</project>
1 -package com.ruoyi.system.rocketmq; 1 +package com.zhonglai.luhui.rocketmq.service;
2 2
3 import com.alibaba.fastjson.JSON; 3 import com.alibaba.fastjson.JSON;
4 import com.alibaba.fastjson.JSONObject; 4 import com.alibaba.fastjson.JSONObject;
  5 +import com.ruoyi.common.core.domain.DeviceCommandApi;
5 import com.ruoyi.common.core.domain.Message; 6 import com.ruoyi.common.core.domain.Message;
6 import com.ruoyi.common.core.domain.MessageCode; 7 import com.ruoyi.common.core.domain.MessageCode;
7 -import com.ruoyi.common.core.domain.MessageCodeType;  
8 -import com.ruoyi.system.dto.DeviceCommandApi;  
9 import org.apache.rocketmq.client.exception.MQBrokerException; 8 import org.apache.rocketmq.client.exception.MQBrokerException;
10 import org.apache.rocketmq.client.exception.MQClientException; 9 import org.apache.rocketmq.client.exception.MQClientException;
11 import org.apache.rocketmq.client.exception.RequestTimeoutException; 10 import org.apache.rocketmq.client.exception.RequestTimeoutException;
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>com.zhonglai.luhui</groupId>
  8 + <artifactId>Luhui</artifactId>
  9 + <version>1.0-SNAPSHOT</version>
  10 + <relativePath>../../pom.xml</relativePath>
  11 + </parent>
  12 +
  13 + <artifactId>lh-jar-sys-service</artifactId>
  14 +
  15 + <properties>
  16 + <maven.compiler.source>8</maven.compiler.source>
  17 + <maven.compiler.target>8</maven.compiler.target>
  18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19 + </properties>
  20 + <dependencies>
  21 + <dependency>
  22 + <groupId>com.zhonglai.luhui</groupId>
  23 + <artifactId>lh-common-datasource</artifactId>
  24 + </dependency>
  25 + <dependency>
  26 + <groupId>com.zhonglai.luhui</groupId>
  27 + <artifactId>lh-public-dao</artifactId>
  28 + </dependency>
  29 + <dependency>
  30 + <groupId>com.zhonglai.luhui</groupId>
  31 + <artifactId>lh-domain</artifactId>
  32 + </dependency>
  33 + <dependency>
  34 + <groupId>com.zhonglai.luhui</groupId>
  35 + <artifactId>ruoyi-common-redis</artifactId>
  36 + </dependency>
  37 +
  38 + </dependencies>
  39 +</project>
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 -import com.ruoyi.system.domain.SysConfig; 3 +import com.ruoyi.system.domain.sys.SysConfig;
4 4
5 import java.util.List; 5 import java.util.List;
6 6
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 import com.ruoyi.system.domain.entity.SysDept; 3 import com.ruoyi.system.domain.entity.SysDept;
4 import org.apache.ibatis.annotations.Param; 4 import org.apache.ibatis.annotations.Param;
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 import com.ruoyi.system.domain.entity.SysDictData; 3 import com.ruoyi.system.domain.entity.SysDictData;
4 import org.apache.ibatis.annotations.Param; 4 import org.apache.ibatis.annotations.Param;
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 import com.ruoyi.system.domain.entity.SysDictType; 3 import com.ruoyi.system.domain.entity.SysDictType;
4 import org.apache.ibatis.annotations.Mapper; 4 import org.apache.ibatis.annotations.Mapper;
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 -import com.ruoyi.system.domain.SysLogininfor; 3 +import com.ruoyi.system.domain.sys.SysLogininfor;
4 4
5 import java.util.List; 5 import java.util.List;
6 6
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 import com.ruoyi.system.domain.entity.SysMenu; 3 import com.ruoyi.system.domain.entity.SysMenu;
4 import org.apache.ibatis.annotations.Param; 4 import org.apache.ibatis.annotations.Param;
1 -package com.ruoyi.system.mapper; 1 +package com.zhonglai.luhui.sys.mapper;
2 2
3 -import com.ruoyi.system.domain.SysNotice; 3 +import com.ruoyi.system.domain.sys.SysNotice;
4 4
5 import java.util.List; 5 import java.util.List;
6 6