Commit b835d0c2 authored by 李超鹏's avatar 李超鹏

初始化项目

parents
Pipeline #7328 canceled with stages
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*
\ No newline at end of file
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
# Plugin directory
/.quarkus/cli/plugins/
maven-wrapper.jar
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public final class MavenWrapperDownloader
{
private static final String WRAPPER_VERSION = "3.2.0";
private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) );
public static void main( String[] args )
{
log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION );
if ( args.length != 2 )
{
System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" );
System.exit( 1 );
}
try
{
log( " - Downloader started" );
final URL wrapperUrl = new URL( args[0] );
final String jarPath = args[1].replace( "..", "" ); // Sanitize path
final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize();
downloadFileFromURL( wrapperUrl, wrapperJarPath );
log( "Done" );
}
catch ( IOException e )
{
System.err.println( "- Error downloading: " + e.getMessage() );
if ( VERBOSE )
{
e.printStackTrace();
}
System.exit( 1 );
}
}
private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath )
throws IOException
{
log( " - Downloading to: " + wrapperJarPath );
if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null )
{
final String username = System.getenv( "MVNW_USERNAME" );
final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray();
Authenticator.setDefault( new Authenticator()
{
@Override
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication( username, password );
}
} );
}
try ( InputStream inStream = wrapperUrl.openStream() )
{
Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING );
}
log( " - Downloader complete" );
}
private static void log( String msg )
{
if ( VERBOSE )
{
System.out.println( msg );
}
}
}
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
# code-with-quarkus
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
## Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```shell script
./mvnw compile quarkus:dev
```
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
## Packaging and running the application
The application can be packaged using:
```shell script
./mvnw package
```
It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.
The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
If you want to build an _über-jar_, execute the following command:
```shell script
./mvnw package -Dquarkus.package.type=uber-jar -Dquarkus.profile=stage -Dquarkus.analytics.disabled=true
```
The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.
## Creating a native executable
You can create a native executable using:
```shell script
./mvnw package -Dnative
```
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
./mvnw package -Dnative -Dquarkus.native.container-build=true
```
You can then execute your native executable with: `./target/code-with-quarkus-1.0.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling.
## Related Guides
- REST Client Classic ([guide](https://quarkus.io/guides/rest-client)): Call REST services
- JDBC Driver - MySQL ([guide](https://quarkus.io/guides/datasource)): Connect to the MySQL database via JDBC
- Redis Cache ([guide](https://quarkus.io/guides/cache-redis-reference)): Use Redis as the caching backend
- MyBatis Plus ([guide](https://quarkiverse.github.io/quarkiverse-docs/quarkus-mybatis/dev/)): A powerful enhanced toolkit of MyBatis to simplify development
## Provided Code
### REST Client
Invoke different services through REST with JSON
[Related guide section...](https://quarkus.io/guides/rest-client)
### RESTEasy JAX-RS
Easily start your RESTful Web Services
[Related guide section...](https://quarkus.io/guides/getting-started#the-jax-rs-resources)
This diff is collapsed.
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>cc.shenjiying.ai</groupId>
<artifactId>ai-chat-agent</artifactId>
<version>1.0.0</version>
<properties>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.5.3</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.1.2</surefire-plugin.version>
<wiremock.version>3.3.1</wiremock.version>
<reactive-messagin-http-version>2.0.2</reactive-messagin-http-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- quarkus -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<!-- 响应式rest请求客户端核心包+json包 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
<!-- 响应式rest接口核心包+默认json -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql</artifactId>
</dependency>
<!-- mybatisy以及plus包 -->
<dependency>
<groupId>io.quarkiverse.mybatis</groupId>
<artifactId>quarkus-mybatis-plus</artifactId>
<version>2.2.0</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- 限流 -->
<dependency>
<groupId>io.quarkiverse.bucket4j</groupId>
<artifactId>quarkus-bucket4j</artifactId>
<version>1.0.1</version>
</dependency>
<!-- &lt;!&ndash; redis缓存包 &ndash;&gt;
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
</dependency>-->
<!-- 测试包 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<!-- mock第三方服务 -->
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
<version>${wiremock.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<configuration>
<systemProperties>
<file.encoding>UTF-8</file.encoding>
<quarkus.analytics.disabled>true</quarkus.analytics.disabled>
</systemProperties>
</configuration>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/code-with-quarkus-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.17
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/code-with-quarkus-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.17
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/code-with-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/code-with-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus
#
###
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
package cc.shenjiying.ai;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import org.eclipse.microprofile.openapi.annotations.Components;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.callbacks.Callback;
import org.eclipse.microprofile.openapi.annotations.headers.Header;
import org.eclipse.microprofile.openapi.annotations.info.Contact;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.info.License;
import org.eclipse.microprofile.openapi.annotations.links.Link;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@ApplicationPath("/rest")
@OpenAPIDefinition(
tags = {
@Tag(name = "问答", description = "向ai提问题"),
},
info = @Info(
title = "API 请求增加 channel 请求头",
version = "1.0.1",
contact = @Contact(
name = "Example API Support",
url = "http://exampleurl.com/contact",
email = "techsupport@example.com"),
license = @License(
name = "Apache 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0.html")),
components = @Components(schemas = {@Schema(name = "the name of the schema")},
responses = {@APIResponse(responseCode = "200")},
parameters = {@Parameter(name = "Parameter's name")},
examples = {@ExampleObject(name = "Example object name")},
requestBodies = {@RequestBody(description = "A brief description of the request body")},
headers = {@Header(name = "the header's name")},
securitySchemes = {@SecurityScheme(securitySchemeName = "oauth2")},
links = {@Link(name = "the link's name")}
)
)
public class SimpleApplication extends Application {
}
package cc.shenjiying.ai.client;
import cc.shenjiying.ai.dto.ChatAIRequest;
import cc.shenjiying.ai.dto.ChatAIResponse;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestSseElementType;
import org.jboss.resteasy.reactive.RestStreamElementType;
import java.time.Duration;
//@RestClientLogger
@RegisterRestClient(configKey = "ai")
@Path("/chat")
@RegisterClientHeaders(KnowLedgeRequestHeaderFactory.class)
public interface KnowLedgeChat {
@POST
@Path("/knowledge_base_chat")
ChatAIResponse chat(ChatAIRequest question);
@POST
@Path("/knowledge_base_chat")
Uni<ChatAIResponse> chatAsync(ChatAIRequest question);
@POST
@Path("/knowledge_base_chat_sse")
@Produces(MediaType.SERVER_SENT_EVENTS + "; charset=utf-8")
@RestStreamElementType(MediaType.TEXT_PLAIN)
Multi<ChatAIResponse> chatStream(ChatAIRequest question);
default Multi<ChatAIResponse> test(ChatAIRequest question) {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.onItem().transform(n -> new ChatAIResponse(String.format("hello %s - %d", "test", n), null))
.select().first(10);
}
}
package cc.shenjiying.ai.client;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
@ApplicationScoped
public class KnowLedgeRequestHeaderFactory implements ClientHeadersFactory {
@Override
public MultivaluedMap<String, String> update(MultivaluedMap<String, String> multivaluedMap, MultivaluedMap<String, String> multivaluedMap1) {
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<>();
map.add("test-header", "knowledge");
return map;
}
}
package cc.shenjiying.ai.client;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public record ReceiptResponse(int code, String data, String msg,Long timestamp) {
}
package cc.shenjiying.ai.client;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import java.util.concurrent.CompletionStage;
@RegisterRestClient(baseUri = "https://api-receipt-test.banshouhui.com")
public interface TestClient {
@GET
@Path("/rest/anno/version/latest")
// @Fallback(fallbackMethod = "fallback")
ReceiptResponse lastVersion();
@GET
@Path("/rest/anno/version/latest")
CompletionStage<ReceiptResponse> lastVersionAsync();
@GET
@Path("/rest/anno/version/latest")
Uni<ReceiptResponse> lastVersionAsync2();
// default ReceiptResponse fallback() {
// return null;
// }
}
package cc.shenjiying.ai.client;
import io.smallrye.mutiny.Multi;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestPath;
@RegisterRestClient(baseUri = "http://localhost:8080/rest")
public interface TestLocalClient {
@POST
@Path("/chat/stream/{count}/{name}")
@Produces(MediaType.SERVER_SENT_EVENTS)
Multi<String> greetingsStream(@RestPath int count, @RestPath String name);
}
package cc.shenjiying.ai.config;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
public class ExceptionMappers {
@ServerExceptionMapper
public RestResponse<Object> handler(Exception e) {
// Uni<RestResponse<?>>
return RestResponse.status(500, e.getMessage());
}
}
package cc.shenjiying.ai.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.quarkus.arc.All;
import io.quarkus.jackson.ObjectMapperCustomizer;
import jakarta.inject.Singleton;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
public class JacksonConfig {
@Singleton
ObjectMapper objectMapper(@All List<ObjectMapperCustomizer> customizers) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setLocale(Locale.CHINA)
//去掉默认的时间戳格式
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
// 时区
.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
//Date参数日期格式
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA))
//该特性决定parser是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符和换行符)。 如果该属性关闭,则如果遇到这些字符,则会抛出异常。JSON标准说明书要求所有控制符必须使用引号,因此这是一个非标准的特性
.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true)
// 忽略不能转义的字符
.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true)
//在使用spring boot + jpa/hibernate,如果实体字段上加有FetchType.LAZY,并使用jackson序列化为json串时,会遇到SerializationFeature.FAIL_ON_EMPTY_BEANS异常
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
//忽略未知字段
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
//单引号处理
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
customizers.forEach(item -> item.customize(objectMapper));
return objectMapper;
}
}
package cc.shenjiying.ai.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import io.quarkus.jackson.ObjectMapperCustomizer;
import jakarta.inject.Singleton;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Singleton
public class JacksonCustomizerConfig implements ObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
objectMapper.registerModule(new CustomerModule() {});
}
public static class CustomerModule extends SimpleModule {
public CustomerModule() {
super(PackageVersion.VERSION);
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd ")));
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
this.addSerializer(Long.class, ToStringSerializer.instance);
this.addSerializer(Long.TYPE, ToStringSerializer.instance);
this.addSerializer(BigInteger.class, ToStringSerializer.instance);
// this.addSerializer(BigDecimal.class, BigDecimalSerializer.INSTANCE);
}
}
}
package cc.shenjiying.ai.config;
import cc.shenjiying.ai.repository.entity.Questioner;
import io.smallrye.common.vertx.ContextLocals;
import java.util.Objects;
import java.util.Optional;
public class QuestionerHolder {
public static final String QUESTIONER_KEY = "questioner";
public static final String FROM_CHANNEL_KEY = "channel";
public static Long getId() {
return QuestionerHolder.get(QUESTIONER_KEY).map(Questioner::getId).orElse(0L);
}
public static Optional<Questioner> get(String key) {
return ContextLocals.get(key);
}
public static void putChannel(String channel) {
if (Objects.isNull(channel))
return;
ContextLocals.put(FROM_CHANNEL_KEY, channel);
}
public static void putQuestioner(Questioner questioner) {
if (Objects.isNull(questioner))
return;
ContextLocals.put(QUESTIONER_KEY, questioner);
}
public static void remove() {
ContextLocals.remove(QUESTIONER_KEY);
ContextLocals.remove(FROM_CHANNEL_KEY);
}
}
package cc.shenjiying.ai.config;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.BeforeDestroyed;
import jakarta.enterprise.context.Destroyed;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.transaction.TransactionScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
public class TransactionEventListeningBean {
private final static Logger LOG = LoggerFactory.getLogger(TransactionEventListeningBean.class);
void onBegin(@Observes @Initialized(TransactionScoped.class) Object event) {
LOG.info("transaction begin:" + event.toString());
}
void onBeforeEnd(@Observes @BeforeDestroyed(TransactionScoped.class) Object event) {
LOG.info("transaction end before:" + event.toString());
}
void onAfterEnd(@Observes @Destroyed(TransactionScoped.class) Object event) {
LOG.info("transaction end after:" + event.toString());
}
}
package cc.shenjiying.ai.config.filters;
import io.quarkus.logging.Log;
import jakarta.ws.rs.container.ContainerRequestContext;
import org.jboss.resteasy.reactive.RestResponse;
import java.util.Optional;
// Response/RestResponse Uni<Response/Response> Optional
// 返回 null or empty 代表继续执行链,其他则返回给客户端
public class AnnotateFilter {
// @ServerRequestFilter(priority = 2000)
public void test(ContainerRequestContext context) {
if (null == context.getHeaderString("token")) {
Log.info("not has token");
}
}
// @ServerRequestFilter
public Optional<RestResponse<Void>> getFilter(ContainerRequestContext ctx) {
// only allow GET methods for now
// if(ctx.getMethod().equals(HttpMethod.GET)) {
// return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
// }
return Optional.empty();
}
}
package cc.shenjiying.ai.config.filters;
import cc.shenjiying.ai.util.IpUtils;
import io.smallrye.common.vertx.ContextLocals;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.mutiny.core.Vertx;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 日志拦截器
*/
@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class);
@Context
UriInfo uriInfo;
@Context
HttpServerRequest request;
@Override
public void filter(ContainerRequestContext requestContext) { // 拦截 request
String method = requestContext.getMethod();
String path = requestContext.getUriInfo().getPath();
LOG.info("Request {} {} from IP {}", method, path, IpUtils.getIpAddr(request));
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { // 拦截 response
}
}
package cc.shenjiying.ai.config.filters;
import cc.shenjiying.ai.config.QuestionerHolder;
import cc.shenjiying.ai.repository.entity.Question;
import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.repository.mapper.QuestionMapper;
import cc.shenjiying.ai.repository.mapper.QuestionerMapper;
import cc.shenjiying.ai.repository.service.QuestionerService;
import cc.shenjiying.ai.util.IpUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.quarkus.runtime.util.StringUtil;
import io.vertx.core.http.HttpServerRequest;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Wrapper;
import java.util.Objects;
import static cc.shenjiying.ai.config.QuestionerHolder.FROM_CHANNEL_KEY;
import static cc.shenjiying.ai.config.QuestionerHolder.QUESTIONER_KEY;
/**
* 日志拦截器
*/
@Provider
public class QuestionerFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final Logger LOG = LoggerFactory.getLogger(QuestionerFilter.class);
@Context
UriInfo uriInfo;
@Context
HttpServerRequest request;
@Inject
QuestionerService questionerService;
@Override
public void filter(ContainerRequestContext requestContext) {
String ipAddr = IpUtils.getIpAddr(request);
String channel = requestContext.getHeaderString(FROM_CHANNEL_KEY);
channel = StringUtil.isNullOrEmpty(channel) ? "qs" : "";
Questioner questioner = questionerService.getByChannel(channel, ipAddr);
if (Objects.isNull(questioner)) {
try {
questionerService.save(new Questioner().setChannel(channel).setChannelUserId(ipAddr));
} catch (Exception e) {
questioner = questionerService.getByChannel(channel, ipAddr);
}
}
QuestionerHolder.putQuestioner(questioner);
QuestionerHolder.putChannel(channel);
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
QuestionerHolder.remove();
}
}
package cc.shenjiying.ai.config.interceptors;
import jakarta.interceptor.InterceptorBinding;
import jakarta.ws.rs.ConstrainedTo;
import jakarta.ws.rs.RuntimeType;
import java.lang.annotation.*;
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Inherited
@ConstrainedTo(RuntimeType.CLIENT)
public @interface RestClientLogger {
}
package cc.shenjiying.ai.config.interceptors;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestClientLogger
@Priority(Interceptor.Priority.APPLICATION + 1)
@Interceptor
public class RestClientLoggerInterceptor {
private final static Logger LOGGER = LoggerFactory.getLogger(RestClientLogger.class);
@Inject
ObjectMapper objectMapper;
@AroundInvoke
private Object around(InvocationContext context) throws Exception {
context.getParameters();
LOGGER.info("请求url:");
LOGGER.info("请求参数:" + objectMapper.writeValueAsString(context.getParameters()));
LOGGER.info("共享数据:" + objectMapper.writeValueAsString(context.getContextData()));
Object proceed = context.proceed();
LOGGER.info("请求结果:" + objectMapper.writeValueAsString(proceed));
return proceed;
}
}
package cc.shenjiying.ai.config.interceptors;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.logging.Log;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.interceptor.Interceptor;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.ext.*;
import java.io.IOException;
//@Provider
@Priority(Interceptor.Priority.APPLICATION + 1)
//@ConstrainedTo(RuntimeType.SERVER)
public class TestWriterInterceptor implements WriterInterceptor, ReaderInterceptor {
@Inject
ObjectMapper objectMapper;
@Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { // 拦截 request 可以修改入参
Object proceed = context.proceed();
Log.info("test readerInterceptor" + proceed);
return proceed;
}
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { // 拦截 response 可以修改响应
Log.info("test writeInterceptorContext");
// Log.info(objectMapper.writeValueAsString(context));
context.proceed();
}
}
package cc.shenjiying.ai.config.reader;
import cc.shenjiying.ai.dto.ChatAIResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
@Provider
public class ChatAIReaderWriter implements MessageBodyReader<ChatAIResponse>, MessageBodyWriter {
@Inject
ObjectMapper objectMapper;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return ChatAIResponse.class.isAssignableFrom(type);
}
@Override
public ChatAIResponse readFrom(Class<ChatAIResponse> type, Type genericType, Annotation[] annotations, MediaType mediaType
, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
return objectMapper.readValue(entityStream, ChatAIResponse.class);
}
@Override
public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return ChatAIResponse.class.isAssignableFrom(type);
}
@Override
public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
entityStream.write(objectMapper.writeValueAsString(o).getBytes(StandardCharsets.UTF_8));
}
}
package cc.shenjiying.ai.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.List;
@RegisterForReflection
public class ChatAIRequest {
@JsonProperty("query")
private String query;
@JsonProperty("knowledge_base_name")
private String knowledgeBaseName;
@JsonProperty("top_k")
private int topKey;
@JsonProperty("score_threshold")
private int scopeThreshold;
@JsonProperty("stream")
private boolean stream;
@JsonProperty("model_name")
private String modelName;
@JsonProperty("temperature")
private double temperature;
@JsonProperty("max_tokens")
private int maxTokens;
@JsonProperty("prompt_name")
private String promptName;
@JsonProperty("history")
private List<ChatAiHistory> history;
public static ChatAIRequest getQsDefault() {
ChatAIRequest request = new ChatAIRequest();
request.knowledgeBaseName = "快查";
request.topKey = 3;
request.scopeThreshold = 1;
request.temperature = 0.7;
request.maxTokens = 500;
request.promptName = "default";
request.stream = false;
request.modelName = "chatglm3-6b";
return request;
}
public static record ChatAiHistory(String role, String content) {
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getKnowledgeBaseName() {
return knowledgeBaseName;
}
public void setKnowledgeBaseName(String knowledgeBaseName) {
this.knowledgeBaseName = knowledgeBaseName;
}
public int getTopKey() {
return topKey;
}
public void setTopKey(int topKey) {
this.topKey = topKey;
}
public int getScopeThreshold() {
return scopeThreshold;
}
public void setScopeThreshold(int scopeThreshold) {
this.scopeThreshold = scopeThreshold;
}
public boolean isStream() {
return stream;
}
public void setStream(boolean stream) {
this.stream = stream;
}
public String getModelName() {
return modelName;
}
public void setModelName(String modelName) {
this.modelName = modelName;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
public int getMaxTokens() {
return maxTokens;
}
public void setMaxTokens(int maxTokens) {
this.maxTokens = maxTokens;
}
public String getPromptName() {
return promptName;
}
public void setPromptName(String promptName) {
this.promptName = promptName;
}
public List<ChatAiHistory> getHistory() {
return history;
}
public void setHistory(List<ChatAiHistory> history) {
this.history = history;
}
}
package cc.shenjiying.ai.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.headers.Header;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.List;
@Data
@RegisterForReflection// 为原生程序添加反射 保留类以及成员
public class ChatAIResponse {
public ChatAIResponse() {
}
public ChatAIResponse(String answer, List<String> docs) {
this.answer = answer;
this.docs = docs;
}
private Long questionId;
private String answer;
private List<String> docs;
}
package cc.shenjiying.ai.dto;
import cc.shenjiying.ai.repository.entity.Feedback;
import lombok.Data;
@Data
public class FeedbackSaveDTO {
private Long questionId;
private String feedbackCategory;
private String feedbackType;
private String remark;
public Feedback toEntity() {
return new Feedback().setQuestionId(questionId).setFeedbackCategory(feedbackCategory).setFeedbackType(feedbackType).setRemark(remark);
}
}
package cc.shenjiying.ai.event;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class EventListener {
void onLoggedEvent(@Observes LoggedEvent loggedEvent) {
}
}
package cc.shenjiying.ai.event;
public class LoggedEvent {
}
package cc.shenjiying.ai.event;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
@ApplicationScoped
public class TestFireEvent {
@Inject
Event<LoggedEvent> event;
public void fire() {
event.fire(new LoggedEvent());
}
}
package cc.shenjiying.ai.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("feedback")
@Accessors(chain = true)
public class Feedback {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_id")
private Long userId;
@TableField("question_id")
private Long questionId;
@TableField("feedback_category")
private String feedbackCategory;
@TableField("feedback_type")
private String feedbackType;
@TableField("remark")
private String remark;
@TableField("create_time")
private LocalDateTime createTime;
}
package cc.shenjiying.ai.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("question")
@Accessors(chain = true)
public class Question {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_id")
private Long userId;
@TableField("question")
private String question;
@TableField("answers")
private String answers;
@TableField("correct")
private int correct;
@TableField("create_time")
private LocalDateTime createTime;
}
package cc.shenjiying.ai.repository.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@TableName("questioner")
@Accessors(chain = true)
public class Questioner {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "channel")
private String channel;
@TableField("channel_user_id")
private String channelUserId;
@TableField("create_time")
private LocalDateTime createTime;
}
package cc.shenjiying.ai.repository.mapper;
import cc.shenjiying.ai.repository.entity.Feedback;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FeedbackMapper extends BaseMapper<Feedback> {
}
package cc.shenjiying.ai.repository.mapper;
import cc.shenjiying.ai.repository.entity.Question;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface QuestionMapper extends BaseMapper<Question> {
}
package cc.shenjiying.ai.repository.mapper;
import cc.shenjiying.ai.repository.entity.Questioner;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface QuestionerMapper extends BaseMapper<Questioner> {
}
package cc.shenjiying.ai.repository.service;
import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.repository.mapper.QuestionerMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.Objects;
@ApplicationScoped
public class QuestionerService {
@Inject
QuestionerMapper questionerMapper;
public Questioner getQuestioner(Long userId) {
return questionerMapper.selectById(userId);
}
public Questioner getByChannel(String channel, String channelId) {
return questionerMapper.selectOne(new LambdaQueryWrapper<Questioner>()
.eq(Questioner::getChannel, channel)
.eq(Questioner::getChannelUserId, channelId).last("limit 1"));
}
public int save(Questioner questioner) {
if (Objects.isNull(questioner)) {
return 0;
}
return questionerMapper.insert(questioner);
}
}
package cc.shenjiying.ai.resource;
import cc.shenjiying.ai.dto.ChatAIResponse;
import cc.shenjiying.ai.dto.FeedbackSaveDTO;
import cc.shenjiying.ai.service.KnowledgeService;
import io.quarkiverse.bucket4j.runtime.RateLimited;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.headers.Header;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.annotations.Producer;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestStreamElementType;
@Path("/chat")
@Tag(name = "问答")
public class KnowLedgeResource {
@Inject
KnowledgeService knowledgeService;
@GET
@Path("/stream")
@Operation(summary = "流式请求")
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestStreamElementType(MediaType.APPLICATION_JSON)
@RateLimited(bucket = "chat")
public Multi<ChatAIResponse> chatStream(@QueryParam("text") String text) {
return knowledgeService.chatStream(text);
}
@PUT
@Path("/like/{questionId}")
@Operation(summary = "点赞")
public Uni<Boolean> like(@RestPath Long questionId) {
return Uni.createFrom().item(() -> knowledgeService.like(questionId));
}
@PUT
@Path("/unlike/{questionId}")
@Operation(summary = "下赞")
public Uni<Boolean> unlike(@RestPath Long questionId) {
return Uni.createFrom().item(() -> knowledgeService.unlike(questionId));
}
@POST
@Path("/feedback")
@Operation(summary = "反馈")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Multi<Long> feedback(FeedbackSaveDTO feedback) {
return Multi.createFrom().item(() -> knowledgeService.newFeedback(feedback));
}
}
package cc.shenjiying.ai.resource;
import cc.shenjiying.ai.client.ReceiptResponse;
import cc.shenjiying.ai.client.TestClient;
import cc.shenjiying.ai.client.TestLocalClient;
import io.quarkus.logging.Log;
import io.smallrye.common.annotation.Blocking;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.sse.OutboundSseEvent;
import jakarta.ws.rs.sse.Sse;
import org.eclipse.microprofile.config.inject.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.reactive.*;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import java.io.File;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
@Path("/test")
public class TestResource {
@Inject
@RestClient
TestClient testClient;
@ConfigProperty(name = "quarkus.http.port")
String prot;
public String getProt() {
return this.prot;
}
@GET
@Path("/sync")
@ResponseStatus(201)
@ResponseHeader(name = "response-header", value = "response-header")
public ReceiptResponse sync(@Context UriInfo uriInfo) {
return testClient.lastVersion();
}
@POST
@Path("/sync")
@ResponseStatus(201)
@ResponseHeader(name = "response-header", value = "response-header")
public ReceiptResponse syncData(@Context UriInfo uriInfo) {
return testClient.lastVersion();
}
@GET
@Path("/async")
public CompletionStage<ReceiptResponse> async() {
return testClient.lastVersionAsync();
}
@GET
@Path("/async2")
public Uni<ReceiptResponse> async2() {
return testClient.lastVersionAsync2();
}
@POST
@Path("/requestParam/{path}")
public String requestParam(@RestPath String path, @RestMatrix String variant, @RestQuery String age,
@RestCookie("token") String cookie, @RestHeader("User-Agent") String otherHeader,
@RestForm("form") String form) {
// RestMatrix http://localhost:8080/rest/test/requestParam/hello2;variant=goat
// RestCookie value must has
// result hello/goat/16/hardcore/PostmanRuntime/7.26.8/strong
Log.info(Thread.currentThread().getName());
return path + "/" + variant + "/" + age + "/" + cookie + "/" + otherHeader + "/" + form;
}
@POST
@Path("/requestParam2/{path}")
public String requestParam2(@PathParam("path") String path, @MatrixParam("matrix") String variant, @QueryParam("age") String age,
@CookieParam("test") String cookie, @HeaderParam("User-Agent") String otherHeader,
@FormParam("form") String form) {
// result hello2/null/16/sfsdfds/PostmanRuntime/7.26.8/strong
return path + "/" + variant + "/" + age + "/" + cookie + "/" + otherHeader + "/" + form;
}
public static class Parameters {
@RestPath
String type;
@RestMatrix
String variant;
@RestQuery
String age;
@RestCookie
String level;
@RestHeader("X-Cheese-Secret-Handshake")
String secretHandshake;
@RestForm
String smell;
}
@POST
@Path("/requestParam3/{path}")
public String requestParam3(@BeanParam Parameters param) {
Log.info(Thread.currentThread().getName());
return param.type + "/" + param.variant + "/" + param.age + "/" + param.level + "/" + param.secretHandshake + "/" + param.smell;
}
public static record Person(String firstName, String lastName) {
}
@POST
@Path("/upload")
public void multipart(@RestForm String desc, @RestForm("image") FileUpload file, @RestForm @PartType(MediaType.APPLICATION_JSON) Person person) {
// MultipartFormDataInput 多文件接受类型
}
public static class DownloadFormData {
@RestForm
public String name;
@RestForm
@PartType(MediaType.APPLICATION_OCTET_STREAM)
public File file;
}
@POST
@Path("/download")
public DownloadFormData download() {
return null;
}
@GET
@Path("/uni/block")
@Blocking
public Uni<String> uniBlock() {
Log.info(Thread.currentThread().getName());
return Uni.createFrom().item("uni but block exec");
}
@GET
@Path("/uni")
public Uni<Person> uniUonBlock() {
return Uni.createFrom().item(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.info(Thread.currentThread().getName());
Person person = new Person("uni", "exec");
return person;
});
}
@GET
@Path("/completionStage")
public CompletionStage<String> completionStageAsync() {
return CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "completion exec";
});
}
@GET
@Path("/multi")
@RestStreamElementType(MediaType.TEXT_PLAIN)
public Multi<Integer> streamLog() {
return Multi.createFrom().generator(() -> 0, (a, b) -> {
if (a > 10) {
b.complete();
}
return ++a;
});
}
@Inject
Sse sse;
@GET
@Path("/sse")
@RestStreamElementType(MediaType.TEXT_PLAIN)
public Multi<OutboundSseEvent> sse() {
return RestMulti.fromMultiData(Multi.createFrom().item("test")
.map(str -> sse.newEventBuilder().data(new Person(str, str)).build())).build();
}
@Inject
Vertx vertx;
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@Path("{name}/streaming")
public Multi<String> greeting(String name) {
return vertx.periodicStream(2000).toMulti().map(l -> String.format("Hello %s! (%s)%n", name, new Date()));
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{count}/{name}")
public Multi<String> greetings(int count, String name) {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.onItem().transform(n -> String.format("hello %s - %d \n", name, n))
.select().first(count);
}
@POST
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestStreamElementType(MediaType.TEXT_PLAIN)
@Path("/stream/{count}/{name}")
public Multi<String> greetingsAsStream(int count, String name) {
Log.infof("{%d},{%s}", count, name);
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.onItem().transform(n -> String.format("hello %s - %d", name, n))
.select().first(count);
}
@Inject
@RestClient
TestLocalClient testLocalClient;
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestStreamElementType(MediaType.TEXT_PLAIN)
@Path("/stream/proxy/{count}/{name}")
public Multi<String> greetingsProxy(int count, String name) {
return testLocalClient.greetingsStream(count, name);
}
}
package cc.shenjiying.ai.service;
import cc.shenjiying.ai.client.KnowLedgeChat;
import cc.shenjiying.ai.config.QuestionerHolder;
import cc.shenjiying.ai.dto.ChatAIRequest;
import cc.shenjiying.ai.dto.ChatAIResponse;
import cc.shenjiying.ai.dto.FeedbackSaveDTO;
import cc.shenjiying.ai.repository.entity.Feedback;
import cc.shenjiying.ai.repository.entity.Question;
import cc.shenjiying.ai.repository.entity.Questioner;
import cc.shenjiying.ai.repository.mapper.FeedbackMapper;
import cc.shenjiying.ai.repository.mapper.QuestionMapper;
import io.quarkus.logging.Log;
import io.quarkus.runtime.util.StringUtil;
import io.smallrye.common.vertx.ContextLocals;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.helpers.BlockingIterable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.Objects;
import java.util.stream.Collectors;
@ApplicationScoped
public class KnowledgeService {
@Inject
@RestClient
KnowLedgeChat knowLedgeChat;
@Inject
QuestionMapper questionMapper;
@Inject
FeedbackMapper feedbackMapper;
public BlockingIterable<ChatAIResponse> chat(@PathParam("channel") String channel, @QueryParam("text") String text) {
if (!StringUtil.isNullOrEmpty(text)) {
ChatAIRequest request = ChatAIRequest.getQsDefault();
request.setQuery(text);
request.setStream(true);
Multi<ChatAIResponse> chatStream = knowLedgeChat.chatStream(request);
BlockingIterable<ChatAIResponse> iterable = chatStream.subscribe().asIterable();
iterable.forEach(item -> Log.info(item.getAnswer()));
return iterable;
}
return null;
}
public Multi<ChatAIResponse> chatStream(String text) {
if (!StringUtil.isNullOrEmpty(text)) {
Question question = new Question().setUserId(QuestionerHolder.getId()).setQuestion(text);
questionMapper.insert(question);
ChatAIRequest request = ChatAIRequest.getQsDefault();
request.setQuery(text);
request.setStream(true);
Multi<ChatAIResponse> chatStream = knowLedgeChat.chatStream(request);
StringBuilder answers = new StringBuilder();
return chatStream
.onItem().invoke(item -> item.setQuestionId(question.getId()))
.onItem().invoke(item -> {
answers.append(StringUtil.isNullOrEmpty(item.getAnswer()) ? "" : item.getAnswer());
if (Objects.nonNull(item.getDocs())) {
answers.append("\n").append(String.join(";", item.getDocs()));
}
})
.onCompletion().invoke(() -> {
questionMapper.updateById(question.setAnswers(answers.toString()));
});
}
return Multi.createFrom().item(new ChatAIResponse());
}
public Boolean like(Long questionId) {
questionMapper.updateById(new Question().setId(questionId).setCorrect(1));
return Boolean.TRUE;
}
public Boolean unlike(Long questionId) {
questionMapper.updateById(new Question().setId(questionId).setCorrect(2));
return Boolean.TRUE;
}
public Long newFeedback(FeedbackSaveDTO feedback) {
Feedback entity = feedback.toEntity().setUserId(QuestionerHolder.getId());
feedbackMapper.insert(entity);
return entity.getId();
}
}
package cc.shenjiying.ai.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
@ApplicationScoped
public class TestService {
@Transactional
public void service(){
}
}
package cc.shenjiying.ai.util;
import io.quarkus.runtime.util.StringUtil;
import io.vertx.core.http.HttpServerRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
/**
* 获取IP方法
*
* @author ruoyi
*/
public class IpUtils {
/**
* 获取客户端IP
*
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddr(HttpServerRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.remoteAddress().hostAddress();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 检查是否为内部IP地址
*
* @param ip IP地址
* @return 结果
*/
public static boolean internalIp(String ip) {
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
/**
* 检查是否为内部IP地址
*
* @param addr byte地址
* @return 结果
*/
private static boolean internalIp(byte[] addr) {
if (Objects.isNull(addr) || addr.length < 2) {
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0) {
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
return true;
}
case SECTION_5:
if (b1 == SECTION_6) {
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*
* @param text IPv4地址
* @return byte 字节
*/
public static byte[] textToNumericFormatV4(String text) {
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1 -> {
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L)) {
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
}
case 2 -> {
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L)) {
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
}
case 3 -> {
for (i = 0; i < 2; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L)) {
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
}
case 4 -> {
for (i = 0; i < 4; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
}
default -> {
return null;
}
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
/**
* 获取IP地址
*
* @return 本地IP地址
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException ignored) {
}
return "127.0.0.1";
}
/**
* 获取主机名
*
* @return 本地主机名
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException ignored) {
}
return "未知";
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (!isUnknown(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknown(String checkString) {
return StringUtil.isNullOrEmpty(checkString) || "unknown".equalsIgnoreCase(checkString);
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
quarkus.http.port=8777
quarkus.log.level=ERROR
#\u6570\u636E\u5E93\u914D\u7F6E
quarkus.datasource.db-kind=mysql
quarkus.datasource.username=sts
quarkus.datasource.password=fnst1234
quarkus.datasource.jdbc.url=jdbc:mysql://172.16.16.3:3306/ai_chat
quarkus.mybatis.map-underscore-to-camel-case=true
quarkus.log.file.enable=true
quarkus.log.file.level=WARE
quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log
quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n
quarkus.log.file.async=true
quarkus.http.port=8777
quarkus.log.level=ERROR
#\u6570\u636E\u5E93\u914D\u7F6E
quarkus.datasource.db-kind=mysql
quarkus.datasource.username=sts
quarkus.datasource.password=fnst1234
quarkus.datasource.jdbc.url=jdbc:mysql://192.168.2.2:3306/ai_chat
#\u663E\u793Aswaggerui
quarkus.swagger-ui.always-include=true
quarkus.log.file.enable=true
quarkus.log.file.level=WARE
quarkus.log.file.path=/weixiuhui/logStore/ai-chat/application.log
quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n
quarkus.log.file.async=true
quarkus.http.port=8862
# http\u591A\u90E8\u5206\u6570\u636E\uFF0C\u6BCF\u90E8\u5206\u6570\u636E\u5927\u5C0F\u6700\u5927\u503C\uFF0C\u8D85\u8FC7\u8FD4\u56DE413
quarkus.http.limits.max-form-attribute-size=4096
# \u6587\u4EF6\u4E0A\u4F20\uFF0C\u4F1A\u5C06\u6587\u4EF6\u7F13\u5B58\u5728\u4E34\u65F6\u76EE\u5F55\u4E2D\uFF0C\u8BBE\u7F6Etrue,http\u54CD\u5E94\u65F6\uFF0C\u4F1A\u5C06\u4E34\u65F6\u6587\u4EF6\u5220\u9664
quarkus.http.body.delete-uploaded-files-on-end=true
# \u4E34\u65F6\u6587\u4EF6\u76EE\u5F55
#quarkus.http.body.uploads-directory=
# http \u8DE8\u57DF\u914D\u7F6E
quarkus.http.cors=false
quarkus.http.cors.origins=/.*/
quarkus.http.cors.methods=/.*/
quarkus.http.cors.headers=/.*/
quarkus.http.cors.exposed-headers=Content-Disposition
quarkus.http.cors.access-control-max-age=24H
quarkus.http.cors.access-control-allow-credentials=true
#\u65E5\u5FD7
quarkus.log.level=DEBUG
#\u81EA\u5B9A\u4E49\u65E5\u5FD7\u6587\u4EF6
#quarkus.log.handler.file.my-file-handler.enable=true
#quarkus.log.handler.file.my-file-handler.path=application.log
#quarkus.log.handler.file.my-file-handler.format=%d{yyyy-MM-dd HH:mm:ss} [com.example] %s%e%n
#quarkus.log.category."com.example".handlers=my-file-handler
#quarkus.log.category."com.example".use-parent-handlers=false
#quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n
#\u81EA\u5B9A\u4E49\u63A7\u5236\u53F0\u65E5\u5FD7
#quarkus.log.handler.console.my-console-handler.format=%d{yyyy-MM-dd HH:mm:ss} [com.example] %s%e%n
#quarkus.log.category."com.example".handlers=my-console-handler
#quarkus.log.category."com.example".use-parent-handlers=false
# ssl\u8BA4\u8BC1\u914D\u7F6E
quarkus.tls.trust-all=false
#rest\u5BA2\u6237\u7AEF ai baseurl
quarkus.rest-client.ai.url=http://115.159.4.244:7861
#quarkus.rest-client.ai.proxy-address=127.0.0.1:8888
quarkus.rest-client.ai.scope=jakarta.inject.Singleton
quarkus.rest-client.ai.verify-host=false
quarkus.rest-client.ai.follow-redirects=true
#\u6570\u636E\u5E93\u914D\u7F6E
quarkus.datasource.db-kind=mysql
quarkus.datasource.username=sts
quarkus.datasource.password=fnst1234
quarkus.datasource.jdbc.url=jdbc:mysql://192.168.1.24:3306/quarkus
#mybatis \u9ED8\u8BA4\u4F7F\u7528\u9ED8\u8BA4\u6570\u636E\u6E90\uFF0C\u4EE3\u7801\u4E2D\u53EF\u4EE5\u901A\u8FC7@MapperDataSource \u6307\u5B9A\u5176\u4ED6\u6570\u636E\u6E90
quarkus.mybatis.map-underscore-to-camel-case=true
#quarkus.mybatis.xmlconfig.enable=true
#quarkus.mybatis.xmlconfig.path=mybatis-config.xml
#quarkus.mybatis.environment=development
#quarkus.mybatis.mapper-locations=mapper
#Redis\u914D\u7F6E
quarkus.redis.hosts=redis://192.168.1.24:3306/
#Reids\u96C6\u7FA4\u6A21\u5F0F
#quarkus.redis.hosts=redis://localhost:7000,redis://localhost:7001,redis://localhost:7002
#quarkus.redis.client-type=cluster
#quarkus.redis.replicas=share
#\u9650\u6D41 \u591A\u4E2A\u9650\u5236
quarkus.rate-limiter.buckets.chat.limits[0].permitted-uses: 5
quarkus.rate-limiter.buckets.chat.limits[0].period: 1S
quarkus.rate-limiter.buckets.chat.limits[1].permitted-uses: 700
quarkus.rate-limiter.buckets.chat.limits[1].period: 5M
package cc.shenjiying.ai;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
public class FileTest {
public static void main(String[] args) throws IOException {
BasicFileAttributeView fileAttributeView = Files.getFileAttributeView(Paths.get("C:\\Users\\Administrator\\Desktop\\iot\\总后台操作说明书.pdf"), BasicFileAttributeView.class);
FileTime fileTime = FileTime.from(LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)));
fileAttributeView.setTimes(fileTime, fileTime, fileTime);
}
}
package cc.shenjiying.ai;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
/**
* rest 测试
*/
@QuarkusTest
@QuarkusTestResource(KnowLedgeServiceMock.class)
public class KnowLedgeResourceTest {
@Test
public void testChat() {
// given().when().get("/chat").then().statusCode(200)
// .body(is(""))
;
}
}
package cc.shenjiying.ai;
import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import java.util.Map;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class KnowLedgeServiceMock implements QuarkusTestResourceLifecycleManager {
private WireMockServer wireMockServer;
@Override
public Map<String, String> start() {
wireMockServer = new WireMockServer();
wireMockServer.start();
wireMockServer.stubFor(get(urlEqualTo("/chat/knowledge_base_chat"))
.willReturn(aResponse().withHeader("Content-Type", "application/json").withBody("{\"data\":\"mock\"}")));
return Map.of("quarkus.rest-client.ai.url", wireMockServer.baseUrl());
}
@Override
public void stop() {
if (null != wireMockServer) {
wireMockServer.stop();
}
}
}
package org.acme;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class GreetingResourceIT extends GreetingResourceTest {
// Execute the same tests but in packaged mode.
}
package org.acme;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("Hello RESTEasy"));
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment