feat(知识库): 增加知识库模块

This commit is contained in:
ageer
2025-03-02 11:19:29 +08:00
parent 04f579d033
commit 1385b165c9
1421 changed files with 166583 additions and 64636 deletions

View File

@@ -1,392 +0,0 @@
# 项目相关配置
ruoyi:
# 名称
name: "xmzs"
# 版本
version: ${revision}
# 版权年份
copyrightYear: 2023
# 实例演示开关
demoEnabled: true
# 获取ip地址开关
addressEnabled: false
captcha:
enable: false
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
category: CIRCLE
# 数字验证码位数
numberLength: 1
# 字符验证码长度
charLength: 4
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 6039
servlet:
# 应用的访问路径
context-path: /
# undertow 配置
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
# 日志配置
logging:
level:
com.xmzs: @logging.level@
org.springframework: warn
config: classpath:logback-plus.xml
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
application:
name: ${ruoyi.name}
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: @profiles.active@
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
jackson:
# 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
serialization:
# 格式化输出
indent_output: false
# 忽略无法转换的对象
fail_on_empty_beans: false
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期 设为7天 (必定过期) 单位: 秒
timeout: 604800
# token临时有效期 (指定时间无操作就过期) 单位: 秒
activity-timeout: 604800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
# security配置
security:
# 排除路径
excludes:
# 修改用户头像
- /system/user/edit/avatar
- /pay/returnUrl
- /pay/notifyUrl
# 上传文件
- /resource/oss/upload
# 重置密码
- /auth/reset/password
# 聊天接口
- /chat
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# 公共路径
- /favicon.ico
- /error
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# actuator 监控配置
- /actuator
- /actuator/**
# 多租户配置
tenant:
# 是否开启
enable: false
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 不支持多包, 如有需要可在注解配置 或 提升扫包等级
# 例如 com.**.**.mapper
mapperPackage: com.xmzs.**.mapper
# 对应的 XML 文件位置
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 实体扫描多个package用逗号或者分号分隔
typeAliasesPackage: com.xmzs.**.domain
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
# 是否打印 Logo banner
banner: true
dbConfig:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值
logicDeleteValue: 2
# 逻辑未删除值
logicNotDeleteValue: 0
# 字段验证策略之 insert,在 insert 的时候的字段验证策略
# IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
insertStrategy: NOT_NULL
# 字段验证策略之 update,在 update 的时候的字段验证策略
updateStrategy: NOT_NULL
# 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
where-strategy: NOT_NULL
# 数据加密
mybatis-encryptor:
# 是否开启加密
enable: false
# 默认加密算法
algorithm: BASE64
# 编码方式 BASE64/HEX。默认BASE64
encode: BASE64
# 安全秘钥 对称算法的秘钥 如AESSM4
password:
# 公私钥 非对称算法的公私钥 如SM2RSA
publicKey:
privateKey:
# Swagger配置
swagger:
info:
# 标题
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${ruoyi.version}'
# 作者信息
contact:
name: ageerle
email: ageerle@163.com
url: https://gitee.com/ageerle/ruoyi-ai
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
swagger-ui:
# 持久化认证数据
persistAuthorization: true
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
packages-to-scan: com.xmzs.demo
- group: 2.通用模块
packages-to-scan: com.xmzs.web
- group: 3.系统模块
packages-to-scan: com.xmzs.system
- group: 4.代码生成模块
packages-to-scan: com.xmzs.generator
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 全局线程池相关配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
--- # 分布式锁 lock4j 全局配置
lock4j:
# 获取分布式锁超时时间,默认为 3000 毫秒
acquire-timeout: 3000
# 分布式锁的超时时间,默认为 30 秒
expire: 30000
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/sys-console.log
--- # websocket
websocket:
enabled: false
# 路径
path: ''
# 设置访问源地址
allowedOrigins: '*'
# 微信小程序配置信息
wx:
miniapp:
configs:
- appid: # 你的appid
secret: # 你的secret
token: #微信小程序消息服务器配置的token
aesKey: #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
baidu:
# 是否开启文本审核
enabled: false
# 文本审核
textReview:
apiKey: '' # apiKey
secretKey: '' # secretKey
appKey: xxxxxxxxxxxxxxxxx
secretKey: xxxxxxxxxxxxxxxxxxxxxxx
wechat:
# 是否使用微信 true/false
enable: true
# 生成的登录二维码路径 默认与项目同级
qrPath: "./"
keyword:
# 重置会话指令
reset: "重置会话"
# ai画图指令(DALL·E模型 https://platform.openai.com/docs/models/dall-e)
# generation 根据关键词生成图片(https://platform.openai.com/docs/guides/images/generations)
image: "ai画图"
# ai语音指令(TTS模型 https://platform.openai.com/docs/api-reference/audio)
audio: "ai语音"
mj:
api-secret: 'sk-xx'
task-store:
type: in_memory
timeout: 30d
translate-way: gpt
# proxy:
# host: 127.0.0.1
# port: 10809
ng-discord:
server: 'https://xxx.pandarobot.chat/'
cdn: 'https://xxx.pandarobot.chat/'
wss: 'https://xxx.pandarobot.chat/'
openai:
gpt-api-url: 'https://api.pandarobot.chat/'
gpt-api-key: 'sk-xx'
accounts:
- guild-id: 'xx'
channel-id: 'xx'
user-token: 'xx'
--- # mail 邮件发送
mail:
enabled: true
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方遵循RFC-822标准
from: ageerle@163.com
# 用户名注意如果使用foxmail邮箱此处user为qq号
user: ageerle@163.com
# 密码(填写授权码)
pass: TOGXBVPYFVPFRQMQ
# 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长单位毫秒缺省值不超时
timeout: 0
# Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0
# chatgpt配置信息
chat:
apiKey: 'sk-xxx'
apiHost: 'https://api.pandarobot.chat/'
# 支付配置信息
pay:
pid: 'xxx'
key: 'xxx'
payUrl: 'https://pay.pandarobot.chat/mapi.php'
notify_url: 'https://www.pandarobot.chat/pay/returnUrl'
return_url: 'https://www.pandarobot.chat/pay/notifyUrl'
type: 'wxpay'
device: 'pc'
sign_type: 'MD5'

View File

@@ -57,7 +57,10 @@
<artifactId>ruoyi-fusion</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge</artifactId>
</dependency>
<!-- demo模块 -->
@@ -78,6 +81,12 @@
<artifactId>thumbnailator</artifactId>
<version>0.4.11</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -1,23 +0,0 @@
package com.xmzs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 启动程序
*
* @author Lion Li
*/
@SpringBootApplication
public class PandaApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(PandaApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ panda智能助手启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@@ -1,18 +0,0 @@
package com.xmzs;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* web容器中进行部署
*
* @author Lion Li
*/
public class PandaServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(PandaApplication.class);
}
}

View File

@@ -1,157 +0,0 @@
package com.xmzs.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import com.xmzs.common.core.constant.Constants;
import com.xmzs.common.core.domain.R;
import com.xmzs.common.core.domain.model.*;
import com.xmzs.common.core.utils.MapstructUtils;
import com.xmzs.common.core.utils.StreamUtils;
import com.xmzs.common.core.utils.StringUtils;
import com.xmzs.common.satoken.utils.LoginHelper;
import com.xmzs.common.tenant.helper.TenantHelper;
import com.xmzs.system.domain.bo.SysTenantBo;
import com.xmzs.system.domain.vo.LoginTenantVo;
import com.xmzs.system.domain.vo.SysTenantVo;
import com.xmzs.system.domain.vo.TenantListVo;
import com.xmzs.system.service.ISysTenantService;
import com.xmzs.system.service.SysLoginService;
import com.xmzs.system.service.SysRegisterService;
import com.xmzs.web.domain.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.util.List;
/**
* 认证
*
* @author Lion Li
*/
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {
private final SysLoginService loginService;
private final SysRegisterService registerService;
private final ISysTenantService tenantService;
/**
* 登录方法
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/login")
public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
body.setTenantId(Constants.TENANT_ID);
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.login(
body.getTenantId(),
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
loginVo.setUserInfo(LoginHelper.getLoginUser());
return R.ok(loginVo);
}
/**
* 短信登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/smsLogin")
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
/**
* 邮件登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/emailLogin")
public R<LoginVo> emailLogin(@Validated @RequestBody EmailLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.emailLogin(body.getTenantId(), body.getEmail(), body.getEmailCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
/**
* 游客登录
*
* @param loginBody
* @return 结果
*/
@PostMapping("/visitorLogin")
public R<LoginVo> xcxLogin(@RequestBody VisitorLoginBody loginBody) {
return R.ok(loginService.visitorLogin(loginBody));
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 用户注册
*/
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) {
registerService.register(user);
return R.ok();
}
/**
* 重置密码
*/
@PostMapping("/reset/password")
@SaIgnore
public R<Void> resetPassWord(@Validated @RequestBody RegisterBody user) {
registerService.resetPassWord(user);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
// 获取域名
String host = new URL(request.getRequestURL().toString()).getHost();
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo -> StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return R.ok(vo);
}
}

View File

@@ -1,140 +0,0 @@
package com.xmzs.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.xmzs.common.core.constant.Constants;
import com.xmzs.common.core.constant.GlobalConstants;
import com.xmzs.common.core.domain.R;
import com.xmzs.common.core.utils.SpringUtils;
import com.xmzs.common.core.utils.StringUtils;
import com.xmzs.common.core.utils.reflect.ReflectUtils;
import com.xmzs.common.mail.config.properties.MailProperties;
import com.xmzs.common.mail.utils.MailUtils;
import com.xmzs.common.redis.utils.RedisUtils;
import com.xmzs.common.sms.config.properties.SmsProperties;
import com.xmzs.common.sms.core.SmsTemplate;
import com.xmzs.common.sms.entity.SmsResult;
import com.xmzs.common.web.config.properties.CaptchaProperties;
import com.xmzs.common.web.enums.CaptchaType;
import com.xmzs.web.domain.request.EmailRequest;
import com.xmzs.web.domain.vo.CaptchaVo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* 验证码操作处理
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties;
private final MailProperties mailProperties;
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
Map<String, String> map = new HashMap<>(1);
map.put("code", code);
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
SmsResult result = smsTemplate.send(phonenumber, templateId, map);
if (!result.isSuccess()) {
log.error("验证码短信发送异常 => {}", result);
return R.fail(result.getMessage());
}
return R.ok();
}
/**
* 邮箱验证码
*
* @param emailRequest 用户邮箱
*/
//@PostMapping("/resource/email/code")
@PostMapping("/resource/email/code")
public R<Void> emailCode(@RequestBody @Valid EmailRequest emailRequest) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + emailRequest.getUsername();
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(emailRequest.getUsername(), "【熊猫助手】登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
}
return R.ok();
}
/**
* 生成验证码
*/
@GetMapping("/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return R.ok(captchaVo);
}
}

View File

@@ -1,113 +0,0 @@
package com.xmzs.controller;
import com.xmzs.common.chat.domain.request.ChatRequest;
import com.xmzs.common.chat.domain.request.Dall3Request;
import com.xmzs.common.chat.entity.Tts.TextToSpeech;
import com.xmzs.common.chat.entity.files.UploadFileResponse;
import com.xmzs.common.chat.entity.images.Item;
import com.xmzs.common.chat.entity.whisper.WhisperResponse;
import com.xmzs.common.core.domain.R;
import com.xmzs.common.core.domain.model.LoginUser;
import com.xmzs.common.core.exception.base.BaseException;
import com.xmzs.common.mybatis.core.page.PageQuery;
import com.xmzs.common.mybatis.core.page.TableDataInfo;
import com.xmzs.common.satoken.utils.LoginHelper;
import com.xmzs.system.domain.bo.ChatMessageBo;
import com.xmzs.system.domain.vo.ChatMessageVo;
import com.xmzs.system.service.IChatMessageService;
import com.xmzs.system.service.ISseService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-03-01
*/
@Controller
@Slf4j
@RequiredArgsConstructor
public class ChatController {
private final ISseService ISseService;
private final IChatMessageService chatMessageService;
/**
* 聊天接口
*/
@PostMapping("/chat")
@ResponseBody
public SseEmitter sseChat(@RequestBody @Valid ChatRequest chatRequest, HttpServletResponse response) {
return ISseService.sseChat(chatRequest);
}
/**
* 上传文件
*/
@PostMapping("/v1/upload")
@ResponseBody
public UploadFileResponse upload(@RequestPart("file") MultipartFile file) {
return ISseService.upload(file);
}
/**
* 语音转文本
*
* @param file
*/
@PostMapping("/audio")
@ResponseBody
public WhisperResponse audio(@RequestParam("file") MultipartFile file) {
WhisperResponse whisperResponse = ISseService.speechToTextTranscriptionsV2(file);
return whisperResponse;
}
/**
* 文本转语音
*
* @param textToSpeech
*/
@PostMapping("/speech")
@ResponseBody
public ResponseEntity<Resource> speech(@RequestBody TextToSpeech textToSpeech) {
return ISseService.textToSpeed(textToSpeech);
}
@PostMapping("/dall3")
@ResponseBody
public R<List<Item>> dall3(@RequestBody @Valid Dall3Request request) {
return R.ok(ISseService.dall3(request));
}
/**
* 聊天记录
*/
@PostMapping("/chatList")
@ResponseBody
public R<TableDataInfo<ChatMessageVo>> list(@RequestBody @Valid ChatMessageBo chatRequest, @RequestBody PageQuery pageQuery) {
// 默认查询当前登录用户消息记录
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new BaseException("用户未登录!");
}
chatRequest.setUserId(loginUser.getUserId());
TableDataInfo<ChatMessageVo> chatMessageVoTableDataInfo = chatMessageService.queryPageList(chatRequest, pageQuery);
return R.ok(chatMessageVoTableDataInfo);
}
}

View File

@@ -1,25 +0,0 @@
package com.xmzs.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 首页
*
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@Controller
public class IndexController {
/**
* 访问首页,提示语
*/
@GetMapping("/")
public String index() {
return "index.html";
}
}

View File

@@ -1,151 +0,0 @@
package com.xmzs.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import com.xmzs.common.config.PayConfig;
import com.xmzs.common.core.domain.R;
import com.xmzs.common.core.domain.model.LoginUser;
import com.xmzs.common.core.exception.base.BaseException;
import com.xmzs.common.core.utils.StringUtils;
import com.xmzs.common.oss.core.OssClient;
import com.xmzs.common.oss.entity.UploadResult;
import com.xmzs.common.oss.factory.OssFactory;
import com.xmzs.common.response.PayResponse;
import com.xmzs.common.satoken.utils.LoginHelper;
import com.xmzs.common.service.PayService;
import com.xmzs.common.utils.MD5Util;
import com.xmzs.system.domain.bo.PaymentOrdersBo;
import com.xmzs.system.domain.bo.SysUserBo;
import com.xmzs.system.domain.request.OrderRequest;
import com.xmzs.system.domain.vo.PaymentOrdersVo;
import com.xmzs.system.domain.vo.SysUserVo;
import com.xmzs.system.service.IPaymentOrdersService;
import com.xmzs.system.service.ISysUserService;
import com.xmzs.system.util.OrderNumberGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/pay")
@Slf4j
public class PayController {
private final PayService payService;
private final ISysUserService userService;
private final IPaymentOrdersService paymentOrdersService;
private final PayConfig payConfig;
/**
* 获取支付二维码
*
* @Date 2023/7/3
* @return void
**/
@PostMapping("/payUrl")
public R<PaymentOrdersVo> payUrl(@RequestBody OrderRequest orderRequest) {
LoginUser loginUser = LoginHelper.getLoginUser();
// 创建订单
PaymentOrdersBo paymentOrders = new PaymentOrdersBo();
paymentOrders.setOrderName(orderRequest.getName());
paymentOrders.setAmount(new BigDecimal(orderRequest.getMoney()));
String orderNo = OrderNumberGenerator.generate();
paymentOrders.setOrderNo(orderNo);
paymentOrders.setUserId(loginUser.getUserId());
// TODO 支付状态默认待支付 - 添加枚举
paymentOrders.setPaymentStatus("1");
paymentOrdersService.insertByBo(paymentOrders);
String payUrl = payService.getPayUrl(orderNo, orderRequest.getName(), Double.parseDouble(orderRequest.getMoney()), "192.168.1.6");
byte[] bytes = QrCodeUtil.generatePng(payUrl, 300, 300);
OssClient storage = OssFactory.instance();
UploadResult upload=storage.upload(bytes, storage.getPath("qrCode",".png"), "image/png");
PaymentOrdersVo paymentOrdersVo = new PaymentOrdersVo();
BeanUtil.copyProperties(paymentOrders,paymentOrdersVo);
paymentOrdersVo.setUrl(upload.getUrl());
return R.ok(paymentOrdersVo);
}
/**
* 跳转通知地址
*
* @Date 2023/7/3
* @param
* @return void
**/
@PostMapping("/notifyUrl")
public void notifyUrl() {
log.info("notifyUrl===========");
}
/**
* 获取订单信息
*
*/
@PostMapping("/orderInfo")
public R<PaymentOrdersVo> orderInfo(@RequestBody OrderRequest orderRequest) {
if(StringUtils.isEmpty(orderRequest.getOrderNo())){
throw new BaseException("订单号不能为空!");
}
PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo();
paymentOrdersBo.setOrderNo(orderRequest.getOrderNo());
List<PaymentOrdersVo> paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo);
if (CollectionUtil.isEmpty(paymentOrdersList)){
throw new BaseException("订单不存在!");
}
PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0);
return R.ok(paymentOrdersVo);
}
/**
* 跳转通知地址
*
* @Date 2023/7/3
* @param payResponse
* @return void
**/
@GetMapping("/returnUrl")
public String returnUrl(PayResponse payResponse) {
// 校验签名
String mdString = "money=" + payResponse.getMoney() + "&name=" + payResponse.getName() +
"&out_trade_no=" + payResponse.getOut_trade_no() + "&pid=" + payConfig.getPid() +
"&trade_no=" + payResponse.getTrade_no() + "&trade_status=" + payResponse.getTrade_status() +
"&type=" + payResponse.getType() + payConfig.getKey();
String sign = MD5Util.GetMD5Code(mdString);
if(!sign.equals(payResponse.getSign())){
throw new BaseException("校验签名失败!");
}
double money = Double.parseDouble(payResponse.getMoney());
log.info("支付订单号{}",payResponse);
PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo();
paymentOrdersBo.setOrderNo(payResponse.getOut_trade_no());
List<PaymentOrdersVo> paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo);
if (CollectionUtil.isEmpty(paymentOrdersList)){
throw new BaseException("订单不存在!");
}
// 订单状态修改为已支付
PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0);
paymentOrdersVo.setPaymentStatus("2");
paymentOrdersVo.setPaymentMethod(payResponse.getType());
BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo);
paymentOrdersService.updateByBo(paymentOrdersBo);
SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId());
sysUserVo.setUserBalance(sysUserVo.getUserBalance()+money);
SysUserBo sysUserBo = new SysUserBo();
BeanUtil.copyProperties(sysUserVo,sysUserBo);
// 设置为付费用户
sysUserBo.setUserGrade("1");
userService.updateUser(sysUserBo);
return "success";
}
}

