first commit

This commit is contained in:
moyangzhan 2023-08-09 20:35:59 +08:00
parent ff102fa689
commit f1065dada5
144 changed files with 6417 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

69
README.md Normal file
View File

@ -0,0 +1,69 @@
# Getting Started
> 声明:此项目只发布于 Github基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号等行为,谨防受骗。
![1691582252468](image/README/1691582252468.png)
![1691583184761](image/README/1691583184761.png)
![1691583124744](image/README/1691583124744.png)
![1691583329105](image/README/1691583329105.png)
该仓库为后端服务,前端项目见[aideepin-web](https://github.com/moyangzhan/aideepin-web)
### 如何部署
#### 初始化
* 初始化数据库
* 创建数据库aideepin
* 执行docs/create.sql
* 填充openai的secret_key
```
update adi_sys_config set value = 'my_chatgpt_secret_key' where name = 'secret_key'
```
* 修改配置文件
* mysql: application-[dev|prod].xml中的spring.datasource
* redis: application-[dev|prod].xml中的spring.data.redis
* mail: application.xml中的spring.mail
#### 编译及运行
* 进入项目
```
cd aideepin
```
* 打包:
```
mvn clean package -Dmaven.test.skip=true
```
* 运行
a. jar包启动
```
cd adi-chat/target
nohup java -jar -Xms768m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError adi-chat-0.0.1-SNAPSHOT.jar --spring.profiles.active=[dev|prod] dev/null 2>&1 &
```
b. docker启动
```
cd adi-chat
docker build . -t aideepin:0.0.1
docker run -d \
--name=aideepin \
-e APP_PROFILE=[dev|prod] \
-v="/data/aideepin/logs:/data/logs" \
aideepin:0.0.1
```

4
adi-admin/README.md Normal file
View File

@ -0,0 +1,4 @@
Admin site
TODO...

32
adi-admin/pom.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.moyz</groupId>
<artifactId>aideepin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>adi-admin</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.moyz</groupId>
<artifactId>adi-bootstrap</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.moyz</groupId>
<artifactId>adi-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

25
adi-bootstrap/pom.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.moyz</groupId>
<artifactId>aideepin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>adi-bootstrap</artifactId>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,17 @@
package com.moyz.adi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableAsync
@EnableScheduling
public class BootstrapApplication {
public static void main(String[] args) {
SpringApplication.run(BootstrapApplication.class, args);
}
}

View File

@ -0,0 +1,42 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
username: root
password: 123456
data:
redis:
host: localhost
port: 6379
password:
database: 0
lettuce:
pool:
#连接池最大连接数
max-active: 20
#连接池最大阻塞等待时间
max-wait: -1
#连接池中的最大空闲连接
max-idle: 5
#连接池中的最小空闲连接
min-idle: 1
logging:
file:
path: D:/data/logs
openai:
proxy:
enable: true
host: 127.0.0.1
http-port: 1087
adi:
frontend-url: http://localhost:1002
backend-url: http://localhost:1002/api
local:
files: D:/data/files/
images: D:/data/images/
tmp_images: D:/data/tmp_images/

View File

@ -0,0 +1,22 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
username: your-mysql-account
password: your-mysql-password
data:
redis:
host: localhost
port: 6379
password:
database: 0
lettuce:
pool:
#连接池最大连接数
max-active: 20
#连接池最大阻塞等待时间
max-wait: -1
#连接池中的最大空闲连接
max-idle: 5
#连接池中的最小空闲连接
min-idle: 1

View File

@ -0,0 +1,60 @@
server:
port: 9999
context-path: /
session:
timeout: 28800
tomcat:
uri-encoding: UTF-8
spring:
application:
name: AiDeepIn
profiles:
active: dev
jackson:
date-format: "yyyy-MM-dd HH:mm:ss"
time-zone: "GMT+8"
serialization: { write-dates-as-timestamps: false }
cache:
type: redis
redis:
key-prefix: CACHE
time-to-live: 1d
mail:
default-encoding: UTF-8
protocol: smtps
host: your-email-host # smtp.exmail.qq.com
username: your-email-username # xxx@qq.com
password: your-email-password
port: 465
properties:
mail:
smtp:
ssl:
enable: true
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
springdoc:
swagger-ui:
path: /swagger-ui.html
mybatis-plus:
# 支持统配符 * 或者 ; 分割
mapper-locations: classpath*:/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
file: /data/logs
adi:
frontend-url: http://www.aideepin.com
backend-url: http://www.aideepin.com/api
local:
files: /data/files/
images: /data/images/
tmp_images: /data/tmp_images/

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- <include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<springProperty name="APP_NAME" scope="context" source="spring.application.name"/>
<springProperty name="LOG_FILEMAXDAY" scope="context" source="logback.filemaxday" defaultValue="30"/>
<springProperty name="LOG_MAXFILESIZE" scope="context" source="logback.filesize" defaultValue="50MB"/>
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%level){blue} %clr([%thread]){orange} %clr(%logger){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<property name="CONSOLE_LOG_PATTERN_NO_COLOR"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread] %logger %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- 控制台日志 -->
<appender name="StdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成常规日志文件 -->
<appender name="FileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/data/logs/adi.log</file>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN_NO_COLOR}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 基于时间的分包策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/data/logs/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--保留时间,单位:天-->
<maxHistory>${LOG_FILEMAXDAY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
</appender>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FileAppender"/>
</root>
</springProfile>
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="StdoutAppender"/>
</root>
</springProfile>
</configuration>

17
adi-chat/Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM openjdk:17
ENV LANG="en_US.UTF-8" \
LANGUAGE="en_US:en" \
LC_ALL="en_US.UTF-8" \
APP_VERSION="1.0.0-SNAPSHOT" \
TZ="Asia/Shanghai" \
APP_PROFILE="dev" \
JAVA_OPTS="-Xms768m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ADD ./target/adi-chat-0.0.1-SNAPSHOT.jar /data/app/aideepin.jar
ENTRYPOINT ["sh","-c", "java $JAVA_OPTS -jar /data/app/aideepin.jar --spring.profiles.active=$APP_PROFILE"]
EXPOSE 9999

1
adi-chat/README.md Normal file
View File

@ -0,0 +1 @@
User side

64
adi-chat/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.moyz</groupId>
<artifactId>aideepin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>adi-chat</artifactId>
<dependencies>
<dependency>
<groupId>com.moyz</groupId>
<artifactId>adi-bootstrap</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.moyz</groupId>
<artifactId>adi-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 引入本地lib包 -->
<dependency>
<groupId>com.ramostear</groupId>
<artifactId>Happy-Captcha</artifactId>
<scope>system</scope>
<version>1.0.1</version>
<systemPath>${project.basedir}/src/lib/Happy-Captcha-1.0.1.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.moyz.adi.BootstrapApplication</mainClass>
<includeSystemScope>true</includeSystemScope>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Binary file not shown.

View File

@ -0,0 +1,64 @@
package com.moyz.adi.chat.controller;
import com.google.common.collect.Maps;
import com.moyz.adi.common.dto.*;
import com.moyz.adi.common.service.AiImageService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.hibernate.validator.constraints.Length;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/ai-image")
@Validated
public class AiImageController {
@Resource
private AiImageService imageService;
@PostMapping("/generation")
public Map<String, String> generation(@RequestBody @Validated GenerateImageReq generateImageReq) {
String uuid = imageService.createByPrompt(generateImageReq);
return Map.of("uuid", uuid);
}
@PostMapping("/regenerate/{uuid}")
public void regenerate(@PathVariable @Length(min = 32, max = 32) String uuid) {
imageService.regenerate(uuid);
}
@Operation(summary = "Edit image")
@PostMapping("/edit")
public Map<String, String> edit(@RequestBody EditImageReq editImageReq) {
String uuid = imageService.editByOriginalImage(editImageReq);
return Map.of("uuid", uuid);
}
@Operation(summary = "Image variation")
@PostMapping("/variation")
public Map<String, String> variation(@RequestBody VariationImageReq variationImageReq) {
String uuid = imageService.variationImage(variationImageReq);
return Map.of("uuid", uuid);
}
@GetMapping("/list")
public AiImagesListResp list(@RequestParam Long maxId, @RequestParam int pageSize) {
return imageService.listAll(maxId, pageSize);
}
@GetMapping("/detail/{uuid}")
public AiImageDto getOne(@PathVariable String uuid) {
return imageService.getOne(uuid);
}
@GetMapping("/del/{uuid}")
public boolean del(@PathVariable String uuid) {
return imageService.del(uuid);
}
}

View File

@ -0,0 +1,126 @@
package com.moyz.adi.chat.controller;
import com.moyz.adi.common.dto.LoginReq;
import com.moyz.adi.common.dto.LoginResp;
import com.moyz.adi.common.dto.RegisterReq;
import com.moyz.adi.common.service.UserService;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.support.CaptchaType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.URLEncoder;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
@Slf4j
@Tag(name = "权限controller", description = "权限controller")
@Validated
@RestController
@RequestMapping("auth")
public class AuthController {
@Value("${adi.frontend-url}")
private String frontendUrl;
@Resource
private UserService userService;
@Operation(summary = "注册")
@PostMapping(value = "/register", produces = MediaType.TEXT_PLAIN_VALUE)
public String register(@RequestBody RegisterReq registerReq) {
userService.register(registerReq.getEmail(), registerReq.getPassword(), registerReq.getCaptchaId(), registerReq.getCaptchaCode());
return "激活链接已经发送到邮箱,请登录邮箱进行激活";
}
@Operation(summary = "注册的验证码")
@GetMapping("/register/captcha")
public void registerCaptcha(@Parameter(description = "验证码ID") @RequestParam @Length(min = 32) String captchaId,
HttpServletRequest request,
HttpServletResponse response) {
HappyCaptcha happyCaptcha = HappyCaptcha.require(request, response).type(CaptchaType.WORD_NUMBER_UPPER).build().finish();
String captchaCode = happyCaptcha.getCode();
userService.cacheRegisterCaptcha(captchaId, captchaCode);
happyCaptcha.output();
}
@Operation(summary = "激活")
@GetMapping("active")
public boolean active(@RequestParam("code") String activeCode, HttpServletResponse response) {
try {
userService.active(activeCode);
response.sendRedirect(frontendUrl + "/#/active?active=success&msg=" + URLEncoder.encode("激活成功,请登录"));
} catch (IOException e) {
log.error("auth.active:", e);
try {
response.sendRedirect(frontendUrl + "/#/active?active=fail&msg=" + URLEncoder.encode("激活失败:系统错误,请重新注册或者登录"));
} catch (IOException ex) {
log.error("auth.active:", ex);
throw new RuntimeException(ex);
}
} catch (Exception e) {
try {
response.sendRedirect(frontendUrl + "/#/active?active=fail&msg=" + URLEncoder.encode(e.getMessage()));
} catch (IOException ex) {
log.error("auth.active:", ex);
throw new RuntimeException(ex);
}
}
return true;
}
@Operation(summary = "忘记密码")
@PostMapping("password/forgot")
public String forgotPassword(@RequestParam @NotBlank String email) {
userService.forgotPassword(email);
return "重置密码链接已发送";
}
@Operation(summary = "重置密码")
@GetMapping("/password/reset")
public void resetPassword(@RequestParam @NotBlank String code, HttpServletResponse response) {
userService.resetPassword(code);
try {
response.sendRedirect(frontendUrl + "/#/active?active=success&msg=" + URLEncoder.encode("密码已经重置"));
} catch (IOException e) {
log.error("resetPassword:", e);
throw new RuntimeException(e);
}
}
@Operation(summary = "登录")
@PostMapping("login")
public LoginResp login(@Validated @RequestBody LoginReq loginReq, HttpServletResponse response) {
LoginResp loginResp = userService.login(loginReq);
response.setHeader(AUTHORIZATION, loginResp.getToken());
Cookie cookie = new Cookie(AUTHORIZATION, loginResp.getToken());
response.addCookie(cookie);
return loginResp;
}
@Operation(summary = "获取登录验证码")
@GetMapping("/login/captcha")
public void captcha(@RequestParam @Length(min = 32) String captchaId, HttpServletRequest request, HttpServletResponse response) {
HappyCaptcha happyCaptcha = HappyCaptcha.require(request, response).type(CaptchaType.WORD_NUMBER_UPPER).build().finish();
String captchaCode = happyCaptcha.getCode();
userService.cacheLoginCaptcha(captchaId, captchaCode);
happyCaptcha.output();
}
}

View File

@ -0,0 +1,54 @@
package com.moyz.adi.chat.controller;
import com.moyz.adi.common.dto.ConvDto;
import com.moyz.adi.common.dto.ConvEditReq;
import com.moyz.adi.common.service.ConversationService;
import com.moyz.adi.common.dto.ConvMsgListResp;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 对话controller
*/
@Tag(name = "对话controller", description = "对话controller")
@RequestMapping("/conversation")
@RestController
public class ConversationController {
@Resource
private ConversationService conversationService;
@Operation(summary = "获取当前用户所有的对话")
@GetMapping("/list")
public List<ConvDto> list() {
return conversationService.listByUser();
}
@Operation(summary = "查询某个对话的信息列表")
@GetMapping("/{uuid}")
public ConvMsgListResp detail(
@Parameter(name = "对话uuid") @PathVariable @NotBlank(message = "对话uuid不能为空") String uuid
, @Parameter(name = "最大uuid") @RequestParam String maxMsgUuid
, @Parameter(name = "每页数量") @RequestParam @Min(1) @Max(100) int pageSize) {
return conversationService.detail(uuid, maxMsgUuid, pageSize);
}
@PostMapping("/edit/{uuid}")
public boolean edit(@PathVariable String uuid, @RequestBody ConvEditReq convEditReq) {
return conversationService.edit(uuid, convEditReq);
}
@PostMapping("/del/{uuid}")
public boolean softDel(@PathVariable String uuid) {
return conversationService.softDel(uuid);
}
}

View File

@ -0,0 +1,31 @@
package com.moyz.adi.chat.controller;
import com.moyz.adi.common.dto.AskReq;
import com.moyz.adi.common.service.ConversationMessageService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@RestController
@RequestMapping("/conversation/message")
@Validated
public class ConversationMessageController {
@Resource
private ConversationMessageService conversationMessageService;
@Operation(summary = "发送一个prompt给模型")
@PostMapping(value = "/process", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter ask(@RequestBody @Validated AskReq askReq) {
return conversationMessageService.sseAsk(askReq);
}
@PostMapping("/del/{uuid}")
public boolean softDelete(@PathVariable String uuid) {
return conversationMessageService.softDelete(uuid);
}
}

View File

@ -0,0 +1,49 @@
package com.moyz.adi.chat.controller;
import com.moyz.adi.common.service.FileService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@Validated
public class FileController {
@Resource
private FileService fileService;
@GetMapping(value = "/image/{uuid}", produces = MediaType.IMAGE_PNG_VALUE)
public void image(@Length(min = 32, max = 32) @PathVariable String uuid, HttpServletResponse response) {
BufferedImage bufferedImage = fileService.readBufferedImage(uuid);
//把图片写给浏览器
try {
ImageIO.write(bufferedImage, "png", response.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@PostMapping(path = "/file/upload", headers = "content-type=multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, String> upload(@RequestPart(value = "file") MultipartFile file) {
Map<String, String> result = new HashMap<>();
result.put("uuid", fileService.writeToLocal(file));
return result;
}
@PostMapping("/file/del/{uuid}")
public boolean del(@PathVariable String uuid) {
return fileService.softDel(uuid);
}
}

View File

@ -0,0 +1,75 @@
package com.moyz.adi.chat.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.moyz.adi.common.base.ThreadContext;
import com.moyz.adi.common.dto.*;
import com.moyz.adi.common.entity.Prompt;
import com.moyz.adi.common.service.PromptService;
import com.moyz.adi.common.util.MPPageUtil;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/prompt")
@Validated
public class PromptController {
@Resource
private PromptService promptService;
@Operation(summary = "查询列表")
@GetMapping(value = "/my/all")
public List<PromptDto> myAll() {
return promptService.getAll(ThreadContext.getCurrentUserId());
}
@Operation(summary = "查询列表")
@GetMapping(value = "/my/listByUpdateTime")
public PromptListResp list(@RequestParam(required = false) LocalDateTime minUpdateTime) {
return promptService.listByMinUpdateTime(minUpdateTime);
}
@Operation(summary = "搜索列表")
@GetMapping(value = "/my/search")
public Page<PromptDto> search(String keyword, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
return promptService.search(keyword, currentPage, pageSize);
}
@Operation(summary = "自动填充列表")
@GetMapping(value = "/my/autocomplete")
public List<PromptDto> autocomplete(String keyword) {
return promptService.autocomplete(keyword);
}
@Operation(summary = "保存列表")
@PostMapping(value = "/save")
public Map<String, Long> SavePrompts(@RequestBody PromptsSaveReq savePromptsReq) {
return promptService.savePrompts(savePromptsReq);
}
@Operation(summary = "删除")
@PostMapping(value = "/del/{id}")
public boolean softDelete(@PathVariable Long id) {
return promptService.softDelete(id);
}
@Operation(summary = "编辑")
@PostMapping(value = "/edit/{id}")
public boolean edit(@PathVariable Long id, @RequestBody PromptEditReq promptEditReq) {
return promptService.edit(id, promptEditReq.getTitle(), promptEditReq.getRemark());
}
@Operation(summary = "search")
@GetMapping(value = "/search")
public List<PromptDto> search(@Validated SearchReq searchReq) {
return promptService.search(searchReq.getKeyword());
}
}

View File

@ -0,0 +1,77 @@
package com.moyz.adi.chat.controller;
import com.moyz.adi.common.service.UserService;
import com.moyz.adi.common.base.ThreadContext;
import com.moyz.adi.common.dto.ConfigResp;
import com.moyz.adi.common.dto.ModifyPasswordReq;
import com.moyz.adi.common.dto.UserUpdateReq;
import com.moyz.adi.common.entity.User;
import com.talanlabs.avatargenerator.Avatar;
import com.talanlabs.avatargenerator.cat.CatAvatar;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
@Slf4j
@Tag(name = "用户controller")
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@Operation(summary = "用户信息")
@GetMapping("/{uuid}")
public void login(@Validated @PathVariable String uuid) {
log.info(uuid);
}
@Operation(summary = "配置信息")
@GetMapping("/config")
public ConfigResp configInfo() {
return userService.getConfig();
}
@Operation(summary = "更新信息")
@PostMapping("/edit")
public void update(@Validated UserUpdateReq userUpdateReq) {
userService.updateConfig(userUpdateReq);
}
@Operation(summary = "修改密码")
@PostMapping("/password/modify")
public String modifyPassword(@RequestBody ModifyPasswordReq modifyPasswordReq) {
userService.modifyPassword(modifyPasswordReq.getOldPassword(), modifyPasswordReq.getNewPassword());
return "修改成功";
}
@Operation(summary = "退出")
@PostMapping("/logout")
public void logout() {
userService.logout();
}
@Operation(summary = "头像")
@GetMapping(value = "/avatar", produces = MediaType.IMAGE_JPEG_VALUE)
public void avatar(HttpServletResponse response) {
User user = ThreadContext.getCurrentUser();
Avatar avatar = CatAvatar.newAvatarBuilder().build();
BufferedImage bufferedImage = avatar.create(user.getId());
//把图片写给浏览器
try {
ImageIO.write(bufferedImage, "png", response.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

14
adi-common/pom.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.moyz</groupId>
<artifactId>aideepin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>adi-common</artifactId>
</project>

View File

@ -0,0 +1,44 @@
package com.moyz.adi.common;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.sql.Types;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true", "root", "123456")
.globalConfig(builder -> {
builder.author("moyz") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
}))
.packageConfig(builder -> {
builder.mapper("com.adi.common.mapper")
.parent("")
.moduleName("")
.entity("po")
.serviceImpl("service.impl")
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatisplus-generatorcode")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("adi_user,adi_conversation,adi_conversation_message") // 设置需要生成的表名
.addTablePrefix("adi_");
builder.mapperBuilder().enableBaseResultMap().enableMapperAnnotation().build();
})
.execute();
}
}

View File

@ -0,0 +1,27 @@
package com.moyz.adi.common.annotation;
import com.moyz.adi.common.validator.AskReqValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Constraint(validatedBy = {
AskReqValidator.class,
})
@Target({TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
public @interface AskReqCheck {
String message() default "dddd";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,27 @@
package com.moyz.adi.common.annotation;
import com.moyz.adi.common.validator.CreateImageReqValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Constraint(validatedBy = {
CreateImageReqValidator.class,
})
@Target({TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
public @interface CreateImageReqCheck {
String message() default "dddd";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,46 @@
package com.moyz.adi.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 分布式锁注解
*
* @author moyz
* date:2021-07-15
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributeLock {
/**
* redis key
*
* @return
*/
String redisKey() default "";
/**
* clientId标识用来加锁的客户端
*
* @return
*/
String clientId() default "";
/**
* 失效时间
*
* @return
*/
int expireInSeconds() default 0;
/**
* 如果获取锁失败是否继续执行
*
* @return
*/
boolean continueIfAcquireFail() default true;
}

View File

@ -0,0 +1,27 @@
package com.moyz.adi.common.annotation;
import com.moyz.adi.common.validator.AskReqValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Constraint(validatedBy = {
AskReqValidator.class,
})
@Target({TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
public @interface NotAllFieldsEmptyCheck {
String message() default "all filed is null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,17 @@
package com.moyz.adi.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 参数打印注解
*
* @author moyz
* date:2021-07-15
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ParamsLog {
}

View File

@ -0,0 +1,33 @@
package com.moyz.adi.common.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 打印controller的请求参数
*
* @author moyz
* date:2021-07-15 03:16:59
*/
@Aspect
@Component
public class ControllerParamsLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ControllerParamsLogAspect.class);
@Pointcut("execution(public * com.adi.*.controller..*.*(..))")
public void controllerMethods() {
}
@Before("controllerMethods()")
public void before(JoinPoint joinPoint) {
ParamsLogAspect.paramsLog(joinPoint, logger);
}
}

View File

@ -0,0 +1,75 @@
package com.moyz.adi.common.aop;
import com.moyz.adi.common.annotation.DistributeLock;
import com.moyz.adi.common.util.RedisTemplateUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* 通用分页式锁
*
* @author moyz
*/
@Slf4j
@Aspect
@Component
public class DistributeLockAspect {
@Resource
private RedisTemplateUtil redisTemplateUtil;
@Around("@annotation(distributeLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributeLock distributeLock) throws Throwable {
String key = distributeLock.redisKey();
int expireInSeconds = distributeLock.expireInSeconds();
boolean continueIfAcquireFail = distributeLock.continueIfAcquireFail();
String clientId = distributeLock.clientId();
boolean lockAndContinue = checkAndLock(key, clientId, expireInSeconds, continueIfAcquireFail, redisTemplateUtil);
if (!lockAndContinue) {
log.warn("该次请求忽略");
return false;
}
try {
return joinPoint.proceed();
} finally {
boolean unlockResult = redisTemplateUtil.unlock(key, clientId);
log.info("unlock:{},key:{},clientId:{}", unlockResult, key, clientId);
}
}
/**
* 校验参数及加锁如果没有加锁方标识clientId则自动生成uuid做为clientId
*
* @param key
* @param clientId 加锁方标识
* @param expireInSeconds 超时时间
* @param continueIfAcquireFail 获取锁失败是否继续执行后面的业务逻辑
* @param redisTemplateUtil redis工具类
* @return
* @throws Exception
*/
public static boolean checkAndLock(String key, String clientId, int expireInSeconds, boolean continueIfAcquireFail, RedisTemplateUtil redisTemplateUtil) throws Exception {
log.info("lock info,key:{},clientId:{},expireInSecond:{},continueIfAcquireFail:{}", key, clientId, expireInSeconds, continueIfAcquireFail);
if (StringUtils.isBlank(key) || expireInSeconds < 1) {
log.warn("加锁参数有误,请确认后再操作");
throw new Exception("加锁参数有误,请确认后再操作");
}
if (StringUtils.isBlank(clientId)) {
clientId = UUID.randomUUID().toString().replace("-", "");
}
boolean lock = redisTemplateUtil.lock(key, clientId, expireInSeconds);
if (!lock && !continueIfAcquireFail) {
log.warn("由于参数continueIfAcquireFail为false并且获取锁失败此次请求忽略");
return false;
}
return lock;
}
}

View File

@ -0,0 +1,60 @@
package com.moyz.adi.common.aop;
import com.moyz.adi.common.annotation.ParamsLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* @author myz
*/
@Slf4j
@Aspect
@Component
public class ParamsLogAspect {
@Before(value = "@annotation(paramsLog)")
public void before(JoinPoint joinPoint, ParamsLog paramsLog) {
paramsLog(joinPoint, log);
}
/**
* 输出方法参数到日志
*
* @param joinPoint joinPoint
* @param logger 日志
*/
static void paramsLog(JoinPoint joinPoint, Logger logger) {
String className = joinPoint.getSignature().getDeclaringType().getName();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Object[] args = joinPoint.getArgs();
Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
StringBuilder sb = new StringBuilder();
sb.append(className);
sb.append(".");
sb.append(method.getName());
sb.append(" params:[");
for (int i = 0; i < args.length; i++) {
String paramName = parameters[i].getName();
sb.append(parameters[i].getName());
sb.append("=>");
if ("password".equals(paramName)) {
sb.append("***");
} else {
sb.append(args[i]);
}
sb.append(";");
}
sb.append("]");
String log = sb.toString();
logger.info(StringUtils.substring(log, 0, 1000));
}
}

View File

@ -0,0 +1,51 @@
package com.moyz.adi.common.base;
import com.moyz.adi.common.enums.ErrorEnum;
import lombok.Data;
import java.io.Serializable;
@Data
public class BaseResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否成功
*/
private boolean success;
/**
* 状态码
*/
private String code;
/**
* 提示
*/
private String message;
/**
* 数据
*/
private T data;
public BaseResponse() {
}
public BaseResponse(boolean success) {
this.success = success;
}
public BaseResponse(boolean success, T data) {
this.data = data;
this.success = success;
}
public BaseResponse(String code, String message, T data) {
this.code = code;
this.success = false;
this.message = message;
this.data = data;
}
public static BaseResponse success(String message){
return new BaseResponse(ErrorEnum.SUCCESS.getCode(), message, "");
}
}

View File

@ -0,0 +1,35 @@
package com.moyz.adi.common.base;
import com.moyz.adi.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@Slf4j
@RestControllerAdvice(basePackages = {"com.moyz.adi"})
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object result, MethodParameter methodParameter,
MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
if (result instanceof BaseResponse) {
return result;
} else if (result instanceof String) {
return JsonUtil.toJson(new BaseResponse(true, result));
}
log.info("result:" + result);
return new BaseResponse(true, result);
}
}

View File

@ -0,0 +1,38 @@
package com.moyz.adi.common.base;
import com.moyz.adi.common.entity.User;
public class ThreadContext {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
private static final ThreadLocal<String> currentToken = new ThreadLocal<>();
public static void setCurrentUser(User user) {
currentUser.set(user);
}
public static User getCurrentUser() {
return currentUser.get();
}
public static Long getCurrentUserId() {
return currentUser.get().getId();
}
public static void setToken(String token) {
currentToken.set(token);
}
public static String getToken() {
return currentToken.get();
}
public static User getExistCurrentUser() {
User user = ThreadContext.getCurrentUser();
if (null == user) {
throw new RuntimeException("用户不存在");
}
return user;
}
}

View File

@ -0,0 +1,100 @@
package com.moyz.adi.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.moyz.adi.common.util.LocalDateTimeUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;
import javax.sql.DataSource;
@Slf4j
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
log.info("Configuration==create restTemplate");
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
// 设置建立连接超时时间 毫秒
requestFactory.setConnectTimeout(60000);
// 设置读取数据超时时间 毫秒
requestFactory.setReadTimeout(60000);
RestTemplate restTemplate = new RestTemplate();
// 注册LOG拦截器
restTemplate.setInterceptors(Lists.newArrayList(new LogClientHttpRequestInterceptor()));
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
return restTemplate;
}
@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
log.info("Configuration==create objectMapper");
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.registerModules(LocalDateTimeUtil.getSimpleModule(), new JavaTimeModule(), new Jdk8Module());
//设置null值不参与序列化(字段不被显示)
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
@Bean(name = "mainExecutor")
@Primary
public AsyncTaskExecutor mainExecutor() {
int processorsNum = Runtime.getRuntime().availableProcessors();
log.info("mainExecutor,processorsNum:{}", processorsNum);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(processorsNum * 2);
executor.setMaxPoolSize(100);
return executor;
}
@Bean(name = "imagesExecutor")
public AsyncTaskExecutor imagesExecutor() {
int processorsNum = Runtime.getRuntime().availableProcessors();
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
log.info("imagesExecutor corePoolSize:{},maxPoolSize:{}", processorsNum, processorsNum * 2);
executor.setCorePoolSize(processorsNum);
executor.setMaxPoolSize(processorsNum * 2);
return executor;
}
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(DataSource dataSource)
throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 防止全表更新
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
bean.setPlugins(interceptor);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
return bean.getObject();
}
}

View File

@ -0,0 +1,53 @@
package com.moyz.adi.common.config;
import com.moyz.adi.common.base.BaseResponse;
import com.moyz.adi.common.enums.ErrorEnum;
import com.moyz.adi.common.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数校验异常
*
* @return BaseResponse
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
private BaseResponse handleMethodArgumentNotValidException(
final MethodArgumentNotValidException exception) {
Map<Object, Object> error = wrapperError(exception.getBindingResult());
log.error("参数校验异常:{}", error);
return new BaseResponse(ErrorEnum.A_PARAMS_ERROR.getCode(), ErrorEnum.A_PARAMS_ERROR.getInfo(), error);
}
@ExceptionHandler(BaseException.class)
private BaseResponse handleBaseException(final BaseException exception) {
log.error("拦截业务异常:{}", exception);
return new BaseResponse(exception.getCode(), exception.getInfo(), exception.getData());
}
/**
* 兜底
*
* @return BaseResponse
*/
@ExceptionHandler(Exception.class)
private BaseResponse handleException(final Exception exception) {
log.error("拦截全局异常:", exception);
return new BaseResponse(ErrorEnum.B_GLOBAL_ERROR.getCode(), ErrorEnum.B_GLOBAL_ERROR.getInfo(), exception.getMessage());
}
private Map<Object, Object> wrapperError(BindingResult result) {
Map<Object, Object> errorMap = new HashMap<>(5);
result.getFieldErrors().forEach(x -> errorMap.put(x.getField(), x.getDefaultMessage()));
return errorMap;
}
}

View File

@ -0,0 +1,49 @@
package com.moyz.adi.common.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StopWatch;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@Slf4j
public class LogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ClientHttpResponse response = execution.execute(request, body);
stopWatch.stop();
StringBuilder resBody = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
resBody.append(line);
line = bufferedReader.readLine();
}
}
if (request.getHeaders().getContentType() != null && request.getHeaders().getContentType()
.includes(MediaType.MULTIPART_FORM_DATA)) {
body = new byte[]{};
}
log.info("rest log status:{},time:{},url:{},body:{},response:{}",
response.getRawStatusCode(), stopWatch.getLastTaskTimeMillis(),
request.getURI(), new String(body, StandardCharsets.UTF_8), resBody);
return response;
}
}

View File

@ -0,0 +1,55 @@
package com.moyz.adi.common.config;
import com.moyz.adi.common.helper.HttpHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
//@Service
public class LogInterceptor implements HandlerInterceptor {
@Resource
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
if (HttpMethod.GET.matches(request.getMethod())) {
log.info("url:{},ip:{},method:{},param:{}", request.getRequestURL(),
request.getRemoteAddr(), method.getMethod().getName(),
objectMapper.writeValueAsString(request.getParameterMap()));
} else {
String bodyString = HttpHelper.getBodyString(request);
log.info("url:{},ip:{},method{},param:{},body:{}", request.getRequestURL(),
request.getRemoteAddr(), method.getMethod().getName(),
objectMapper.writeValueAsString(request.getParameterMap()), bodyString);
}
} else {
log.info("url:{},ip:{}", request.getRequestURL(),
request.getRemoteAddr());
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}

View File

@ -0,0 +1,15 @@
package com.moyz.adi.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("openai")
@Data
public class OpenAiProperties {
private boolean proxyEnable;
}

View File

@ -0,0 +1,59 @@
package com.moyz.adi.common.config;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 缓存请求的输入流以便重复使用
*/
public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream newInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return newInputStream.read();
}
@Override
public boolean isFinished() {
return newInputStream.available() == 0;
}
@Override
public boolean isReady() {
return newInputStream.available() > 0;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}

View File

@ -0,0 +1,8 @@
package com.moyz.adi.common.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringdocConfig {
}

View File

@ -0,0 +1,41 @@
package com.moyz.adi.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private ObjectMapper objectMapper;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("WebMvcConfig==configureMessageConverters");
WebMvcConfigurer.super.configureMessageConverters(converters);
converters.add(new StringHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new ByteArrayHttpMessageConverter());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//使用ParamsLogAspect代替
// log.info("WebMvcConfig==addInterceptors");
// registry.addInterceptor(logInterceptor)
// .addPathPatterns("/**")
// .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v3/**", "/swagger-ui.html");
}
}

View File

@ -0,0 +1,81 @@
package com.moyz.adi.common.cosntant;
import java.util.List;
public class AdiConstant {
public static final int DEFAULT_PAGE_SIZE = 1;
/**
* 验证码id过期时间1小时
*/
public static final int AUTH_CAPTCHA_ID_EXPIRE = 1;
/**
* 验证码过期时间5分钟
*/
public static final int AUTH_CAPTCHA_EXPIRE = 5;
/**
* 注册激活码有效时长8小时
*/
public static final int AUTH_ACTIVE_CODE_EXPIRE = 8;
/**
* token存活时间8小时
*/
public static final int USER_TOKEN_EXPIRE = 8;
public static final String DEFAULT_PASSWORD = "123456";
public static final int LOGIN_MAX_FAIL_TIMES = 3;
public static final String[] WEB_RESOURCES = {
"/swagger-ui/index.html",
"/swagger-ui",
"/swagger-resources",
"/v3/api-docs",
"/favicon.ico",
".css",
".js",
"/doc.html"
};
public static final int SECRET_KEY_TYPE_SYSTEM = 1;
public static final int SECRET_KEY_TYPE_CUSTOM = 2;
public static final String OPENAI_MESSAGE_DONE_FLAG = "[DONE]";
public static final String DEFAULT_MODEL = "gpt-3.5-turbo";
public static final String CREATE_IMAGE_RESP_FORMATS_B64JSON = "b64_json";
public static final String OPENAI_CREATE_IMAGE_RESP_FORMATS_URL = "url";
public static final List<String> OPENAI_CREATE_IMAGE_SIZES = List.of("256x256", "512x512", "1024x1024");
public static class GenerateImage{
public static final int INTERACTING_METHOD_GENERATE_IMAGE = 1;
public static final int INTERACTING_METHOD_EDIT_IMAGE = 2;
public static final int INTERACTING_METHOD_VARIATION = 3;
public static final int STATUS_DOING = 1;
public static final int STATUS_FAIL = 2;
public static final int STATUS_SUCCESS = 3;
}
public static class SysConfigKey {
public static final String SECRET_KEY = "secret_key";
public static final String REQUEST_TEXT_RATE_LIMIT = "request_text_rate_limit";
public static final String REQUEST_IMAGE_RATE_LIMIT = "request_image_rate_limit";
public static final String CONVERSATION_MAX_NUM = "conversation_max_num";
public static final String QUOTA_BY_TOKEN_DAILY = "quota_by_token_daily";
public static final String QUOTA_BY_TOKEN_MONTHLY = "quota_by_token_monthly";
public static final String QUOTA_BY_REQUEST_DAILY = "quota_by_request_daily";
public static final String QUOTA_BY_REQUEST_MONTHLY = "quota_by_request_monthly";
public static final String QUOTA_BY_IMAGE_DAILY = "quota_by_image_daily";
public static final String QUOTA_BY_IMAGE_MONTHLY = "quota_by_image_monthly";
}
}

View File

@ -0,0 +1,81 @@
package com.moyz.adi.common.cosntant;
public class RedisKeyConstant {
/**
* 账号激活码的key
*/
public static final String AUTH_ACTIVE_CODE = "auth:activeCode:{0}";
/**
* 注册时使用的验证码
* 参数验证码id
* 验证码
*/
public static final String AUTH_REGISTER_CAPTCHA_ID = "auth:register:captcha:{0}";
/**
* 登录时使用的验证码id缓存
* 参数验证码id
* 验证码
*/
public static final String AUTH_LOGIN_CAPTCHA_ID = "auth:login:captcha:{0}";
/**
* 注册验证码缓存
* 参数验证码
* 1
*/
public static final String AUTH_CAPTCHA = "auth:register:captcha:{0}";
/**
* 登录token
* {0}:用户token
* json.format(user)
*/
public static final String USER_TOKEN = "user:token:{0}";
/**
* 参数游客的uuid
* json.format(guest)
*/
public static final String GUEST_UUID = "guest:uuid:{0}";
/**
* 登录失败次数
* 参数用户邮箱
* : 失效次数
*/
public static final String LOGIN_FAIL_COUNT = "user:login:fail:{0}";
/**
* 用户是否请求ai中
* 参数用户id
* : 1或者0
*/
public static final String USER_ASKING = "user:asking:{0}";
/**
* 用户是否画画中
* 参数用户id
* : 1或者0
*/
public static final String USER_DRAWING = "user:drawing:{0}";
/**
* 用户提问限流计数
* 参数用户id
* : 当前时间窗口访问量
*/
public static final String USER_REQUEST_TEXT_TIMES = "user:request-text:times:{0}";
public static final String USER_REQUEST_IMAGE_TIMES = "user:request-image:times:{0}";
/**
* 找回密码的请求绑在
* 参数随机数
* : 用户id用于校验后续流程中的重置密码使用
*/
public static final String FIND_MY_PASSWORD = "user:find:password:{0}";
}