View File

@@ -1,61 +0,0 @@
package com.xmzs.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.xmzs.common.core.domain.R;
import com.xmzs.common.wechat.Wechat;
import com.xmzs.common.wechat.controller.LoginController;
import com.xmzs.common.wechat.core.MsgCenter;
import com.xmzs.system.cofing.KeywordConfig;
import com.xmzs.system.cofing.WechatConfig;
import com.xmzs.system.handler.WechatMessageHandler;
import com.xmzs.system.service.ISseService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 个人微信扩展控制器
*
* @author WangLe
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class WeChatController {
@Getter
private Wechat wechatBot;
private final WechatConfig wechatConfig;
private final ISseService sseService;
private final KeywordConfig keywordConfig;
/**
* 获取微信登录二维码
*
*/
@GetMapping("/getQr")
public R<String> getQr() {
//微信
if (wechatConfig.getEnable()){
log.info("正在登录微信,请按提示操作:");
wechatBot = new Wechat(new WechatMessageHandler(sseService, keywordConfig));
// 登陆
LoginController login = new LoginController();
String qrCode = login.login_1();
new Thread(login::login_2).start();
wechatBot.start();
return R.ok(qrCode);
}else {
return R.fail();
}
}
}

View File

@@ -10,12 +10,12 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt
* @author Lion Li
*/
@SpringBootApplication
public class RuoYiApplication {
public class RuoYiAIApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(RuoYiApplication.class);
SpringApplication application = new SpringApplication(RuoYiAIApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ RuoYiAi启动成功 ლ(´ڡ`ლ)゙");
System.out.println("(♥◠‿◠)ノ゙ RuoYiAI启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@@ -8,11 +8,11 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
*
* @author Lion Li
*/
public class RuoYiServletInitializer extends SpringBootServletInitializer {
public class RuoYiAIServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RuoYiApplication.class);
return application.sources(RuoYiAIApplication.class);
}
}

View File

@@ -0,0 +1,210 @@
package org.ruoyi.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.theokanning.openai.completion.chat.ChatMessageRole;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.chat.config.ChatConfig;
import org.ruoyi.common.chat.domain.request.ChatRequest;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeFragmentBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo;
import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest;
import org.ruoyi.knowledge.domain.vo.KnowledgeAttachVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeFragmentVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
import org.ruoyi.knowledge.service.EmbeddingService;
import org.ruoyi.knowledge.service.IKnowledgeAttachService;
import org.ruoyi.knowledge.service.IKnowledgeFragmentService;
import org.ruoyi.knowledge.service.IKnowledgeInfoService;
import org.ruoyi.system.listener.SSEEventSourceListener;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.ruoyi.knowledge.chain.vectorstore.VectorStore;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.ArrayList;
import java.util.List;
/**
* 知识库
*
* @author Lion Li
* @date 2024-10-21
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/knowledge")
public class KnowledgeController extends BaseController {
private final IKnowledgeInfoService knowledgeInfoService;
private final VectorStore vectorStore;
private final IKnowledgeAttachService attachService;
private final IKnowledgeFragmentService fragmentService;
private final EmbeddingService embeddingService;
private OpenAiStreamClient openAiStreamClient;
private final ChatConfig chatConfig;
/**
* 知识库对话
*/
@PostMapping("/send")
public SseEmitter send(@RequestBody @Valid ChatRequest chatRequest) {
openAiStreamClient = chatConfig.getOpenAiStreamClient();
SseEmitter sseEmitter = new SseEmitter(0L);
SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
List<Message> messages = chatRequest.getMessages();
String content = messages.get(messages.size() - 1).getContent().toString();
List<String> nearestList;
List<Double> queryVector = embeddingService.getQueryVector(content);
nearestList = vectorStore.nearest(queryVector,chatRequest.getKid());
for (String prompt : nearestList) {
Message sysMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
messages.add(sysMessage);
}
Message userMessage = Message.builder().content(content + (nearestList.size() > 0 ? "\n\n注意回答问题时须严格根据我给你的系统上下文内容原文进行回答请不要自己发挥,回答时保持原来文本的段落层级" : "") ).role(Message.Role.USER).build();
messages.add(userMessage);
ChatCompletion completion = ChatCompletion
.builder()
.messages(messages)
.model(chatRequest.getModel())
.temperature(chatRequest.getTemperature())
.topP(chatRequest.getTop_p())
.stream(true)
.build();
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
return sseEmitter;
}
/**
* 根据用户信息查询本地知识库
*/
@GetMapping("/list")
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
if(!StpUtil.isLogin()){
throw new SecurityException("请先去登录!");
}
bo.setUid(LoginHelper.getUserId());
return knowledgeInfoService.queryPageList(bo, pageQuery);
}
/**
* 新增知识库
*/
@Log(title = "知识库", businessType = BusinessType.INSERT)
@PostMapping("/save")
public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) {
knowledgeInfoService.saveOne(bo);
return R.ok();
}
/**
* 删除知识库
*/
@PostMapping("/remove/{id}")
public R<String> remove(@PathVariable String id){
knowledgeInfoService.removeKnowledge(id);
return R.ok("删除知识库成功!");
}
/**
* 修改知识库
*/
@Log(title = "知识库", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
public R<Void> edit( @RequestBody KnowledgeInfoBo bo) {
return toAjax(knowledgeInfoService.updateByBo(bo));
}
/**
* 导出知识库列表
*/
@Log(title = "知识库", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeInfoBo bo, HttpServletResponse response) {
List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response);
}
/**
* 查询知识附件信息
*/
@GetMapping("/detail/{kid}")
public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery,@PathVariable String kid){
bo.setKid(kid);
return attachService.queryPageList(bo, pageQuery);
}
/**
* 上传知识库附件
*/
@PostMapping(value = "/attach/upload")
public R<String> upload(KnowledgeInfoUploadRequest request){
knowledgeInfoService.upload(request);
return R.ok("上传知识库附件成功!");
}
/**
* 获取知识库附件详细信息
*
* @param id 主键
*/
@GetMapping("attach/info/{id}")
public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(attachService.queryById(id));
}
/**
* 删除知识库附件
*
*/
@PostMapping("attach/remove/{kid}")
public R<Void> removeAttach(@NotEmpty(message = "主键不能为空")
@PathVariable String kid) {
attachService.removeKnowledgeAttach(kid);
return R.ok();
}
/**
* 查询知识片段
*/
@GetMapping("/fragment/list/{docId}")
public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) {
bo.setDocId(docId);
return fragmentService.queryPageList(bo, pageQuery);
}
}

View File

@@ -0,0 +1,67 @@
package org.ruoyi.controller;
import io.github.ollama4j.OllamaAPI;
import io.github.ollama4j.exceptions.OllamaBaseException;
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
import io.github.ollama4j.models.chat.OllamaChatResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* 描述Ollama API调用
*
* @author ageerle@163.com
* date 2024/11/6
*/
@RestController
public class OllamaController {
@RequestMapping(value = "/qianwen-ai2")
private void ollama2(@RequestParam(value = "msg") String msg) {
}
public static void main(String[] args) throws OllamaBaseException, IOException, InterruptedException {
String host = "http://localhost:11434/";
OllamaAPI ollama = new OllamaAPI(host);
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance("qwen2.5:7b");
// 设置超时时间
ollama.setRequestTimeoutSeconds(100);
// create first user question
String text = "";
String json = "{\"instruction\": \"解答以下数学问题。\",\"input\": \"计算 2 + 3 的结果。\",\"output\": \"2 + 3 = 5\"}";
OllamaChatRequestModel requestModel = builder.withMessage
(OllamaChatMessageRole.USER, "###文本:"+text+"###,理解文本内容,并且将文本内容转换为:"+json+"输出JSON格式,不要包含其他无关内容,内部使用无需脱敏")
.withGetJsonResponse()
.withStreaming()
.build();
// start conversation with model
OllamaChatResult chatResult = ollama.chat(requestModel);
System.out.println("First answer: " + chatResult.getResponse());
// create next userQuestion
// requestModel = builder.withMessages(chatResult.getChatHistory()).withMessage(OllamaChatMessageRole.USER, "And what is the second largest city?").build();
// "continue" conversation with model
// chatResult = ollamaAPI.chat(requestModel);
//System.out.println("Second answer: " + chatResult.getResponse());
// 历史记录
//System.out.println("Chat History: " + chatResult.getChatHistory());
}
}

View File

@@ -8,28 +8,6 @@ spring.boot.admin.client:
username: ruoyi
password: 123456
--- # xxl-job 配置
xxl.job:
# 执行器开关
enabled: false
# 调度中心地址:如调度中心集群部署存在多个地址则用逗号分隔。
admin-addresses: http://localhost:9100/xxl-job-admin
# 执行器通讯TOKEN非空时启用
access-token: xxl-job
executor:
# 执行器AppName执行器心跳注册分组依据为空则关闭自动注册
appname: xxl-job-executor
# 执行器端口号 执行器从9101开始往后写
port: 9101
# 执行器注册默认IP:PORT
address:
# 执行器IP默认自动获取IP
ip:
# 执行器运行日志文件存储磁盘路径
logpath: ./logs/xxl-job
# 执行器日志文件保存天数大于3生效
logretentiondays: 30
--- # 数据源配置
spring:
datasource:
@@ -49,7 +27,8 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://43.139.70.230:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ruoyi-ai
password: TZ7yaGtSRWeeBaBJ
password: eCaZ278N62k6fhYj
hikari:
# 最大连接池数量
maxPoolSize: 20
@@ -78,7 +57,7 @@ spring.data:
# 数据库索引
database: 0
# 密码(如没有密码请注释掉)
#password:
# password: 123456
# 连接超时时间
timeout: 10S
# 是否开启ssl

View File

@@ -6,7 +6,7 @@ ruoyi:
# 版本
version: ${revision}
# 版权年份
copyrightYear: 2023
copyrightYear: 2025
# 实例演示开关
demoEnabled: true
# 获取ip地址开关
@@ -294,7 +294,7 @@ management:
websocket:
enabled: true
# 路径
path: ''
path: '/resource/websocket'
# 设置访问源地址
allowedOrigins: '*'
@@ -308,28 +308,34 @@ wx:
aesKey: #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
# 企业微信应用
wechat:
# 是否使用微信 true/false
enable: true
# 生成的登录二维码路径 默认与项目同级
qrPath: "./"
# 企业微信应用
cp:
corpId:
appConfigs:
- agentId:
secret: ''
token: ''
aesKey: ''
# 知识库配置
chain:
split:
chunk:
endspliter: "<STOP>"
# 分块文本大小
size: 500
overlay: 0
qaspliter: "######"
size: 200
overlay: 30
qaspliter: "###"
# 知识库中检索的条数
limits: 5
vectorization:
type: openai
openai:
model: 'text-embedding-3-small'
baidu:
model: bge-large-zh
zhipu:
model: embedding-2
# 智普API KEY
token: xx
vector:
model: 'text-embedding-3-small'
store:
type: weaviate
weaviate:
@@ -341,38 +347,4 @@ chain:
port: 19530
dimension: 1536
collection: LocalKnowledge
llm:
openai:
token: sk-xx
model: gpt-4-1106-preview
chatglm:
baseurl: http://127.0.0.1:8000/
model: chatglm2-6b
baidu:
appKey: xx
secretKey: xx
model: ernie_bot
zhipu:
model: glm-4
audio:
type: openai
text:
type: openai
function:
type: baidu
vision:
type: openai
image:
type: openai
upload:
path: /data/upload
proxy:
socket:
host: 127.0.0.1
port: 7890
resource:
domain: http://127.0.0.1:${server.port}/resources

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
@font-face {font-family: "Engravers' Old English BT";
src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot"); /* IE9*/
src: url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff2") format("woff2"), /* chrome firefox */
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.woff") format("woff"), /* chrome firefox */
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.ttf") format("truetype"), /* chrome firefox opera Safari, Android, iOS 4.2+*/
url("https://unpkg.com/dmego-home-page@latest/assets/fonts/d571b52b60b5617399ce8eab62bf3eb3.svg#Engravers' Old English BT") format("svg"); /* iOS 4.1- */
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -1,38 +0,0 @@
const https = require('https')
const fs = require('fs')
const options = {
hostname: 'www.bing.com',
port: 443,
path: '/HPImageArchive.aspx?format=js&idx=0&n=8',
method: 'GET'
}
const req = https.request(options, bing_res => {
let bing_body = [], bing_data = {};
bing_res.on('data', (chunk) => {
bing_body.push(chunk);
});
bing_res.on('end', () => {
bing_body = Buffer.concat(bing_body);
bing_data = JSON.parse(bing_body.toString());
let img_array = bing_data.images;
let img_url = [];
img_array.forEach(img => {
img_url.push(img.url);
});
var jsonpStr = "getBingImages(" + JSON.stringify(img_url) + ")";
fs.writeFile('./assets/json/images.json', jsonpStr, (err) => {
if (err) {
throw err;
}
console.log("JSON data is saved: " + jsonpStr);
});
});
})
req.on('error', error => {
console.error(error)
})
req.end()

View File

@@ -1,104 +0,0 @@
var iUp = (function () {
var time = 0,
duration = 150,
clean = function () {
time = 0;
},
up = function (element) {
setTimeout(function () {
element.classList.add("up");
}, time);
time += duration;
},
down = function (element) {
element.classList.remove("up");
},
toggle = function (element) {
setTimeout(function () {
element.classList.toggle("up");
}, time);
time += duration;
};
return {
clean: clean,
up: up,
down: down,
toggle: toggle
};
})();
function getBingImages(imgUrls) {
/**
* 获取Bing壁纸
* 先使用 GitHub Action 每天获取 Bing 壁纸 URL 并更新 images.json 文件
* 然后读取 images.json 文件中的数据
*/
var indexName = "bing-image-index";
var index = sessionStorage.getItem(indexName);
var panel = document.querySelector('#panel');
if (isNaN(index) || index == 7) index = 0;
else index++;
var imgUrl = imgUrls[index];
var url = "https://www.cn.bing.com" + imgUrl;
panel.style.background = "url('" + url + "') center center no-repeat #666";
panel.style.backgroundSize = "cover";
sessionStorage.setItem(indexName, index);
}
function decryptEmail(encoded) {
var address = atob(encoded);
window.location.href = "mailto:" + address;
}
document.addEventListener('DOMContentLoaded', function () {
// 获取一言数据
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var res = JSON.parse(this.responseText);
document.getElementById('description').innerHTML = res.hitokoto + "<br/> -「<strong>" + res.from + "</strong>」";
}
};
xhr.open("GET", "https://v1.hitokoto.cn", true);
xhr.send();
var iUpElements = document.querySelectorAll(".iUp");
iUpElements.forEach(function (element) {
iUp.up(element);
});
var avatarElement = document.querySelector(".js-avatar");
avatarElement.addEventListener('load', function () {
avatarElement.classList.add("show");
});
});
var btnMobileMenu = document.querySelector('.btn-mobile-menu__icon');
var navigationWrapper = document.querySelector('.navigation-wrapper');
btnMobileMenu.addEventListener('click', function () {
if (navigationWrapper.style.display == "block") {
navigationWrapper.addEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
navigationWrapper.classList.toggle('visible');
navigationWrapper.classList.toggle('animated');
navigationWrapper.classList.toggle('bounceOutUp');
navigationWrapper.removeEventListener('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', arguments.callee);
});
navigationWrapper.classList.toggle('animated');
navigationWrapper.classList.toggle('bounceInDown');
navigationWrapper.classList.toggle('animated');
navigationWrapper.classList.toggle('bounceOutUp');
} else {
navigationWrapper.classList.toggle('visible');
navigationWrapper.classList.toggle('animated');
navigationWrapper.classList.toggle('bounceInDown');
}
btnMobileMenu.classList.toggle('social');
btnMobileMenu.classList.toggle('iconfont');
btnMobileMenu.classList.toggle('icon-list');
btnMobileMenu.classList.toggle('social');
btnMobileMenu.classList.toggle('iconfont');
btnMobileMenu.classList.toggle('icon-angleup');
btnMobileMenu.classList.toggle('animated');
btnMobileMenu.classList.toggle('fadeIn');
});

View File

@@ -1 +0,0 @@
getBingImages(["/th?id=OHR.TheRoachesPeakDistrict_EN-US9733115206_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SanMiguelAllende_EN-US9621237021_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.JediMonastery_EN-US9398447907_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.SonoranSpring_EN-US9207877073_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CratersOfTheMoon_EN-US6516727783_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.HawaiianLei_EN-US6290126556_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.CheetahRain_EN-US6179670004_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp","/th?id=OHR.TulouFujian_EN-US6009679228_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp"])

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1714852590465" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1571" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M263.1 844.8h497.7c22.9 0 41.5 18.6 41.5 41.5s-18.6 41.5-41.5 41.5H263.1c-22.9 0-41.5-18.6-41.5-41.5s18.6-41.5 41.5-41.5zM138.7 98.2h746.6c22.9 0 41.5 18.6 41.5 41.5v580.7c0 11-4.4 21.6-12.1 29.3-7.8 7.8-18.3 12.1-29.3 12.1H138.7c-11 0-21.6-4.4-29.3-12.1-7.8-7.8-12.1-18.3-12.1-29.3V139.7c-0.1-22.9 18.5-41.5 41.4-41.5z m331.8 478.9v60.4h83v-60.3c8.5-5.8 17.4-12.8 26.5-20.5l49.4 49.4 58.7-58.7-49.5-49.4c7.7-9 14.6-18 20.5-26.5h60.3v-83h-60.3c-6.4-9.1-13.3-18-20.5-26.5l49.4-49.4-58.7-58.6-49.4 49.4c-8.5-7.3-17.3-14.1-26.5-20.5v-60.3h-83V283c-8.5 5.8-17.4 12.8-26.5 20.5l-49.4-49.4-58.5 58.6 49.4 49.4c-7.2 8.5-14.1 17.3-20.5 26.5h-60.3v83h60.3c5.8 8.5 12.8 17.4 20.5 26.5L336 547.3l58.7 58.7 49.4-49.4c9.1 7.7 18 14.6 26.4 20.5z m0 0" p-id="1572"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,45 +0,0 @@
package com.xmzs.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* 断言单元测试案例
*
* @author Lion Li
*/
@DisplayName("断言单元测试案例")
public class AssertUnitTest {
@DisplayName("测试 assertEquals 方法")
@Test
public void testAssertEquals() {
Assertions.assertEquals("666", new String("666"));
Assertions.assertNotEquals("666", new String("666"));
}
@DisplayName("测试 assertSame 方法")
@Test
public void testAssertSame() {
Object obj = new Object();
Object obj1 = obj;
Assertions.assertSame(obj, obj1);
Assertions.assertNotSame(obj, obj1);
}
@DisplayName("测试 assertTrue 方法")
@Test
public void testAssertTrue() {
Assertions.assertTrue(true);
Assertions.assertFalse(true);
}
@DisplayName("测试 assertNull 方法")
@Test
public void testAssertNull() {
Assertions.assertNull(null);
Assertions.assertNotNull(null);
}
}

View File

@@ -1,70 +0,0 @@
package com.xmzs.test;
import com.xmzs.common.core.config.RuoYiConfig;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
/**
* 单元测试案例
*
* @author Lion Li
*/
@SpringBootTest // 此注解只能在 springboot 主包下使用 需包含 main 方法与 yml 配置文件
@DisplayName("单元测试案例")
public class DemoUnitTest {
@Autowired
private RuoYiConfig ruoYiConfig;
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
@Test
public void testTest() {
System.out.println(ruoYiConfig);
}
@Disabled
@DisplayName("测试 @Disabled 注解")
@Test
public void testDisabled() {
System.out.println(ruoYiConfig);
}
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
@DisplayName("测试 @Timeout 注解")
@Test
public void testTimeout() throws InterruptedException {
Thread.sleep(3000);
System.out.println(ruoYiConfig);
}
@DisplayName("测试 @RepeatedTest 注解")
@RepeatedTest(3)
public void testRepeatedTest() {
System.out.println(666);
}
@BeforeAll
public static void testBeforeAll() {
System.out.println("@BeforeAll ==================");
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
@AfterAll
public static void testAfterAll() {
System.out.println("@AfterAll ==================");
}
}

View File

@@ -1,72 +0,0 @@
package com.xmzs.test;
import com.xmzs.common.core.enums.UserType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* 带参数单元测试案例
*
* @author Lion Li
*/
@DisplayName("带参数单元测试案例")
public class ParamUnitTest {
@DisplayName("测试 @ValueSource 注解")
@ParameterizedTest
@ValueSource(strings = {"t1", "t2", "t3"})
public void testValueSource(String str) {
System.out.println(str);
}
@DisplayName("测试 @NullSource 注解")
@ParameterizedTest
@NullSource
public void testNullSource(String str) {
System.out.println(str);
}
@DisplayName("测试 @EnumSource 注解")
@ParameterizedTest
@EnumSource(UserType.class)
public void testEnumSource(UserType type) {
System.out.println(type.getUserType());
}
@DisplayName("测试 @MethodSource 注解")
@ParameterizedTest
@MethodSource("getParam")
public void testMethodSource(String str) {
System.out.println(str);
}
public static Stream<String> getParam() {
List<String> list = new ArrayList<>();
list.add("t1");
list.add("t2");
list.add("t3");
return list.stream();
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
}

View File

@@ -1,54 +0,0 @@
package com.xmzs.test;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 标签单元测试案例
*
* @author Lion Li
*/
@SpringBootTest
@DisplayName("标签单元测试案例")
public class TagUnitTest {
@Tag("dev")
@DisplayName("测试 @Tag dev")
@Test
public void testTagDev() {
System.out.println("dev");
}
@Tag("prod")
@DisplayName("测试 @Tag prod")
@Test
public void testTagProd() {
System.out.println("prod");
}
@Tag("local")
@DisplayName("测试 @Tag local")
@Test
public void testTagLocal() {
System.out.println("local");
}
@Tag("exclude")
@DisplayName("测试 @Tag exclude")
@Test
public void testTagExclude() {
System.out.println("exclude");
}
@BeforeEach
public void testBeforeEach() {
System.out.println("@BeforeEach ==================");
}
@AfterEach
public void testAfterEach() {
System.out.println("@AfterEach ==================");
}
}

View File

@@ -33,6 +33,7 @@
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-chat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-wechat</module>
</modules>
<artifactId>ruoyi-common</artifactId>

View File

@@ -159,6 +159,13 @@
<version>${revision}</version>
</dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-wechat</artifactId>
<version>${revision}</version>
</dependency>
<!-- AI绘画 -->
<dependency>
<groupId>org.ruoyi</groupId>

View File

@@ -26,6 +26,18 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
<version>1.0.0-beta.12</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
@@ -42,11 +54,6 @@
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
@@ -74,5 +81,16 @@
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -1,49 +0,0 @@
package com.xmzs.common.chat.config;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import com.xmzs.common.chat.openai.OpenAiStreamClient;
import com.xmzs.common.chat.openai.function.KeyRandomStrategy;
import com.xmzs.common.chat.openai.interceptor.OpenAILogger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* chat配置类
*
* @author: wangle
* @date: 2023/5/16
*/
@Configuration
public class ChatConfig {
@Value("${chat.apiKey}")
private List<String> apiKey;
@Value("${chat.apiHost}")
private String apiHost;
@Bean(name = "openAiStreamClient")
public OpenAiStreamClient openAiStreamClient() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(600, TimeUnit.SECONDS)
.readTimeout(600, TimeUnit.SECONDS)
.build();
return OpenAiStreamClient
.builder()
.apiHost(apiHost)
.apiKey(apiKey)
//自定义key使用策略 默认随机策略
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
.build();
}
}

View File

@@ -1,36 +0,0 @@
package com.xmzs.common.chat.config;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUnit;
import lombok.extern.slf4j.Slf4j;
/**
* 描述:
*
* @author https:www.unfbx.com
* @date 2023-03-10
*/
@Slf4j
public class LocalCache {
/**
* 缓存时长
*/
public static final long TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
/**
* 清理间隔
*/
private static final long CLEAN_TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
/**
* 缓存对象
*/
public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
static {
//启动定时任务
CACHE.schedulePrune(CLEAN_TIMEOUT);
}
}

View File

@@ -1,61 +0,0 @@
package com.xmzs.common.chat.config;
import cn.hutool.core.util.StrUtil;
import com.xmzs.common.chat.config.properties.WebSocketProperties;
import com.xmzs.common.chat.handler.PlusWebSocketHandler;
import com.xmzs.common.chat.interceptor.PlusWebSocketInterceptor;
import com.xmzs.common.chat.listener.WebSocketTopicListener;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* WebSocket 配置
*
* @author zendwang
*/
@AutoConfiguration
@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true")
@EnableConfigurationProperties(WebSocketProperties.class)
@EnableWebSocket
public class WebSocketConfig {
@Bean
public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
WebSocketHandler webSocketHandler,
WebSocketProperties webSocketProperties) {
if (StrUtil.isBlank(webSocketProperties.getPath())) {
webSocketProperties.setPath("/websocket");
}
if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
webSocketProperties.setAllowedOrigins("*");
}
return registry -> registry
.addHandler(webSocketHandler, webSocketProperties.getPath())
.addInterceptors(handshakeInterceptor)
.setAllowedOrigins(webSocketProperties.getAllowedOrigins());
}
@Bean
public HandshakeInterceptor handshakeInterceptor() {
return new PlusWebSocketInterceptor();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new PlusWebSocketHandler();
}
@Bean
public WebSocketTopicListener topicListener() {
return new WebSocketTopicListener();
}
}

View File

@@ -1,26 +0,0 @@
package com.xmzs.common.chat.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* WebSocket 配置项
*
* @author zendwang
*/
@ConfigurationProperties("websocket")
@Data
public class WebSocketProperties {
private Boolean enabled;
/**
* 路径
*/
private String path;
/**
* 设置访问源地址
*/
private String allowedOrigins;
}

View File

@@ -1,38 +0,0 @@
package com.xmzs.common.chat.constant;
/**
* 描述:
*
* @author https:www.unfbx.com
* @since 2023-03-06
*/
public class OpenAIConst {
public final static String OPENAI_HOST = "https://api.openai.com/";
public final static int SUCCEED_CODE = 200;
/** GPT3扣除费用 */
public final static double GPT3_COST = 0.05;
/** GPT4扣除费用 */
public final static double GPT4_COST = 0.2;
/** DALL普通绘图扣除费用 */
public final static double DALL3_COST = 0.3;
/** DALL高清绘图扣除费用 */
public final static double DALL3_HD_COST = 0.5;
/** MJ操作类型1(变化、变焦、文生图、图生图、局部重绘、混图)扣除费用 */
public final static double MJ_COST_TYPE1 = 0.3;
/** MJ操作类型2(换脸、放大、图生文、prompt分析)扣除费用 */
public final static double MJ_COST_TYPE2 = 0.1;
/** MJ操作类型3(查询任务进度、获取seed)扣除费用 */
public final static double MJ_COST_TYPE3 = 0.0;
/** 默认账户余额 */
public final static double USER_BALANCE = 5;
}

View File

@@ -1,28 +0,0 @@
package com.xmzs.common.chat.constant;
/**
* websocket的常量配置
*
* @author zendwang
*/
public interface WebSocketConstants {
/**
* websocketSession中的参数的key
*/
String LOGIN_USER_KEY = "loginUser";
/**
* 订阅的频道
*/
String WEB_SOCKET_TOPIC = "global:websocket";
/**
* 前端心跳检查的命令
*/
String PING = "ping";
/**
* 服务端心跳恢复的字符串
*/
String PONG = "pong";
}

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