View File

@ -0,0 +1,29 @@
package com.moyz.adi.common.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class AiImageDto {
private Long id;
private String uuid;
private String prompt;
private String originalImageUrl;
private String maskImageUrl;
private Integer interactingMethod;
@JsonIgnore
private String generatedImages;
/**
* http url
*/
private List<String> imageUrlList;
private Integer processStatus;
private LocalDateTime createTime;
}

View File

@ -0,0 +1,11 @@
package com.moyz.adi.common.dto;
import lombok.Data;
import java.util.List;
@Data
public class AiImagesListResp {
private Long minId;
private List<AiImageDto> imageItems;
}

View File

@ -0,0 +1,24 @@
package com.moyz.adi.common.dto;
import com.moyz.adi.common.annotation.AskReqCheck;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Schema(description = "对话的请求对象")
@Data
@AskReqCheck
public class AskReq {
@Length(min = 32, max = 32)
private String conversationUuid;
private String parentMessageId;
private String prompt;
/**
* If not empty, it means will request AI with the exist prompt, param {@code prompt} is ignored
*/
private String regenerateQuestionUuid;
}

View File

@ -0,0 +1,22 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class ConfigResp {
private String secretKey;
private Boolean contextEnable;
private Integer contextMsgPairNum;
private Integer quotaByTokenDaily;
private Integer quotaByTokenMonthly;
private Integer quotaByRequestDaily;
private Integer quotaByRequestMonthly;
private Integer quotaByImageDaily;
private Integer quotaByImageMonthly;
private Integer todayTokenCost;
private Integer todayRequestTimes;
private Integer todayGeneratedImageNumber;
private Integer currMonthTokenCost;
private Integer currMonthRequestTimes;
private Integer currMonthGeneratedImageNumber;
}

View File

@ -0,0 +1,21 @@
package com.moyz.adi.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class ConvDto {
private String uuid;
@NotBlank
private String title;
private Integer tokens;
@Schema(title = "set the system message to ai, ig: you are a lawyer")
private String aiSystemMessage;
private Boolean understandContextEnable;
}

View File

@ -0,0 +1,17 @@
package com.moyz.adi.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class ConvEditReq {
@NotBlank
private String title;
@Schema(title = "set the system message to ai, ig: you are a lawyer")
private String aiSystemMessage;
private Boolean understandContextEnable;
}

View File

@ -0,0 +1,15 @@
package com.moyz.adi.common.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class ConvMsgListResp {
private String minMsgUuid;
private List<ConvMsgResp> msgList;
}

View File

@ -0,0 +1,38 @@
package com.moyz.adi.common.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class ConvMsgResp {
@JsonIgnore
private Long id;
@Schema(title = "消息的uuid")
private String uuid;
@Schema(title = "父级消息id")
private Long parentMessageId;
@Schema(title = "对话的消息")
@TableField("content")
private String content;
@Schema(title = "产生该消息的角色1: 用户,2:系统,3:助手")
private String messageRole;
@Schema(title = "消耗的token数量")
private Integer tokens;
@Schema(title = "创建时间")
private LocalDateTime createTime;
@Schema(title = "子级消息一般指的是AI的响应")
private List<ConvMsgResp> children;
}

View File

@ -0,0 +1,13 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class CreateImageDto {
private String prompt;
private String size;
private int number;
private int interactingMethod;
private String originalImage;
private String maskImage;
}

View File

@ -0,0 +1,22 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
public class EditImageReq {
@Length(min = 32, max = 32)
private String originalImage;
@Length(min = 32, max = 32)
private String maskImage;
@NotBlank
private String prompt;
@NotBlank
private String size;
@Min(1)
@Max(10)
private int number;
}

View File

@ -0,0 +1,17 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class GenerateImageReq {
@NotBlank
private String prompt;
@NotBlank
private String size;
@Min(1)
@Max(10)
private int number;
}

View File

@ -0,0 +1,8 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class ImagesReq {
private Integer generateStatus;
}

View File

@ -0,0 +1,20 @@
package com.moyz.adi.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(name = "登录请求参数")
@Data
public class LoginReq {
@NotBlank(message = "邮箱不能为空")
String email;
@NotBlank(message = "密码不能为空")
String password;
String captchaId;
String captchaCode;
}

View File

@ -0,0 +1,13 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class LoginResp {
private String token;
private String name;
private String email;
private String activeTime;
private String captchaId;
}

View File

@ -0,0 +1,19 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
@Data
@Validated
public class ModifyPasswordReq {
@NotBlank
@Length(min = 6)
private String oldPassword;
@NotBlank
@Length(min = 6)
private String newPassword;
}

View File

@ -0,0 +1,10 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class PromptDto {
private Long id;
private String act;
private String prompt;
}

View File

@ -0,0 +1,16 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.springframework.validation.annotation.Validated;
@Data
@Validated
public class PromptEditReq {
@NotBlank
private String title;
@NotBlank
private String remark;
}

View File

@ -0,0 +1,11 @@
package com.moyz.adi.common.dto;
import lombok.Data;
import java.util.List;
@Data
public class PromptListResp {
private String maxUpdateTime;
private List<PromptDto> prompts;
}

View File

@ -0,0 +1,15 @@
package com.moyz.adi.common.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@Data
@Validated
public class PromptsSaveReq {
@Length(min = 1)
private List<PromptDto> prompts;
}

View File

@ -0,0 +1,11 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class RegenerateImageReq {
private String uuid;
}

View File

@ -0,0 +1,31 @@
package com.moyz.adi.common.dto;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
@Schema(name = "注册请求参数")
@Data
@Validated
public class RegisterReq {
@Parameter(description = "邮箱")
@Email
private String email;
@Parameter(description = "密码")
@Min(6)
private String password;
@Parameter(description = "验证码ID")
@Length(min = 32)
private String captchaId;
@Parameter(description = "验证码")
@Length(min = 4, max = 4)
private String captchaCode;
}

View File

@ -0,0 +1,11 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class SearchReq {
@NotBlank
private String keyword;
}

View File

@ -0,0 +1,10 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class SessionResp {
private Boolean auth;
private String model;
}

View File

@ -0,0 +1,12 @@
package com.moyz.adi.common.dto;
import com.moyz.adi.common.annotation.NotAllFieldsEmptyCheck;
import lombok.Data;
@Data
@NotAllFieldsEmptyCheck
public class UserUpdateReq {
private String secretKey;
private Boolean contextEnable;
private Integer contextMsgPairNum;
}

View File

@ -0,0 +1,18 @@
package com.moyz.adi.common.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
public class VariationImageReq {
@Length(min = 32, max = 32)
private String originalImage;
@NotBlank
private String size;
@Min(1)
@Max(10)
private int number;
}

View File

@ -0,0 +1,7 @@
package com.moyz.adi.common.dto;
import lombok.Data;
@Data
public class VerifyResp {
}

View File

@ -0,0 +1,43 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_file")
@Schema(title = "文件表")
public class AdiFile extends BaseEntity {
@Schema(title = "用户id")
@TableField(value = "user_id")
private Long userId;
@Schema(title = "name")
@TableField(value = "name")
private String name;
@Schema(title = "uuid")
@TableField(value = "uuid")
private String uuid;
@Schema(title = "md5")
@TableField(value = "md5")
private String md5;
@Schema(title = "file extension")
@TableField(value = "ext")
private String ext;
@Schema(title = "路径")
@TableField(value = "path")
private String path;
@Schema(title = "引用数量")
@TableField(value = "ref_count")
private Integer refCount;
@Schema(title = "是否删除0未删除1已删除")
@TableField(value = "is_delete")
private Boolean isDelete;
}

View File

@ -0,0 +1,51 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_ai_image")
@Schema(title = "ai images", description = "Image generated by ai")
public class AiImage extends BaseEntity {
@TableField("user_id")
private Long userId;
@TableField("uuid")
private String uuid;
@TableField("prompt")
private String prompt;
@TableField("generate_size")
private String generateSize;
@TableField("generate_number")
private Integer generateNumber;
@Schema(title = "file uuid")
@TableField("original_image")
private String originalImage;
@Schema(title = "file uuid")
@TableField("mask_image")
private String maskImage;
@TableField("resp_images_path")
private String respImagesPath;
@Schema(title = "generated image uuids")
@TableField("generated_images")
private String generatedImages;
@TableField("interacting_method")
private Integer interactingMethod;
@TableField("process_status")
private Integer processStatus;
@TableField("is_delete")
private Boolean isDelete;
}

View File

@ -0,0 +1,25 @@
package com.moyz.adi.common.entity;
import com.moyz.adi.common.enums.AiModelStatus;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_ai_model")
@Schema(title = "AiModel对象", description = "AI模型表")
public class AiModel extends BaseEntity {
@Schema(title = "模型名称")
@TableField("name")
private String name;
@Schema(title = "说明")
@TableField("remark")
private String remark;
@Schema(title = "状态(1:正常使用,2:不可用)")
@TableField("model_status")
private AiModelStatus modelStatus;
}

View File

@ -0,0 +1,29 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
@Getter
@Setter
@ToString
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "create_time")
private LocalDateTime createTime;
@TableField(value = "update_time")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,51 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* <p>
* 会话表
* </p>
*
* @author moyz
* @since 2023-04-11
*/
@Data
@TableName("adi_conversation")
@Schema(title = "对话实体", description = "对话表")
public class Conversation extends BaseEntity {
@Schema(title = "用户id")
@TableField("user_id")
private Long userId;
@Schema(title = "对话uuid")
@TableField("uuid")
private String uuid;
@Schema(title = "会话标题")
@TableField("title")
private String title;
@Schema(title = "消耗的token数量")
@TableField("tokens")
private Integer tokens;
@Schema(title = "ai model name")
@TableField("ai_model")
private String aiModel;
@Schema(name = "是否开启理解上下文的功能")
@TableField("understand_context_enable")
private Boolean understandContextEnable;
@Schema(title = "set the system message to ai, ig: you are a lawyer")
@TableField("ai_system_message")
private String aiSystemMessage;
@TableField(value = "is_delete")
private Boolean isDelete;
}

View File

@ -0,0 +1,63 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* <p>
*
* </p>
*
* @author moyz
* @since 2023-04-11
*/
@Data
@TableName("adi_conversation_message")
@Schema(title = "ConversationMessage对象")
public class ConversationMessage extends BaseEntity {
@Schema(title = "消息的uuid")
@TableField("uuid")
private String uuid;
@Schema(title = "父级消息id")
@TableField("parent_message_id")
private Long parentMessageId;
@Schema(title = "对话id")
@TableField("conversation_id")
private Long conversationId;
@Schema(title = "对话uuid")
@TableField("conversation_uuid")
private String conversationUuid;
@Schema(title = "用户id")
@TableField("user_id")
private Long userId;
@Schema(title = "对话的消息")
@TableField("content")
private String content;
@Schema(title = "产生该消息的角色1: 用户,2:系统,3:助手")
@TableField("message_role")
private String messageRole;
@Schema(title = "消耗的token数量")
@TableField("tokens")
private Integer tokens;
@Schema(title = "secret key type(1:system secret key,2:custom secret key)")
@TableField(value = "secret_key_type")
private Integer secretKeyType;
@Schema(name = "上下文理解中携带的消息对数量(提示词及回复)")
@TableField("understand_context_msg_pair_num")
private Integer understandContextMsgPairNum;
@TableField(value = "is_delete")
private Boolean isDelete;
}

View File

@ -0,0 +1,28 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_prompt")
@Schema(title = "提示词实体")
public class Prompt extends BaseEntity {
@Schema(title = "用户id")
@TableField(value = "user_id")
private Long userId;
@Schema(title = "标题")
@TableField(value = "act")
private String act;
@Schema(title = "内容")
@TableField(value = "prompt")
private String prompt;
@Schema(title = "是否删除0未删除1已删除")
@TableField(value = "is_delete")
private Boolean isDelete;
}

View File

@ -0,0 +1,23 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_sys_config")
@Schema(title = "系统配置表")
public class SysConfig extends BaseEntity {
@Schema(title = "配置名称")
@TableField("name")
private String name;
@Schema(title = "配置项的值")
private String value;
@Schema(title = "是否删除0未删除1已删除")
@TableField(value = "is_delete")
private Boolean isDelete;
}

View File

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

View File

@ -0,0 +1,35 @@
package com.moyz.adi.common.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("adi_user_day_cost")
@Schema(title = "用户每天使用量")
public class UserDayCost extends BaseEntity {
@Schema(title = "用户id")
@TableField(value = "user_id")
private Long userId;
@Schema(title = "日期")
@TableField(value = "day")
private Integer day;
@Schema(title = "请求量")
@TableField(value = "requests")
private Integer requests;
@Schema(title = "token数量")
@TableField(value = "tokens")
private Integer tokens;
@Schema(title = "The number of generated images")
@TableField(value = "images_number")
private Integer imagesNumber;
@Schema(title = "secret key type(1:system secret key,2:custom secret key)")
@TableField(value = "secret_key_type")
private Integer secretKeyType;
}

View File

@ -0,0 +1,14 @@
package com.moyz.adi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AiModelStatus implements BaseEnum {
ACTIVE(1, "启用"),
INACTIVE(2, "停用");
private final Integer value;
private final String desc;
}

View File

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

View File

@ -0,0 +1,67 @@
package com.moyz.adi.common.enums;
public enum ErrorEnum {
SUCCESS("00000", "成功"),
A_URL_NOT_FOUND("A0001", "地址不存在"),
A_PARAMS_ERROR("A0002", "参数校验不通过"),
A_REQUEST_TOO_MUCH("A0003", "访问次数太多"),
A_LOGIN_ERROR("A0004", "登陆失败,账号或密码错误"),
A_LOGIN_ERROR_MAX("A0005", "失败次数太多,请输入验证码重试"),
A_LOGIN_CAPTCHA_ERROR("A0006", "验证码不正确"),
A_USER_NOT_EXIST("A0007", "用户不存在"),
A_CONVERSATION_NOT_EXIST("A0008", "对话不存在"),
A_IMAGE_NUMBER_ERROR("A0009", "图片数量不对"),
A_IMAGE_SIZE_ERROR("A0010", "图片尺寸不对"),
A_FILE_NOT_EXIST("A0011", "文件不存在"),
A_DRAWING("A0012", "作图还未完成"),
A_REGISTER_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
B_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
B_COMMON_ERROR("B0002", "业务出错"),
B_GLOBAL_ERROR("B0003", "全局异常"),
B_SAVE_IMAGE_ERROR("B0004", "保存图片异常"),
B_FIND_IMAGE_404("B0005", "无法找到图片"),
B_DAILY_QUOTA_USED("B0006", "今天额度已经用完"),
B_MONTHLY_QUOTA_USED("B0007", "当月额度已经用完"),
B_MESSAGE_NOT_FOUND("B0008", "消息不存在");
private String code;
private String info;
ErrorEnum(String code, String info) {
this.code = code;
this.info = info;
}
public static ErrorEnum getErrorEnum(String code) {
ErrorEnum result = null;
for (ErrorEnum c : ErrorEnum.values()) {
if (c.getCode().equals(code)) {
result = c;
break;
}
}
if (null == result) {
result = B_COMMON_ERROR;
}
return result;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}

View File

@ -0,0 +1,17 @@
package com.moyz.adi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserStatusEnum implements BaseEnum {
WAIT_CONFIRM(1, "待验证"),
NORMAL(2, "正常"),
FREEZE(3, "冻结");
private final Integer value;
private final String desc;
}

View File

@ -0,0 +1,57 @@
package com.moyz.adi.common.exception;
import com.moyz.adi.common.enums.ErrorEnum;
import java.text.MessageFormat;
public class BaseException extends RuntimeException {
private String code;
private String info;
private Object data;
public BaseException(String code, String info) {
super(code + ":" + info);
this.code = code;
this.info = info;
}
public BaseException(ErrorEnum errorEnum, String... infoValues) {
super(errorEnum.getCode() + ":" + MessageFormat.format(errorEnum.getInfo(), infoValues));
this.code = errorEnum.getCode();
if (infoValues.length > 0) {
this.info = MessageFormat.format(errorEnum.getInfo(), infoValues);
} else {
this.info = errorEnum.getInfo();
}
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public Object getData() {
if (null != data) {
return data;
}
return getMessage();
}
public BaseException setData(Object data) {
this.data = data;
return this;
}
}

View File

@ -0,0 +1,33 @@
package com.moyz.adi.common.filter;
import com.moyz.adi.common.config.RequestReaderHttpServletRequestWrapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* 过滤器
* 当前主要是配合LogInterceptor使用已使用ControllerParamsLogAspect代替LogInterceptor
*/
@Slf4j
//@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HttpServletRequestReplacedFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.info("HttpServletRequestReplacedFilter:" + request.getRequestURI());
ServletRequest requestWrapper = new RequestReaderHttpServletRequestWrapper(request);
filterChain.doFilter(requestWrapper, response);
}
}

View File

@ -0,0 +1,95 @@
package com.moyz.adi.common.filter;
import com.moyz.adi.common.base.ThreadContext;
import com.moyz.adi.common.cosntant.AdiConstant;
import com.moyz.adi.common.cosntant.RedisKeyConstant;
import com.moyz.adi.common.entity.User;
import com.moyz.adi.common.util.JsonUtil;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
@Slf4j
@Component
public class TokenFilter extends OncePerRequestFilter {
public static final String[] EXCLUDE_API = {
"/auth/",
};
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.servlet.context-path:}")
private String contextPath;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestUri = request.getRequestURI();
if (excludePath(requestUri)) {
filterChain.doFilter(request, response);
return;
}
if (null == request.getCookies()) {
log.warn("未授权:{}", requestUri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Cookie cookie = Arrays.stream(request.getCookies()).filter(item -> item.getName().equals(AUTHORIZATION)).findFirst().orElse(null);
if (null == cookie || StringUtils.isBlank(cookie.getValue())) {
log.warn("未授权:{}", requestUri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String token = cookie.getValue();
String tokenKey = MessageFormat.format(RedisKeyConstant.USER_TOKEN, token);
String userJson = stringRedisTemplate.opsForValue().get(tokenKey);
if (StringUtils.isBlank(userJson)) {
log.warn("未登录:{}", requestUri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
User user = JsonUtil.fromJson(userJson, User.class);
if (null == user) {
log.warn("用户不存在:{}", requestUri);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
ThreadContext.setCurrentUser(user);
ThreadContext.setToken(token);
log.info("response::" + response);
filterChain.doFilter(request, response);
}
private boolean excludePath(String requestUri) {
for (String path : EXCLUDE_API) {
if (requestUri.startsWith(contextPath + path)) {
// log.info("path exclude{}", requestUri);
return true;
}
}
for (String path : AdiConstant.WEB_RESOURCES) {
if (requestUri.startsWith(contextPath + path) || requestUri.endsWith(path)) {
// log.info("path exclude{}", requestUri);
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,56 @@
package com.moyz.adi.common.helper;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class AdiMailSender {
@Resource
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String from;
public void send(String subject, String content, String to) {
log.info("mail sender:{}", from);
if (StringUtils.isAnyBlank(from, to)) {
return;
}
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
// 设置发件人名称和地址
InternetAddress fromAddress = new InternetAddress(from, "AIDeepIn");
helper.setFrom(fromAddress);
// 设置收件人主题内容等其他信息
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content);
javaMailSender.send(message);
} catch (Exception e) {
log.error("发送邮件时发生异常", e);
}
// SimpleMailMessage message = new SimpleMailMessage();
// message.setFrom(from);
// message.setTo(to);
// message.setSubject(subject);
// message.setText(content);
// try {
// javaMailSender.send(message);
// } catch (Exception e) {
// log.error("发送邮件时发生异常", e);
// }
}
}

View File

@ -0,0 +1,45 @@
package com.moyz.adi.common.helper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
@Slf4j
public class HttpHelper {
public static String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("error", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("error", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("error", e);
}
}
}
return sb.toString();
}
}

View File

@ -0,0 +1,231 @@
package com.moyz.adi.common.helper;
import com.didalgo.gpt3.ChatFormatDescriptor;
import com.didalgo.gpt3.Encoding;
import com.didalgo.gpt3.GPT3Tokenizer;
import com.didalgo.gpt3.TokenCount;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.moyz.adi.common.cosntant.AdiConstant;
import com.moyz.adi.common.entity.AiImage;
import com.moyz.adi.common.entity.User;
import com.moyz.adi.common.enums.ErrorEnum;
import com.moyz.adi.common.exception.BaseException;
import com.moyz.adi.common.model.AnswerMeta;
import com.moyz.adi.common.model.ChatMeta;
import com.moyz.adi.common.model.QuestionMeta;
import com.moyz.adi.common.service.FileService;
import com.moyz.adi.common.service.SysConfigService;
import com.moyz.adi.common.util.FileUtil;
import com.moyz.adi.common.util.ImageUtil;
import com.moyz.adi.common.util.JsonUtil;
import com.moyz.adi.common.util.TriConsumer;
import com.theokanning.openai.OpenAiApi;
import com.theokanning.openai.completion.chat.ChatCompletionChoice;
import com.theokanning.openai.completion.chat.ChatCompletionRequest;
import com.theokanning.openai.completion.chat.ChatMessage;
import com.theokanning.openai.image.*;
import com.theokanning.openai.service.OpenAiService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import retrofit2.Retrofit;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import static com.moyz.adi.common.cosntant.AdiConstant.OPENAI_CREATE_IMAGE_RESP_FORMATS_URL;
import static com.moyz.adi.common.cosntant.AdiConstant.OPENAI_CREATE_IMAGE_SIZES;
import static com.theokanning.openai.service.OpenAiService.defaultClient;
import static com.theokanning.openai.service.OpenAiService.defaultRetrofit;
@Slf4j
@Service
public class OpenAiHelper {
@Value("${openai.proxy.enable:false}")
private boolean proxyEnable;
@Value("${openai.proxy.host:0}")
private String proxyHost;
@Value("${openai.proxy.http-port:0}")
private int proxyHttpPort;
@Value("${local.images}")
private String localImagesPath;
@Resource
private FileService fileService;
@Resource
private ObjectMapper objectMapper;
public OpenAiService getOpenAiService(User user) {
String secretKey = SysConfigService.getSecretKey();
String userSecretKey = user.getSecretKey();
if (StringUtils.isNotBlank(userSecretKey)) {
secretKey = userSecretKey;
}
if (proxyEnable) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
OkHttpClient client = defaultClient(secretKey, Duration.of(60, ChronoUnit.SECONDS))
.newBuilder()
.proxy(proxy)
.build();
Retrofit retrofit = defaultRetrofit(client, objectMapper);
OpenAiApi api = retrofit.create(OpenAiApi.class);
return new OpenAiService(api);
}
return new OpenAiService(secretKey, Duration.of(60, ChronoUnit.SECONDS));
}
/**
* Send http request to openai server <br/>
* Calculate token
*
* @param user
* @param regenerateQuestionUuid
* @param chatMessageList
* @param sseEmitter
* @param consumer
*/
public void sseAsk(User user, String regenerateQuestionUuid, List<ChatMessage> chatMessageList, SseEmitter sseEmitter, TriConsumer<String, QuestionMeta, AnswerMeta> consumer) {
final int[] answerTokens = {0};
StringBuilder response = new StringBuilder();
OpenAiService service = getOpenAiService(user);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model(AdiConstant.DEFAULT_MODEL)
.messages(chatMessageList)
.n(1)
.logitBias(new HashMap<>())
.build();
service.streamChatCompletion(chatCompletionRequest)
.doOnError(onError -> {
log.error("openai error", onError);
sseEmitter.send(SseEmitter.event().name("error").data(onError.getMessage()));
sseEmitter.complete();
}).subscribe(completionChunk -> {
answerTokens[0]++;
List<ChatCompletionChoice> choices = completionChunk.getChoices();
String content = choices.get(0).getMessage().getContent();
log.info("get content:{}", content);
if (null == content && response.isEmpty()) {
return;
}
if (null == content || AdiConstant.OPENAI_MESSAGE_DONE_FLAG.equals(content)) {
log.info("OpenAI返回数据结束了");
sseEmitter.send(AdiConstant.OPENAI_MESSAGE_DONE_FLAG);
GPT3Tokenizer tokenizer = new GPT3Tokenizer(Encoding.CL100K_BASE);
int questionTokens = 0;
try {
questionTokens = TokenCount.fromMessages(chatMessageList, tokenizer, ChatFormatDescriptor.forModel(AdiConstant.DEFAULT_MODEL));
} catch (IllegalArgumentException e) {
log.error("该模型的token无法统计,model:{}", AdiConstant.DEFAULT_MODEL);
}
System.out.println("requestTokens:" + questionTokens);
System.out.println("返回内容:" + response);
String questionUuid = StringUtils.isNotBlank(regenerateQuestionUuid) ? regenerateQuestionUuid : UUID.randomUUID().toString().replace("-", "");
QuestionMeta questionMeta = new QuestionMeta(questionTokens, questionUuid);
AnswerMeta answerMeta = new AnswerMeta(answerTokens[0], UUID.randomUUID().toString().replace("-", ""));
ChatMeta chatMeta = new ChatMeta(questionMeta, answerMeta);
// String meta = JsonUtil.toJson(chatMeta).replaceAll("\r\n", "");
String meta = JsonUtil.toJson(chatMeta).replaceAll("\r\n", "");
log.info("meta:" + meta);
sseEmitter.send(" [META]" + meta);
// close eventSourceEmitter after tokens was calculated
sseEmitter.complete();
consumer.accept(response.toString(), questionMeta, answerMeta);
return;
}
//加空格配合前端的fetchEventSource进行解析见https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/parse.ts#L129-L133
sseEmitter.send(" " + content);
response.append(content);
});
System.out.println("返回内容1111" + response);
}
public List<Image> createImage(User user, AiImage aiImage) {
if (aiImage.getGenerateNumber() < 1 || aiImage.getGenerateNumber() > 10) {
throw new BaseException(ErrorEnum.A_IMAGE_NUMBER_ERROR);
}
if (!OPENAI_CREATE_IMAGE_SIZES.contains(aiImage.getGenerateSize())) {
throw new BaseException(ErrorEnum.A_IMAGE_SIZE_ERROR);
}
OpenAiService service = getOpenAiService(user);
CreateImageRequest createImageRequest = new CreateImageRequest();
createImageRequest.setPrompt(aiImage.getPrompt());
createImageRequest.setN(aiImage.getGenerateNumber());
createImageRequest.setSize(aiImage.getGenerateSize());
createImageRequest.setResponseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL);
createImageRequest.setUser(user.getUuid());
try {
ImageResult imageResult = service.createImage(createImageRequest);
log.info("createImage response:{}", imageResult);
return imageResult.getData();
} catch (Exception e) {
log.error("create image error", e);
}
return Collections.emptyList();
}
public List<Image> editImage(User user, AiImage aiImage) {
File originalFile = new File(fileService.getImagePath(aiImage.getOriginalImage()));
File maskFile = null;
if (StringUtils.isNotBlank(aiImage.getMaskImage())) {
maskFile = new File(fileService.getImagePath(aiImage.getMaskImage()));
}
//如果不是RGBA类型的图片先转成RGBA
File rgbaOriginalImage = ImageUtil.rgbConvertToRgba(originalFile, fileService.getTmpImagesPath(aiImage.getOriginalImage()));
OpenAiService service = getOpenAiService(user);
CreateImageEditRequest request = new CreateImageEditRequest();
request.setPrompt(aiImage.getPrompt());
request.setN(aiImage.getGenerateNumber());
request.setSize(aiImage.getGenerateSize());
request.setResponseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL);
request.setUser(user.getUuid());
try {
ImageResult imageResult = service.createImageEdit(request, rgbaOriginalImage, maskFile);
log.info("editImage response:{}", imageResult);
return imageResult.getData();
} catch (Exception e) {
log.error("edit image error", e);
}
return Collections.emptyList();
}
public List<Image> createImageVariation(User user, AiImage aiImage) {
File imagePath = new File(fileService.getImagePath(aiImage.getOriginalImage()));
OpenAiService service = getOpenAiService(user);
CreateImageVariationRequest request = new CreateImageVariationRequest();
request.setN(aiImage.getGenerateNumber());
request.setSize(aiImage.getGenerateSize());
request.setResponseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL);
request.setUser(user.getUuid());
try {
ImageResult imageResult = service.createImageVariation(request, imagePath);
log.info("createImageVariation response:{}", imageResult);
return imageResult.getData();
} catch (Exception e) {
log.error("image variation error", e);
}
return Collections.emptyList();
}
}

View File

@ -0,0 +1,64 @@
package com.moyz.adi.common.helper;
import com.moyz.adi.common.entity.User;
import com.moyz.adi.common.enums.ErrorEnum;
import com.moyz.adi.common.model.CostStat;
import com.moyz.adi.common.service.UserDayCostService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class QuotaHelper {
@Resource
private UserDayCostService userDayCostService;
public ErrorEnum checkTextQuota(User user) {
if (StringUtils.isNotBlank(user.getSecretKey())) {
log.info("Custom secret key,dont need to check text request quota,userId:{}", user.getId());
return null;
}
int userQuotaByTokenDay = user.getQuotaByTokenDaily();
int userQuotaByTokenMonth = user.getQuotaByTokenMonthly();
int userQuotaByRequestDay = user.getQuotaByRequestDaily();
int userQuotaByRequestMonth = user.getQuotaByRequestMonthly();
CostStat costStat = userDayCostService.costStatByUser(user.getId());
if (costStat.getTextTokenCostByDay() >= userQuotaByTokenDay || costStat.getTextRequestTimesByDay() >= userQuotaByRequestDay) {
log.warn("Reach limit of a day,userId:{},token:{},request:{},used token:{}, used request:{}", user.getId(), userQuotaByRequestDay, userQuotaByRequestDay, userQuotaByTokenMonth, userQuotaByRequestMonth);
return ErrorEnum.B_DAILY_QUOTA_USED;
}
if (costStat.getTextTokenCostByMonth() >= user.getQuotaByTokenMonthly() || costStat.getTextRequestTimesByMonth() >= user.getQuotaByRequestMonthly()) {
log.warn("Reach limit of a month,userId:{},token:{},request:{},used token:{}, used request:{}", user.getId(), user.getQuotaByTokenMonthly(), user.getQuotaByRequestMonthly(), costStat.getTextTokenCostByMonth(), costStat.getTextRequestTimesByMonth());
return ErrorEnum.B_MONTHLY_QUOTA_USED;
}
return null;
}
/**
* Check the generate image request if it can be accepted
*
* @param user
* @return
*/
public ErrorEnum checkImageQuota(User user) {
if (StringUtils.isNotBlank(user.getSecretKey())) {
log.info("Custom secret key,dont need to check image quota,userId:{}", user.getId());
return null;
}
int userDailyQuota = user.getQuotaByImageDaily();
int userMonthlyQuota = user.getQuotaByImageMonthly();
CostStat costStat = userDayCostService.costStatByUser(user.getId());
if (costStat.getImageGeneratedNumberByDay() >= userDailyQuota) {
log.warn("Generate image reach limit of a day,userId:{},request quota:{},used request times:{}", user.getId(), userDailyQuota, costStat.getImageGeneratedNumberByDay());
return ErrorEnum.B_DAILY_QUOTA_USED;
}
if (costStat.getImageGeneratedNumberByMonth() >= userMonthlyQuota) {
log.warn("Generate image reach limit of a month,userId:{},token:{},request quota:{},used request times:{}", user.getId(), user.getQuotaByImageMonthly(), costStat.getImageGeneratedNumberByMonth());
return ErrorEnum.B_MONTHLY_QUOTA_USED;
}
return null;
}
}

View File

@ -0,0 +1,40 @@
package com.moyz.adi.common.helper;
import com.moyz.adi.common.model.RequestRateLimit;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RateLimitHelper {
@Resource
private StringRedisTemplate stringRedisTemplate;
public boolean checkRequestTimes(String requestTimesKey, RequestRateLimit rateLimitConfig) {
int requestCountInTimeWindow = 0;
String rateLimitVal = stringRedisTemplate.opsForValue().get(requestTimesKey);
if (StringUtils.isNotBlank(rateLimitVal)) {
requestCountInTimeWindow = Integer.parseInt(rateLimitVal);
}
if (requestCountInTimeWindow >= rateLimitConfig.getTimes()) {
return false;
}
return true;
}
public void increaseRequestTimes(String requestTimesKey, RequestRateLimit rateLimitConfig) {
long expireTime = stringRedisTemplate.getExpire(requestTimesKey).longValue();
if (expireTime == -1) {
stringRedisTemplate.opsForValue().increment(requestTimesKey);
stringRedisTemplate.opsForValue().set(requestTimesKey, String.valueOf(1), rateLimitConfig.getMinutes(), TimeUnit.MINUTES);
} else if (expireTime > 3) {
//If expireTime <= 3, too short to cache
stringRedisTemplate.opsForValue().increment(requestTimesKey);
}
}
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.moyz.adi.common.entity.AiImage;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AiImageMapper extends BaseMapper<AiImage> {
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.AiModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AiModelMapper extends BaseMapper<AiModel> {
}

View File

@ -0,0 +1,18 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.Conversation;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* 会话表 Mapper 接口
* </p>
*
* @author moyz
* @since 2023-04-11
*/
@Mapper
public interface ConversationMapper extends BaseMapper<Conversation> {
}

View File

@ -0,0 +1,18 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.ConversationMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author moyz
* @since 2023-04-11
*/
@Mapper
public interface ConversationMessageMapper extends BaseMapper<ConversationMessage> {
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.moyz.adi.common.entity.AdiFile;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FileMapper extends BaseMapper<AdiFile> {
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.Prompt;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PromptMapper extends BaseMapper<Prompt> {
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.SysConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysConfigMapper extends BaseMapper<SysConfig> {
}

View File

@ -0,0 +1,9 @@
package com.moyz.adi.common.mapper;
import com.moyz.adi.common.entity.UserDayCost;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDayCostMapper extends BaseMapper<UserDayCost> {
}

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