Compare commits
20 Commits
637f125348
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a34168ef75 | |||
| 45f8348395 | |||
| 5681b6bcef | |||
| c06cfc10ee | |||
| be709efa2e | |||
| 17f58a7b45 | |||
| 9337540c77 | |||
| 873fc3b149 | |||
| cf0e326b0c | |||
| 61fb847ac1 | |||
| ef6b5cb11e | |||
| 51d16ea077 | |||
| 439fdf90c4 | |||
| 08043672f9 | |||
| c3c07ff1e7 | |||
| 5522eaa1d6 | |||
| 93759b4a1a | |||
| c9e9a1a4c7 | |||
| a4575cebd4 | |||
| 8bd56a6001 |
@@ -7,7 +7,12 @@
|
||||
"Bash(mvn dependency:tree:*)",
|
||||
"Bash(mvn spring-javaformat:apply:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" status)",
|
||||
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" checkout 3da91e5 -- aioj-backend-ai-service/)",
|
||||
"Bash(git -C \"C:\\\\Users\\\\meowr\\\\Desktop\\\\bishe\\\\AI_OJ\" push)",
|
||||
"Bash(mvn compile:*)",
|
||||
"Bash(mvn clean install:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.5.7</version>
|
||||
<relativePath></relativePath>
|
||||
</parent>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>ai-oj</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>ai-oj-microservices</name>
|
||||
<description>一款集成了AI功能的OJ判题系统</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<modules>
|
||||
<module>aioj-backend-common</module>
|
||||
<module>aioj-backend-gateway</module>
|
||||
<module>aioj-backend-judge-service</module>
|
||||
<module>aioj-backend-user-service</module>
|
||||
<module>aioj-backend-question-service</module>
|
||||
<module>aioj-backend-ai-service</module>
|
||||
<module>aioj-backend-auth</module>
|
||||
<module>aioj-backend-upms</module>
|
||||
<module>aioj-backend-file-service</module>
|
||||
</modules>
|
||||
<properties>
|
||||
<docker.spring.active>dev</docker.spring.active>
|
||||
<flatten.plugin.version>1.6.0</flatten.plugin.version>
|
||||
<git.commit.plugin>9.0.2</git.commit.plugin>
|
||||
<java.version>17</java.version>
|
||||
<jib.plugin.version>3.4.5</jib.plugin.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<spring-boot.version>3.5.7</spring-boot.version>
|
||||
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
|
||||
<revision>1.0.0</revision>
|
||||
<spring-cloud.version>2025.0.0</spring-cloud.version>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven.compiler.plugin.version>3.14.1</maven.compiler.plugin.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring.checkstyle.plugin>0.0.47</spring.checkstyle.plugin>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-bom</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||
<version>${spring-cloud-alibaba.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>${jib.plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>buildTar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<allowInsecureRegistries>true</allowInsecureRegistries>
|
||||
<from>
|
||||
<image>registry.cn-shanghai.aliyuncs.com/all_lib/eclipse-temurin:17.0.10_7-jdk-jammy</image>
|
||||
</from>
|
||||
<to>
|
||||
<image>10.0.0.3/aioj/${project.artifactId}:${project.version}</image>
|
||||
<tags>
|
||||
<tag>${project.version}</tag>
|
||||
</tags>
|
||||
<auth>
|
||||
<username></username>
|
||||
<password></password>
|
||||
</auth>
|
||||
</to>
|
||||
<outputPaths>
|
||||
<tar>${project.build.directory}/${project.artifactId}-${project.version}.tar</tar>
|
||||
</outputPaths>
|
||||
<container>
|
||||
<environment>
|
||||
<TZ>Asia/Shanghai</TZ>
|
||||
<PROFILES_ACTIVE>${docker.spring.active}</PROFILES_ACTIVE>
|
||||
</environment>
|
||||
<jvmFlags>
|
||||
<jvmFlag>-Xms512m</jvmFlag>
|
||||
<jvmFlag>-Xmx512m</jvmFlag>
|
||||
</jvmFlags>
|
||||
</container>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven.compiler.plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>${flatten.plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>flatten</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>flatten</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten.clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.spring.javaformat</groupId>
|
||||
<artifactId>spring-javaformat-maven-plugin</artifactId>
|
||||
<version>${spring.checkstyle.plugin}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<inherited>true</inherited>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.github.git-commit-id</groupId>
|
||||
<artifactId>git-commit-id-maven-plugin</artifactId>
|
||||
<version>${git.commit.plugin}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-the-git-infos</id>
|
||||
<phase>initialize</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<failOnNoGitDirectory>false</failOnNoGitDirectory>
|
||||
<generateGitPropertiesFile>true</generateGitPropertiesFile>
|
||||
<dateFormat>yyyy-MM-dd HH:mm:ss</dateFormat>
|
||||
<includeOnlyProperties>
|
||||
<includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
|
||||
<includeOnlyProperty>^git.commit.(id|message|time).*$</includeOnlyProperty>
|
||||
</includeOnlyProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<env>dev</env>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>test</id>
|
||||
<properties>
|
||||
<env>test</env>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<properties>
|
||||
<env>prod</env>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -39,4 +39,9 @@ build/
|
||||
|
||||
|
||||
### mybatis plus generator
|
||||
/generator/
|
||||
/generator/
|
||||
|
||||
### Uploads ###
|
||||
/uploads/
|
||||
### Logs ###
|
||||
/logs/
|
||||
2
.idea/CoolRequestCommonStatePersistent.xml
generated
2
.idea/CoolRequestCommonStatePersistent.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CoolRequestCommonStatePersistent">
|
||||
<option name="searchCache" value="AIOJAdmin" />
|
||||
<option name="searchCache" value="difficul" />
|
||||
</component>
|
||||
</project>
|
||||
52
.idea/dataSources.xml
generated
52
.idea/dataSources.xml
generated
@@ -49,5 +49,57 @@
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="AIOJAdminApplication" uuid="3a647305-fb45-441b-ba2b-a79ec82a3778">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="AIOJAuthApplication" uuid="38fda843-f467-435e-99e4-2a771f7af3f3">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="FileServiceApplication" uuid="8d957a30-3743-40eb-a916-c6503a783fb9">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="UserServiceApplication" uuid="38b7e47b-6235-4576-8b43-df28c967dbcc">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="aioj" uuid="cfc60cc1-f725-4d9c-b129-5b722771d69e">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10:3306</jdbc-url>
|
||||
<jdbc-additional-properties>
|
||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||
</jdbc-additional-properties>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="jdbc:mysql://10.0.0.10/aioj_dev [DEBUG]" group="QuestionServiceApplication" uuid="5a11088e-1728-4471-a8db-deaeac511136">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://10.0.0.10/aioj_dev</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/db-forest-config.xml
generated
2
.idea/db-forest-config.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="db-tree-configuration">
|
||||
<option name="data" value="1:0:AIOJAdminApplication 4:0:UserServiceApplication 7:0:AIOJAuthApplication ---------------------------------------- 2:1:43cc61de-66e1-44cc-b4a2-b24d7e03b490 3:1:1323cc2e-0b2e-40de-abe6-d1f4c7567b1e 5:4:903d03c4-df11-4cf8-939a-3e5fba0ab207 6:4:c52f5e64-993d-4013-9e2b-838e23d604a2 8:7:2fd8684a-b9aa-4507-abb0-f7c259d91286 9:7:e757fbaf-3605-4bf2-9eb5-852d06273adc " />
|
||||
<option name="data" value="1:0:AIOJAdminApplication 5:0:UserServiceApplication 9:0:AIOJAuthApplication 13:0:FileServiceApplication 15:0:QuestionServiceApplication ---------------------------------------- 2:1:43cc61de-66e1-44cc-b4a2-b24d7e03b490 3:1:1323cc2e-0b2e-40de-abe6-d1f4c7567b1e 4:1:3a647305-fb45-441b-ba2b-a79ec82a3778 6:5:903d03c4-df11-4cf8-939a-3e5fba0ab207 7:5:c52f5e64-993d-4013-9e2b-838e23d604a2 8:5:38b7e47b-6235-4576-8b43-df28c967dbcc 10:9:2fd8684a-b9aa-4507-abb0-f7c259d91286 11:9:e757fbaf-3605-4bf2-9eb5-852d06273adc 12:9:38fda843-f467-435e-99e4-2a771f7af3f3 14:13:8d957a30-3743-40eb-a916-c6503a783fb9 16:15:5a11088e-1728-4471-a8db-deaeac511136 17:0:cfc60cc1-f725-4d9c-b129-5b722771d69e " />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/easyCodeTableSettingEncode.xml
generated
Normal file
10
.idea/easyCodeTableSettingEncode.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EasyCodeTableSetting">
|
||||
<option name="tableInfoMap">
|
||||
<map>
|
||||
<entry key="aioj_dev.attachment" value="eyJuYW1lIjoiQXR0YWNobWVudCIsInByZU5hbWUiOiIiLCJjb21tZW50Ijoi6YCa55So6ZmE5Lu26KGoIiwidGVtcGxhdGVHcm91cE5hbWUiOiIiLCJmdWxsQ29sdW1uIjpbeyJuYW1lIjoiaWQiLCJjb21tZW50Ijoi5Li76ZSuIiwidHlwZSI6ImphdmEubGFuZy5Mb25nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJmaWxlTmFtZSIsImNvbW1lbnQiOiLljp/lp4vmlofku7blkI0iLCJ0eXBlIjoiamF2YS5sYW5nLlN0cmluZyIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoiZmlsZUV4dGVuc2lvbiIsImNvbW1lbnQiOiLmlofku7blkI7nvIDlkI0iLCJ0eXBlIjoiamF2YS5sYW5nLlN0cmluZyIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoiZmlsZVNpemUiLCJjb21tZW50Ijoi5paH5Lu25aSn5bCPKEJ5dGUpIiwidHlwZSI6ImphdmEubGFuZy5Mb25nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJmaWxlSGFzaCIsImNvbW1lbnQiOiLmlofku7blk4jluIwoTUQ1L1NIQTI1NinnlKjkuo7ljrvph40iLCJ0eXBlIjoiamF2YS5sYW5nLlN0cmluZyIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoibWltZVR5cGUiLCJjb21tZW50IjoiTUlNReexu+WeiyIsInR5cGUiOiJqYXZhLmxhbmcuU3RyaW5nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJzdG9yYWdlVHlwZSIsImNvbW1lbnQiOiLlrZjlgqjmlrnmoYg6IExPQ0FMLCBPU1MsIFMzLCBNSU5JTyIsInR5cGUiOiJqYXZhLmxhbmcuU3RyaW5nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJzdG9yYWdlUGF0aCIsImNvbW1lbnQiOiLniannkIblrZjlgqjot6/lvoTmiJblr7nosaHlrZjlgqhLZXkiLCJ0eXBlIjoiamF2YS5sYW5nLlN0cmluZyIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoiYnVzaW5lc3NUeXBlIiwiY29tbWVudCI6IuaJgOWxnuS4muWKoeaooeWdlyIsInR5cGUiOiJqYXZhLmxhbmcuU3RyaW5nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJidXNpbmVzc0lkIiwiY29tbWVudCI6IuaJgOWxnuS4muWKoWlkIiwidHlwZSI6ImphdmEubGFuZy5Mb25nIiwiY3VzdG9tIjpmYWxzZSwiZXh0Ijoie30ifSx7Im5hbWUiOiJ1c2VySWQiLCJjb21tZW50Ijoi5LiK5Lyg6ICFSUQiLCJ0eXBlIjoiamF2YS5sYW5nLkxvbmciLCJjdXN0b20iOmZhbHNlLCJleHQiOiJ7fSJ9LHsibmFtZSI6ImltYWdlSW5mbyIsImNvbW1lbnQiOiLlm77niYflrr3pq5jjgIFFWElG562J5YWD5pWw5o2uIiwidHlwZSI6ImphdmEubGFuZy5TdHJpbmciLCJjdXN0b20iOmZhbHNlLCJleHQiOiJ7fSJ9LHsibmFtZSI6ImlzRGVsZXRlZCIsImNvbW1lbnQiOiLpgLvovpHliKDpmaQoMC3mraPluLgsIDEt5bey5Yig6ZmkKSIsInR5cGUiOiJqYXZhLmxhbmcuSW50ZWdlciIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoiY3JlYXRlZEF0IiwiY29tbWVudCI6IuWIm+W7uuaXtumXtCIsInR5cGUiOiJqYXZhLnV0aWwuRGF0ZSIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In0seyJuYW1lIjoidXBkYXRlZEF0IiwiY29tbWVudCI6IuabtOaWsOaXtumXtCIsInR5cGUiOiJqYXZhLnV0aWwuRGF0ZSIsImN1c3RvbSI6ZmFsc2UsImV4dCI6Int9In1dLCJzYXZlUGFja2FnZU5hbWUiOiIiLCJzYXZlUGF0aCI6IiIsInNhdmVNb2RlbE5hbWUiOiIifQ==" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/encodings.xml
generated
2
.idea/encodings.xml
generated
@@ -6,6 +6,7 @@
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-ai-service/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-auth/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-blog-service/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-client/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-bom/src/main/java" charset="UTF-8" />
|
||||
@@ -14,6 +15,7 @@
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-feign/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-log/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-mybatis/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-security/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-starter/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-starter/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/aioj-backend-common/aioj-backend-common-swagger/src/main/java" charset="UTF-8" />
|
||||
|
||||
87
.idea/mybatisx/templates.xml
generated
Normal file
87
.idea/mybatisx/templates.xml
generated
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="TemplatesSettings">
|
||||
<option name="templateConfigs">
|
||||
<TemplateContext>
|
||||
<option name="generateConfig">
|
||||
<GenerateConfig>
|
||||
<option name="annotationType" value="MYBATIS_PLUS3" />
|
||||
<option name="basePackage" value="generator" />
|
||||
<option name="basePath" value="src/main/java" />
|
||||
<option name="classNameStrategy" value="camel" />
|
||||
<option name="encoding" value="UTF-8" />
|
||||
<option name="extraClassSuffix" value="" />
|
||||
<option name="ignoreFieldPrefix" value="" />
|
||||
<option name="ignoreFieldSuffix" value="" />
|
||||
<option name="ignoreTablePrefix" value="" />
|
||||
<option name="ignoreTableSuffix" value="" />
|
||||
<option name="moduleName" value="ai-oj" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="moduleUIInfoList">
|
||||
<list>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="serviceImpl.ftl" />
|
||||
<option name="configName" value="serviceImpl" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}ServiceImpl" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}ServiceImpl.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.service.impl" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="mapperInterface.ftl" />
|
||||
<option name="configName" value="mapperInterface" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Mapper" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Mapper.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.mapper" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="serviceInterface.ftl" />
|
||||
<option name="configName" value="serviceInterface" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Service" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Service.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.service" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="src/main/resources" />
|
||||
<option name="configFileName" value="mapperXml.ftl" />
|
||||
<option name="configName" value="mapperXml" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Mapper" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Mapper.xml" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.mapper" />
|
||||
</ModuleInfoGo>
|
||||
</list>
|
||||
</option>
|
||||
<option name="needsComment" value="true" />
|
||||
<option name="needsModel" value="true" />
|
||||
<option name="relativePackage" value="domain" />
|
||||
<option name="superClass" value="" />
|
||||
<option name="tableUIInfoList">
|
||||
<list>
|
||||
<TableUIInfo>
|
||||
<option name="className" value="Question" />
|
||||
<option name="tableName" value="question" />
|
||||
</TableUIInfo>
|
||||
</list>
|
||||
</option>
|
||||
<option name="templatesName" value="mybatis-plus3" />
|
||||
<option name="useActualColumns" value="true" />
|
||||
<option name="useLombokPlugin" value="true" />
|
||||
</GenerateConfig>
|
||||
</option>
|
||||
<option name="moduleName" value="ai-oj" />
|
||||
<option name="projectPath" value="$PROJECT_DIR$" />
|
||||
<option name="templateName" value="mybatis-plus3" />
|
||||
</TemplateContext>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
78
README.md
Normal file
78
README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# AIOJ - Online Judge System
|
||||
|
||||
基于 Spring Boot 微服务架构的在线判题系统。
|
||||
|
||||
## 服务端口配置
|
||||
|
||||
| 服务名称 | 端口 | 说明 |
|
||||
|---------|------|------|
|
||||
| Gateway | 18085 | API 网关服务 |
|
||||
| Auth Service | 18081 | 认证授权服务 |
|
||||
| User Service | 18082 | 用户服务 |
|
||||
| UPMS | 18083 | 用户权限管理服务 |
|
||||
| File Service | 18066 | 文件服务 |
|
||||
|
||||
## 模块结构
|
||||
|
||||
### 核心模块 (aioj-backend-common)
|
||||
|
||||
- **aioj-backend-common-bom** - 依赖管理
|
||||
- **aioj-backend-common-core** - 核心工具类
|
||||
- **aioj-backend-common-feign** - Feign 客户端配置
|
||||
- **aioj-backend-common-log** - 日志框架
|
||||
- **aioj-backend-common-mybatis** - MyBatis 扩展
|
||||
- **aioj-backend-common-starter** - 自动配置启动器
|
||||
|
||||
### 服务模块
|
||||
|
||||
- **aioj-backend-gateway** - API 网关
|
||||
- **aioj-backend-auth** - 认证服务
|
||||
- **aioj-backend-user-service** - 用户服务
|
||||
- **aioj-backend-upms** - 权限管理服务
|
||||
- **aioj-backend-file-service** - 文件服务
|
||||
- **aioj-backend-judge-service** - 判题服务(开发中)
|
||||
- **aioj-backend-question-service** - 题库服务(开发中)
|
||||
- **aioj-backend-ai-service** - AI 服务(开发中)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 构建项目
|
||||
|
||||
```bash
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
### 运行服务
|
||||
|
||||
```bash
|
||||
# 运行网关
|
||||
mvn spring-boot:run -pl aioj-backend-gateway
|
||||
|
||||
# 运行认证服务
|
||||
mvn spring-boot:run -pl aioj-backend-auth
|
||||
|
||||
# 运行用户服务
|
||||
mvn spring-boot:run -pl aioj-backend-user-service
|
||||
```
|
||||
|
||||
### 访问地址
|
||||
|
||||
- Gateway: http://localhost:18085
|
||||
- Auth Service: http://localhost:18081/api
|
||||
- User Service: http://localhost:18082/api
|
||||
- UPMS: http://localhost:18083/api
|
||||
- File Service: http://localhost:18066/api
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 代码格式化
|
||||
|
||||
```bash
|
||||
mvn spring-javaformat:apply
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
<parent>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>ai-oj</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>aioj-backend-ai-service</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<description>AIOJ AI服务</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
</project>
|
||||
@@ -14,5 +14,111 @@
|
||||
<packaging>jar</packaging>
|
||||
<description>AIOJ AI服务</description>
|
||||
|
||||
<!-- TODO: 添加依赖 -->
|
||||
</project>
|
||||
<dependencies>
|
||||
<!-- ==================== API文档 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 内部模块 ==================== -->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Web ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== gRPC ==================== -->
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>1.68.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>1.68.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>1.68.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-services</artifactId>
|
||||
<version>1.68.1</version>
|
||||
</dependency>
|
||||
<!-- Protobuf -->
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>4.29.2</version>
|
||||
</dependency>
|
||||
<!-- 对于 Java 9+ 需要 javax.annotation -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>annotations-api</artifactId>
|
||||
<version>6.0.53</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Spring Cloud 服务发现 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 测试 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<!-- Protobuf 编译插件 -->
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:4.29.2:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.68.1:exe:${os.detected.classifier}</pluginArtifact>
|
||||
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.aiservice;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* AI 服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = "cn.meowrain.aioj")
|
||||
public class AIServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AIServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.client;
|
||||
|
||||
import cn.meowrain.aioj.backend.aiservice.grpc.*;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AI Service gRPC 客户端封装类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AIServiceGrpcClient {
|
||||
|
||||
private final AIServiceGrpc.AIServiceBlockingStub blockingStub;
|
||||
private final AIServiceGrpc.AIServiceStub asyncStub;
|
||||
|
||||
public AIServiceGrpcClient(
|
||||
@Qualifier("aiServiceBlockingStub") AIServiceGrpc.AIServiceBlockingStub blockingStub,
|
||||
@Qualifier("aiServiceStub") AIServiceGrpc.AIServiceStub asyncStub) {
|
||||
this.blockingStub = blockingStub;
|
||||
this.asyncStub = asyncStub;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析代码
|
||||
*/
|
||||
public AnalyzeCodeResponse analyzeCode(String code, String language, String questionId, String userId) {
|
||||
try {
|
||||
log.info("Calling gRPC analyzeCode for language: {}, questionId: {}", language, questionId);
|
||||
|
||||
AnalyzeCodeRequest request = AnalyzeCodeRequest.newBuilder()
|
||||
.setCode(code)
|
||||
.setLanguage(language)
|
||||
.setQuestionId(questionId)
|
||||
.setUserId(userId)
|
||||
.build();
|
||||
|
||||
AnalyzeCodeResponse response = blockingStub.analyzeCode(request);
|
||||
|
||||
log.info("gRPC analyzeCode response: success={}", response.getSuccess());
|
||||
return response;
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
log.error("gRPC analyzeCode failed: {}", e.getStatus(), e);
|
||||
return AnalyzeCodeResponse.newBuilder()
|
||||
.setSuccess(false)
|
||||
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化代码
|
||||
*/
|
||||
public OptimizeCodeResponse optimizeCode(String code, String language, String optimizationType) {
|
||||
try {
|
||||
log.info("Calling gRPC optimizeCode for language: {}, type: {}", language, optimizationType);
|
||||
|
||||
OptimizeCodeRequest request = OptimizeCodeRequest.newBuilder()
|
||||
.setCode(code)
|
||||
.setLanguage(language)
|
||||
.setOptimizationType(optimizationType)
|
||||
.build();
|
||||
|
||||
OptimizeCodeResponse response = blockingStub.optimizeCode(request);
|
||||
|
||||
log.info("gRPC optimizeCode response: success={}", response.getSuccess());
|
||||
return response;
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
log.error("gRPC optimizeCode failed: {}", e.getStatus(), e);
|
||||
return OptimizeCodeResponse.newBuilder()
|
||||
.setSuccess(false)
|
||||
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试用例
|
||||
*/
|
||||
public GenerateTestCasesResponse generateTestCases(String code, String language, String problemDescription) {
|
||||
try {
|
||||
log.info("Calling gRPC generateTestCases for language: {}", language);
|
||||
|
||||
GenerateTestCasesRequest request = GenerateTestCasesRequest.newBuilder()
|
||||
.setCode(code)
|
||||
.setLanguage(language)
|
||||
.setProblemDescription(problemDescription)
|
||||
.build();
|
||||
|
||||
GenerateTestCasesResponse response = blockingStub.generateTestCases(request);
|
||||
|
||||
log.info("gRPC generateTestCases response: success={}", response.getSuccess());
|
||||
return response;
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
log.error("gRPC generateTestCases failed: {}", e.getStatus(), e);
|
||||
return GenerateTestCasesResponse.newBuilder()
|
||||
.setSuccess(false)
|
||||
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释代码
|
||||
*/
|
||||
public ExplainCodeResponse explainCode(String code, String language, String detailLevel) {
|
||||
try {
|
||||
log.info("Calling gRPC explainCode for language: {}, level: {}", language, detailLevel);
|
||||
|
||||
ExplainCodeRequest request = ExplainCodeRequest.newBuilder()
|
||||
.setCode(code)
|
||||
.setLanguage(language)
|
||||
.setDetailLevel(detailLevel)
|
||||
.build();
|
||||
|
||||
ExplainCodeResponse response = blockingStub.explainCode(request);
|
||||
|
||||
log.info("gRPC explainCode response: success={}", response.getSuccess());
|
||||
return response;
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
log.error("gRPC explainCode failed: {}", e.getStatus(), e);
|
||||
return ExplainCodeResponse.newBuilder()
|
||||
.setSuccess(false)
|
||||
.setMessage("gRPC 调用失败: " + e.getStatus().getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.config;
|
||||
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.ManagedChannelBuilder;
|
||||
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
|
||||
import cn.meowrain.aioj.backend.aiservice.grpc.AIServiceGrpc;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* gRPC 客户端配置
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class GrpcClientConfiguration {
|
||||
|
||||
private final GrpcClientProperties properties;
|
||||
private ManagedChannel channel;
|
||||
|
||||
public GrpcClientConfiguration(GrpcClientProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 gRPC ManagedChannel
|
||||
*/
|
||||
@Bean
|
||||
@Qualifier("aiServiceChannel")
|
||||
public ManagedChannel aiServiceChannel() {
|
||||
log.info("Initializing gRPC channel to {}:{}", properties.getHost(), properties.getPort());
|
||||
|
||||
NettyChannelBuilder builder = NettyChannelBuilder
|
||||
.forAddress(properties.getHost(), properties.getPort())
|
||||
.maxInboundMessageSize(properties.getMaxMessageSize());
|
||||
|
||||
if (properties.isTlsEnabled()) {
|
||||
builder.useTransportSecurity();
|
||||
} else {
|
||||
builder.usePlaintext();
|
||||
}
|
||||
|
||||
this.channel = builder.build();
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 AI Service gRPC 客户端存根
|
||||
*/
|
||||
@Bean
|
||||
public AIServiceGrpc.AIServiceBlockingStub aiServiceBlockingStub(
|
||||
@Qualifier("aiServiceChannel") ManagedChannel channel) {
|
||||
log.info("Creating AI Service gRPC blocking stub");
|
||||
return AIServiceGrpc.newBlockingStub(channel)
|
||||
.withDeadlineAfter(properties.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 AI Service gRPC 异步客户端存根
|
||||
*/
|
||||
@Bean
|
||||
public AIServiceGrpc.AIServiceStub aiServiceStub(
|
||||
@Qualifier("aiServiceChannel") ManagedChannel channel) {
|
||||
log.info("Creating AI Service gRPC async stub");
|
||||
return AIServiceGrpc.newStub(channel)
|
||||
.withDeadlineAfter(properties.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用关闭时清理资源
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (channel != null && !channel.isShutdown()) {
|
||||
log.info("Shutting down gRPC channel");
|
||||
try {
|
||||
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Error shutting down gRPC channel", e);
|
||||
channel.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* gRPC 客户端配置属性
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "grpc.client")
|
||||
public class GrpcClientProperties {
|
||||
|
||||
/**
|
||||
* gRPC 服务器地址
|
||||
*/
|
||||
private String host = "localhost";
|
||||
|
||||
/**
|
||||
* gRPC 服务器端口
|
||||
*/
|
||||
private int port = 50051;
|
||||
|
||||
/**
|
||||
* 连接超时时间(秒)
|
||||
*/
|
||||
private int timeout = 10;
|
||||
|
||||
/**
|
||||
* 是否启用 TLS
|
||||
*/
|
||||
private boolean tlsEnabled = false;
|
||||
|
||||
/**
|
||||
* 最大消息大小(字节)
|
||||
*/
|
||||
private int maxMessageSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public boolean isTlsEnabled() {
|
||||
return tlsEnabled;
|
||||
}
|
||||
|
||||
public void setTlsEnabled(boolean tlsEnabled) {
|
||||
this.tlsEnabled = tlsEnabled;
|
||||
}
|
||||
|
||||
public int getMaxMessageSize() {
|
||||
return maxMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxMessageSize(int maxMessageSize) {
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger 配置
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("AIOJ AI 服务 API")
|
||||
.version("1.0.0")
|
||||
.description("AI 代码分析、优化、测试用例生成等服务接口")
|
||||
.contact(new Contact()
|
||||
.name("AIOJ Team")
|
||||
.email("contact@aioj.com"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||
import cn.meowrain.aioj.backend.aiservice.dto.resp.*;
|
||||
import cn.meowrain.aioj.backend.aiservice.service.AIService;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* AI 服务控制器
|
||||
*/
|
||||
@Tag(name = "AI 服务", description = "AI 代码分析、优化、测试用例生成等接口")
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
@RequiredArgsConstructor
|
||||
public class AIController {
|
||||
|
||||
private final AIService aiService;
|
||||
|
||||
@PostMapping("/analyze")
|
||||
@Operation(summary = "分析代码", description = "对提交的代码进行分析,包括复杂度、性能、可读性等评分")
|
||||
public Result<AnalyzeCodeRespDTO> analyzeCode(@Valid @RequestBody AnalyzeCodeReqDTO request) {
|
||||
AnalyzeCodeRespDTO response = (AnalyzeCodeRespDTO) aiService.analyzeCode(request);
|
||||
return Results.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/optimize")
|
||||
@Operation(summary = "优化代码", description = "对代码进行优化,提升性能、可读性或内存使用")
|
||||
public Result<OptimizeCodeRespDTO> optimizeCode(@Valid @RequestBody OptimizeCodeReqDTO request) {
|
||||
OptimizeCodeRespDTO response = (OptimizeCodeRespDTO) aiService.optimizeCode(request);
|
||||
return Results.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/test-cases")
|
||||
@Operation(summary = "生成测试用例", description = "根据代码和问题描述自动生成测试用例")
|
||||
public Result<GenerateTestCasesRespDTO> generateTestCases(@Valid @RequestBody GenerateTestCasesReqDTO request) {
|
||||
GenerateTestCasesRespDTO response = (GenerateTestCasesRespDTO) aiService.generateTestCases(request);
|
||||
return Results.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/explain")
|
||||
@Operation(summary = "解释代码", description = "对代码进行详细解释,帮助理解代码逻辑")
|
||||
public Result<ExplainCodeRespDTO> explainCode(@Valid @RequestBody ExplainCodeReqDTO request) {
|
||||
ExplainCodeRespDTO response = (ExplainCodeRespDTO) aiService.explainCode(request);
|
||||
return Results.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/health")
|
||||
@Operation(summary = "健康检查", description = "检查 AI 服务是否正常运行")
|
||||
public Result<String> health() {
|
||||
return Results.success("AI Service is running");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 代码分析请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "代码分析请求")
|
||||
public class AnalyzeCodeReqDTO {
|
||||
|
||||
@NotBlank(message = "代码不能为空")
|
||||
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "def hello():\n print('Hello World')")
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "编程语言不能为空")
|
||||
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||
private String language;
|
||||
|
||||
@Schema(description = "题目ID", example = "1001")
|
||||
private String questionId;
|
||||
|
||||
@Schema(description = "用户ID", example = "user123")
|
||||
private String userId;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 代码解释请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "代码解释请求")
|
||||
public class ExplainCodeReqDTO {
|
||||
|
||||
@NotBlank(message = "代码不能为空")
|
||||
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "编程语言不能为空")
|
||||
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||
private String language;
|
||||
|
||||
@Schema(description = "详细程度", example = "normal", allowableValues = {"brief", "normal", "detailed"})
|
||||
private String detailLevel = "normal";
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 生成测试用例请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "生成测试用例请求")
|
||||
public class GenerateTestCasesReqDTO {
|
||||
|
||||
@NotBlank(message = "代码不能为空")
|
||||
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "编程语言不能为空")
|
||||
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||
private String language;
|
||||
|
||||
@NotBlank(message = "问题描述不能为空")
|
||||
@Schema(description = "问题描述", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String problemDescription;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 代码优化请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "代码优化请求")
|
||||
public class OptimizeCodeReqDTO {
|
||||
|
||||
@NotBlank(message = "代码不能为空")
|
||||
@Schema(description = "代码内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "编程语言不能为空")
|
||||
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "python")
|
||||
private String language;
|
||||
|
||||
@Schema(description = "优化类型", example = "performance", allowableValues = {"performance", "readability", "memory"})
|
||||
private String optimizationType = "performance";
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 代码分析响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "代码分析响应")
|
||||
public class AnalyzeCodeRespDTO {
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "响应消息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "分析结果")
|
||||
private CodeAnalysisRespDTO analysis;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代码分析结果 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "代码分析结果")
|
||||
public class CodeAnalysisRespDTO {
|
||||
|
||||
@Schema(description = "发现的问题")
|
||||
private List<String> issues;
|
||||
|
||||
@Schema(description = "改进建议")
|
||||
private List<String> suggestions;
|
||||
|
||||
@Schema(description = "复杂度评分 (0-100)")
|
||||
private Integer complexityScore;
|
||||
|
||||
@Schema(description = "性能评分 (0-100)")
|
||||
private Integer performanceScore;
|
||||
|
||||
@Schema(description = "可读性评分 (0-100)")
|
||||
private Integer readabilityScore;
|
||||
|
||||
@Schema(description = "时间复杂度")
|
||||
private String timeComplexity;
|
||||
|
||||
@Schema(description = "空间复杂度")
|
||||
private String spaceComplexity;
|
||||
|
||||
@Schema(description = "最佳实践建议")
|
||||
private List<String> bestPractices;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代码解释响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "代码解释响应")
|
||||
public class ExplainCodeRespDTO {
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "响应消息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "代码解释")
|
||||
private String explanation;
|
||||
|
||||
@Schema(description = "关键点")
|
||||
private List<String> keyPoints;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生成测试用例响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "生成测试用例响应")
|
||||
public class GenerateTestCasesRespDTO {
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "响应消息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "测试用例列表")
|
||||
private List<TestCaseDTO> testCases;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代码优化响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "代码优化响应")
|
||||
public class OptimizeCodeRespDTO {
|
||||
|
||||
@Schema(description = "是否成功")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "响应消息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "优化后的代码")
|
||||
private String optimizedCode;
|
||||
|
||||
@Schema(description = "改进说明")
|
||||
private List<String> improvements;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 测试用例 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "测试用例")
|
||||
public class TestCaseDTO {
|
||||
|
||||
@Schema(description = "输入")
|
||||
private String input;
|
||||
|
||||
@Schema(description = "预期输出")
|
||||
private String expectedOutput;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.grpc;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
/**
|
||||
* AI Service gRPC 接口
|
||||
*/
|
||||
public class AIServiceGrpc {
|
||||
|
||||
private AIServiceGrpc() {}
|
||||
|
||||
public static final String SERVICE_NAME = "ai.service.AIService";
|
||||
|
||||
// 创建阻塞存根
|
||||
public static AIServiceBlockingStub newBlockingStub(Channel channel) {
|
||||
return new AIServiceBlockingStub(channel);
|
||||
}
|
||||
|
||||
// 创建异步存根
|
||||
public static AIServiceStub newStub(Channel channel) {
|
||||
return new AIServiceStub(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 阻塞存根
|
||||
*/
|
||||
public static final class AIServiceBlockingStub extends AbstractStub<AIServiceBlockingStub> {
|
||||
|
||||
private AIServiceBlockingStub(Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
private AIServiceBlockingStub(Channel channel, CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AIServiceBlockingStub build(Channel channel, CallOptions callOptions) {
|
||||
return new AIServiceBlockingStub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析代码
|
||||
*/
|
||||
public AIServiceProto.AnalyzeCodeResponse analyzeCode(AIServiceProto.AnalyzeCodeRequest request) {
|
||||
// 注意:这里需要真实的 gRPC 服务器才能调用
|
||||
// 如果没有连接服务器,会抛出 StatusRuntimeException
|
||||
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化代码
|
||||
*/
|
||||
public AIServiceProto.OptimizeCodeResponse optimizeCode(AIServiceProto.OptimizeCodeRequest request) {
|
||||
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试用例
|
||||
*/
|
||||
public AIServiceProto.GenerateTestCasesResponse generateTestCases(AIServiceProto.GenerateTestCasesRequest request) {
|
||||
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释代码
|
||||
*/
|
||||
public AIServiceProto.ExplainCodeResponse explainCode(AIServiceProto.ExplainCodeRequest request) {
|
||||
throw new io.grpc.StatusRuntimeException(io.grpc.Status.UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步存根
|
||||
*/
|
||||
public static final class AIServiceStub extends AbstractStub<AIServiceStub> {
|
||||
|
||||
private AIServiceStub(Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
private AIServiceStub(Channel channel, CallOptions callOptions) {
|
||||
super(channel, callOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AIServiceStub build(Channel channel, CallOptions callOptions) {
|
||||
return new AIServiceStub(channel, callOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析代码(异步)
|
||||
*/
|
||||
public void analyzeCode(AIServiceProto.AnalyzeCodeRequest request,
|
||||
StreamObserver<AIServiceProto.AnalyzeCodeResponse> responseObserver) {
|
||||
// 异步调用实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化代码(异步)
|
||||
*/
|
||||
public void optimizeCode(AIServiceProto.OptimizeCodeRequest request,
|
||||
StreamObserver<AIServiceProto.OptimizeCodeResponse> responseObserver) {
|
||||
// 异步调用实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试用例(异步)
|
||||
*/
|
||||
public void generateTestCases(AIServiceProto.GenerateTestCasesRequest request,
|
||||
StreamObserver<AIServiceProto.GenerateTestCasesResponse> responseObserver) {
|
||||
// 异步调用实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 解释代码(异步)
|
||||
*/
|
||||
public void explainCode(AIServiceProto.ExplainCodeRequest request,
|
||||
StreamObserver<AIServiceProto.ExplainCodeResponse> responseObserver) {
|
||||
// 异步调用实现
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StreamObserver 接口(简化版)
|
||||
*/
|
||||
public interface StreamObserver<V> {
|
||||
void onNext(V value);
|
||||
void onError(Throwable t);
|
||||
void onCompleted();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.grpc;
|
||||
|
||||
/**
|
||||
* AI Service Proto 外部类
|
||||
*/
|
||||
public final class AIServiceProto {
|
||||
private AIServiceProto() {}
|
||||
|
||||
// 代码分析请求
|
||||
public static final class AnalyzeCodeRequest extends
|
||||
com.google.protobuf.GeneratedMessageV3 implements
|
||||
com.google.protobuf.Message {
|
||||
|
||||
private AnalyzeCodeRequest() {
|
||||
this.code = "";
|
||||
this.language = "";
|
||||
this.questionId = "";
|
||||
this.userId = "";
|
||||
}
|
||||
|
||||
private String code;
|
||||
private String language;
|
||||
private String questionId;
|
||||
private String userId;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public String getLanguage() { return language; }
|
||||
public String getQuestionId() { return questionId; }
|
||||
public String getUserId() { return userId; }
|
||||
|
||||
@Override
|
||||
public com.google.protobuf.Parser<AnalyzeCodeRequest> getParserForType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AnalyzeCodeRequest getDefaultInstance() {
|
||||
return new AnalyzeCodeRequest();
|
||||
}
|
||||
|
||||
public static AnalyzeCodeRequest newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final AnalyzeCodeRequest result = new AnalyzeCodeRequest();
|
||||
|
||||
public Builder setCode(String value) {
|
||||
result.code = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLanguage(String value) {
|
||||
result.language = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setQuestionId(String value) {
|
||||
result.questionId = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUserId(String value) {
|
||||
result.userId = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnalyzeCodeRequest build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代码分析响应
|
||||
public static final class AnalyzeCodeResponse extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private AnalyzeCodeResponse() {
|
||||
this.success = false;
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private CodeAnalysis analysis;
|
||||
|
||||
public boolean getSuccess() { return success; }
|
||||
public String getMessage() { return message; }
|
||||
public boolean hasAnalysis() { return analysis != null; }
|
||||
public CodeAnalysis getAnalysis() { return analysis; }
|
||||
|
||||
public static AnalyzeCodeResponse newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final AnalyzeCodeResponse result = new AnalyzeCodeResponse();
|
||||
|
||||
public Builder setSuccess(boolean value) {
|
||||
result.success = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessage(String value) {
|
||||
result.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAnalysis(CodeAnalysis value) {
|
||||
result.analysis = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnalyzeCodeResponse build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代码分析结果
|
||||
public static final class CodeAnalysis extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private CodeAnalysis() {}
|
||||
|
||||
public java.util.List<String> getIssuesList() { return java.util.Collections.emptyList(); }
|
||||
public java.util.List<String> getSuggestionsList() { return java.util.Collections.emptyList(); }
|
||||
public int getComplexityScore() { return 0; }
|
||||
public int getPerformanceScore() { return 0; }
|
||||
public int getReadabilityScore() { return 0; }
|
||||
public String getTimeComplexity() { return ""; }
|
||||
public String getSpaceComplexity() { return ""; }
|
||||
public java.util.List<String> getBestPracticesList() { return java.util.Collections.emptyList(); }
|
||||
|
||||
public static CodeAnalysis newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final CodeAnalysis result = new CodeAnalysis();
|
||||
public CodeAnalysis build() { return result; }
|
||||
}
|
||||
}
|
||||
|
||||
// 代码优化请求
|
||||
public static final class OptimizeCodeRequest extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private OptimizeCodeRequest() {
|
||||
this.code = "";
|
||||
this.language = "";
|
||||
this.optimizationType = "";
|
||||
}
|
||||
|
||||
private String code;
|
||||
private String language;
|
||||
private String optimizationType;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public String getLanguage() { return language; }
|
||||
public String getOptimizationType() { return optimizationType; }
|
||||
|
||||
public static OptimizeCodeRequest newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final OptimizeCodeRequest result = new OptimizeCodeRequest();
|
||||
|
||||
public Builder setCode(String value) {
|
||||
result.code = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLanguage(String value) {
|
||||
result.language = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOptimizationType(String value) {
|
||||
result.optimizationType = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptimizeCodeRequest build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代码优化响应
|
||||
public static final class OptimizeCodeResponse extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private OptimizeCodeResponse() {
|
||||
this.success = false;
|
||||
this.message = "";
|
||||
this.optimizedCode = "";
|
||||
}
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String optimizedCode;
|
||||
private java.util.List<String> improvements = java.util.Collections.emptyList();
|
||||
|
||||
public boolean getSuccess() { return success; }
|
||||
public String getMessage() { return message; }
|
||||
public String getOptimizedCode() { return optimizedCode; }
|
||||
public java.util.List<String> getImprovementsList() { return improvements; }
|
||||
|
||||
public static OptimizeCodeResponse newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final OptimizeCodeResponse result = new OptimizeCodeResponse();
|
||||
|
||||
public Builder setSuccess(boolean value) {
|
||||
result.success = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessage(String value) {
|
||||
result.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOptimizedCode(String value) {
|
||||
result.optimizedCode = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setImprovementsList(java.util.List<String> value) {
|
||||
result.improvements = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptimizeCodeResponse build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成测试用例请求
|
||||
public static final class GenerateTestCasesRequest extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private GenerateTestCasesRequest() {
|
||||
this.code = "";
|
||||
this.language = "";
|
||||
this.problemDescription = "";
|
||||
}
|
||||
|
||||
private String code;
|
||||
private String language;
|
||||
private String problemDescription;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public String getLanguage() { return language; }
|
||||
public String getProblemDescription() { return problemDescription; }
|
||||
|
||||
public static GenerateTestCasesRequest newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final GenerateTestCasesRequest result = new GenerateTestCasesRequest();
|
||||
|
||||
public Builder setCode(String value) {
|
||||
result.code = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLanguage(String value) {
|
||||
result.language = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProblemDescription(String value) {
|
||||
result.problemDescription = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GenerateTestCasesRequest build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成测试用例响应
|
||||
public static final class GenerateTestCasesResponse extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private GenerateTestCasesResponse() {
|
||||
this.success = false;
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private java.util.List<TestCase> testCases = java.util.Collections.emptyList();
|
||||
|
||||
public boolean getSuccess() { return success; }
|
||||
public String getMessage() { return message; }
|
||||
public java.util.List<TestCase> getTestCasesList() { return testCases; }
|
||||
|
||||
public static GenerateTestCasesResponse newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final GenerateTestCasesResponse result = new GenerateTestCasesResponse();
|
||||
|
||||
public Builder setSuccess(boolean value) {
|
||||
result.success = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessage(String value) {
|
||||
result.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTestCasesList(java.util.List<TestCase> value) {
|
||||
result.testCases = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GenerateTestCasesResponse build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
public static final class TestCase extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private TestCase() {
|
||||
this.input = "";
|
||||
this.expectedOutput = "";
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
private String input;
|
||||
private String expectedOutput;
|
||||
private String description;
|
||||
|
||||
public String getInput() { return input; }
|
||||
public String getExpectedOutput() { return expectedOutput; }
|
||||
public String getDescription() { return description; }
|
||||
|
||||
public static TestCase newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final TestCase result = new TestCase();
|
||||
|
||||
public Builder setInput(String value) {
|
||||
result.input = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setExpectedOutput(String value) {
|
||||
result.expectedOutput = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDescription(String value) {
|
||||
result.description = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestCase build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代码解释请求
|
||||
public static final class ExplainCodeRequest extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private ExplainCodeRequest() {
|
||||
this.code = "";
|
||||
this.language = "";
|
||||
this.detailLevel = "";
|
||||
}
|
||||
|
||||
private String code;
|
||||
private String language;
|
||||
private String detailLevel;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public String getLanguage() { return language; }
|
||||
public String getDetailLevel() { return detailLevel; }
|
||||
|
||||
public static ExplainCodeRequest newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final ExplainCodeRequest result = new ExplainCodeRequest();
|
||||
|
||||
public Builder setCode(String value) {
|
||||
result.code = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLanguage(String value) {
|
||||
result.language = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDetailLevel(String value) {
|
||||
result.detailLevel = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExplainCodeRequest build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 代码解释响应
|
||||
public static final class ExplainCodeResponse extends
|
||||
com.google.protobuf.GeneratedMessageV3 {
|
||||
|
||||
private ExplainCodeResponse() {
|
||||
this.success = false;
|
||||
this.message = "";
|
||||
this.explanation = "";
|
||||
}
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String explanation;
|
||||
private java.util.List<String> keyPoints = java.util.Collections.emptyList();
|
||||
|
||||
public boolean getSuccess() { return success; }
|
||||
public String getMessage() { return message; }
|
||||
public String getExplanation() { return explanation; }
|
||||
public java.util.List<String> getKeyPointsList() { return keyPoints; }
|
||||
|
||||
public static ExplainCodeResponse newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final ExplainCodeResponse result = new ExplainCodeResponse();
|
||||
|
||||
public Builder setSuccess(boolean value) {
|
||||
result.success = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessage(String value) {
|
||||
result.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setExplanation(String value) {
|
||||
result.explanation = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeyPointsList(java.util.List<String> value) {
|
||||
result.keyPoints = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExplainCodeResponse build() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.service;
|
||||
|
||||
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||
|
||||
/**
|
||||
* AI 服务接口
|
||||
*/
|
||||
public interface AIService {
|
||||
|
||||
/**
|
||||
* 分析代码
|
||||
*/
|
||||
Object analyzeCode(AnalyzeCodeReqDTO request);
|
||||
|
||||
/**
|
||||
* 优化代码
|
||||
*/
|
||||
Object optimizeCode(OptimizeCodeReqDTO request);
|
||||
|
||||
/**
|
||||
* 生成测试用例
|
||||
*/
|
||||
Object generateTestCases(GenerateTestCasesReqDTO request);
|
||||
|
||||
/**
|
||||
* 解释代码
|
||||
*/
|
||||
Object explainCode(ExplainCodeReqDTO request);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.meowrain.aioj.backend.aiservice.service.impl;
|
||||
|
||||
import cn.meowrain.aioj.backend.aiservice.client.AIServiceGrpcClient;
|
||||
import cn.meowrain.aioj.backend.aiservice.dto.req.*;
|
||||
import cn.meowrain.aioj.backend.aiservice.dto.resp.*;
|
||||
import cn.meowrain.aioj.backend.aiservice.grpc.*;
|
||||
import cn.meowrain.aioj.backend.aiservice.service.AIService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI 服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AIServiceImpl implements AIService {
|
||||
|
||||
private final AIServiceGrpcClient grpcClient;
|
||||
|
||||
public AIServiceImpl(AIServiceGrpcClient grpcClient) {
|
||||
this.grpcClient = grpcClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnalyzeCodeRespDTO analyzeCode(AnalyzeCodeReqDTO request) {
|
||||
log.info("Analyzing code for language: {}, questionId: {}", request.getLanguage(), request.getQuestionId());
|
||||
|
||||
AnalyzeCodeResponse grpcResponse = grpcClient.analyzeCode(
|
||||
request.getCode(),
|
||||
request.getLanguage(),
|
||||
request.getQuestionId() != null ? request.getQuestionId() : "",
|
||||
request.getUserId() != null ? request.getUserId() : ""
|
||||
);
|
||||
|
||||
CodeAnalysisRespDTO analysis = null;
|
||||
if (grpcResponse.getSuccess() && grpcResponse.hasAnalysis()) {
|
||||
CodeAnalysis grpcAnalysis = grpcResponse.getAnalysis();
|
||||
analysis = CodeAnalysisRespDTO.builder()
|
||||
.issues(grpcAnalysis.getIssuesList())
|
||||
.suggestions(grpcAnalysis.getSuggestionsList())
|
||||
.complexityScore(grpcAnalysis.getComplexityScore())
|
||||
.performanceScore(grpcAnalysis.getPerformanceScore())
|
||||
.readabilityScore(grpcAnalysis.getReadabilityScore())
|
||||
.timeComplexity(grpcAnalysis.getTimeComplexity())
|
||||
.spaceComplexity(grpcAnalysis.getSpaceComplexity())
|
||||
.bestPractices(grpcAnalysis.getBestPracticesList())
|
||||
.build();
|
||||
}
|
||||
|
||||
return AnalyzeCodeRespDTO.builder()
|
||||
.success(grpcResponse.getSuccess())
|
||||
.message(grpcResponse.getMessage())
|
||||
.analysis(analysis)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptimizeCodeRespDTO optimizeCode(OptimizeCodeReqDTO request) {
|
||||
log.info("Optimizing code for language: {}, type: {}", request.getLanguage(), request.getOptimizationType());
|
||||
|
||||
OptimizeCodeResponse grpcResponse = grpcClient.optimizeCode(
|
||||
request.getCode(),
|
||||
request.getLanguage(),
|
||||
request.getOptimizationType() != null ? request.getOptimizationType() : "performance"
|
||||
);
|
||||
|
||||
return OptimizeCodeRespDTO.builder()
|
||||
.success(grpcResponse.getSuccess())
|
||||
.message(grpcResponse.getMessage())
|
||||
.optimizedCode(grpcResponse.getOptimizedCode())
|
||||
.improvements(grpcResponse.getImprovementsList())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerateTestCasesRespDTO generateTestCases(GenerateTestCasesReqDTO request) {
|
||||
log.info("Generating test cases for language: {}", request.getLanguage());
|
||||
|
||||
GenerateTestCasesResponse grpcResponse = grpcClient.generateTestCases(
|
||||
request.getCode(),
|
||||
request.getLanguage(),
|
||||
request.getProblemDescription()
|
||||
);
|
||||
|
||||
var testCases = grpcResponse.getTestCasesList().stream()
|
||||
.map(tc -> TestCaseDTO.builder()
|
||||
.input(tc.getInput())
|
||||
.expectedOutput(tc.getExpectedOutput())
|
||||
.description(tc.getDescription())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return GenerateTestCasesRespDTO.builder()
|
||||
.success(grpcResponse.getSuccess())
|
||||
.message(grpcResponse.getMessage())
|
||||
.testCases(testCases)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplainCodeRespDTO explainCode(ExplainCodeReqDTO request) {
|
||||
log.info("Explaining code for language: {}, level: {}", request.getLanguage(), request.getDetailLevel());
|
||||
|
||||
ExplainCodeResponse grpcResponse = grpcClient.explainCode(
|
||||
request.getCode(),
|
||||
request.getLanguage(),
|
||||
request.getDetailLevel() != null ? request.getDetailLevel() : "normal"
|
||||
);
|
||||
|
||||
return ExplainCodeRespDTO.builder()
|
||||
.success(grpcResponse.getSuccess())
|
||||
.message(grpcResponse.getMessage())
|
||||
.explanation(grpcResponse.getExplanation())
|
||||
.keyPoints(grpcResponse.getKeyPointsList())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
100
aioj-backend-ai-service/src/main/proto/ai_service.proto
Normal file
100
aioj-backend-ai-service/src/main/proto/ai_service.proto
Normal file
@@ -0,0 +1,100 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package ai.service;
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "cn.meowrain.aioj.backend.aiservice.grpc";
|
||||
option java_outer_classname = "AIServiceProto";
|
||||
|
||||
// AI 代码分析服务
|
||||
service AIService {
|
||||
// 代码分析
|
||||
rpc AnalyzeCode(AnalyzeCodeRequest) returns (AnalyzeCodeResponse);
|
||||
|
||||
// 代码优化建议
|
||||
rpc OptimizeCode(OptimizeCodeRequest) returns (OptimizeCodeResponse);
|
||||
|
||||
// 生成测试用例
|
||||
rpc GenerateTestCases(GenerateTestCasesRequest) returns (GenerateTestCasesResponse);
|
||||
|
||||
// 代码解释
|
||||
rpc ExplainCode(ExplainCodeRequest) returns (ExplainCodeResponse);
|
||||
}
|
||||
|
||||
// 代码分析请求
|
||||
message AnalyzeCodeRequest {
|
||||
string code = 1; // 代码内容
|
||||
string language = 2; // 编程语言 (python, java, cpp, etc.)
|
||||
string question_id = 3; // 题目ID
|
||||
string user_id = 4; // 用户ID
|
||||
}
|
||||
|
||||
// 代码分析响应
|
||||
message AnalyzeCodeResponse {
|
||||
bool success = 1; // 是否成功
|
||||
string message = 2; // 响应消息
|
||||
CodeAnalysis analysis = 3; // 分析结果
|
||||
}
|
||||
|
||||
// 代码分析结果
|
||||
message CodeAnalysis {
|
||||
repeated string issues = 1; // 发现的问题
|
||||
repeated string suggestions = 2; // 改进建议
|
||||
int32 complexity_score = 3; // 复杂度评分 (0-100)
|
||||
int32 performance_score = 4; // 性能评分 (0-100)
|
||||
int32 readability_score = 5; // 可读性评分 (0-100)
|
||||
string time_complexity = 6; // 时间复杂度
|
||||
string space_complexity = 7; // 空间复杂度
|
||||
repeated string best_practices = 8; // 最佳实践建议
|
||||
}
|
||||
|
||||
// 代码优化请求
|
||||
message OptimizeCodeRequest {
|
||||
string code = 1; // 代码内容
|
||||
string language = 2; // 编程语言
|
||||
string optimization_type = 3; // 优化类型 (performance, readability, memory)
|
||||
}
|
||||
|
||||
// 代码优化响应
|
||||
message OptimizeCodeResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
string optimized_code = 3; // 优化后的代码
|
||||
repeated string improvements = 4; // 改进说明
|
||||
}
|
||||
|
||||
// 生成测试用例请求
|
||||
message GenerateTestCasesRequest {
|
||||
string code = 1; // 代码内容
|
||||
string language = 2; // 编程语言
|
||||
string problem_description = 3; // 问题描述
|
||||
}
|
||||
|
||||
// 生成测试用例响应
|
||||
message GenerateTestCasesResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
repeated TestCase test_cases = 3; // 测试用例列表
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
message TestCase {
|
||||
string input = 1; // 输入
|
||||
string expected_output = 2; // 预期输出
|
||||
string description = 3; // 描述
|
||||
}
|
||||
|
||||
// 代码解释请求
|
||||
message ExplainCodeRequest {
|
||||
string code = 1; // 代码内容
|
||||
string language = 2; // 编程语言
|
||||
string detail_level = 3; // 详细程度 (brief, normal, detailed)
|
||||
}
|
||||
|
||||
// 代码解释响应
|
||||
message ExplainCodeResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
string explanation = 3; // 代码解释
|
||||
repeated string key_points = 4; // 关键点
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_dev
|
||||
username: root
|
||||
password: root
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
register-enabled: true
|
||||
server-addr: 10.0.0.10:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
|
||||
# AI 服务配置 - 开发环境
|
||||
ai:
|
||||
openai:
|
||||
api-key: ${OPENAI_API_KEY:sk-dev-key}
|
||||
base-url: ${OPENAI_BASE_URL:https://api.openai.com}
|
||||
model: gpt-4
|
||||
max-tokens: 2000
|
||||
temperature: 0.7
|
||||
@@ -0,0 +1,28 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD}
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
register-enabled: true
|
||||
server-addr: ${NACOS_ADDR}
|
||||
username: ${NACOS_USERNAME}
|
||||
password: ${NACOS_PASSWORD}
|
||||
|
||||
# AI 服务配置 - 生产环境
|
||||
ai:
|
||||
openai:
|
||||
api-key: ${OPENAI_API_KEY}
|
||||
base-url: ${OPENAI_BASE_URL:https://api.openai.com}
|
||||
model: ${OPENAI_MODEL:gpt-4}
|
||||
max-tokens: ${OPENAI_MAX_TOKENS:2000}
|
||||
temperature: ${OPENAI_TEMPERATURE:0.7}
|
||||
@@ -0,0 +1,25 @@
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost/aioj_test
|
||||
username: root
|
||||
password: root
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
register-enabled: true
|
||||
server-addr: localhost:8848
|
||||
|
||||
# AI 服务配置 - 测试环境
|
||||
ai:
|
||||
openai:
|
||||
api-key: ${OPENAI_API_KEY:sk-test-key}
|
||||
base-url: ${OPENAI_BASE_URL:https://api.openai.com}
|
||||
model: gpt-4
|
||||
max-tokens: 2000
|
||||
temperature: 0.7
|
||||
57
aioj-backend-ai-service/src/main/resources/application.yml
Normal file
57
aioj-backend-ai-service/src/main/resources/application.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
spring:
|
||||
application:
|
||||
name: aioj-ai-service
|
||||
profiles:
|
||||
active: @env@
|
||||
server:
|
||||
port: 18084
|
||||
servlet:
|
||||
context-path: /api
|
||||
error:
|
||||
include-stacktrace: never
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
default-flat-param-object: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
paths-to-match: '/api/**'
|
||||
packages-to-scan: cn.meowrain.aioj.backend.aiservice.controller
|
||||
knife4j:
|
||||
basic:
|
||||
enable: true
|
||||
setting:
|
||||
language: zh_cn
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
|
||||
# JWT 配置(必须与 auth-service 保持一致)
|
||||
jwt:
|
||||
enabled: true
|
||||
secret: "12345678901234567890123456789012" # 至少32字节
|
||||
access-expire: 900000 # 15分钟
|
||||
refresh-expire: 604800000 # 7天
|
||||
|
||||
# gRPC 客户端配置
|
||||
grpc:
|
||||
client:
|
||||
host: ${GRPC_SERVER_HOST:localhost}
|
||||
port: ${GRPC_SERVER_PORT:50051}
|
||||
timeout: ${GRPC_TIMEOUT:10}
|
||||
tls-enabled: ${GRPC_TLS_ENABLED:false}
|
||||
max-message-size: ${GRPC_MAX_MESSAGE_SIZE:10485760}
|
||||
|
||||
aioj:
|
||||
log:
|
||||
enabled: true
|
||||
max-length: 20000
|
||||
logging:
|
||||
file:
|
||||
path: ./logs/${spring.application.name}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<!-- 日志输出位置 -->
|
||||
<springProperty scope="context" name="LOG_HOME" source="logging.file.path" defaultValue="./logs/aioj-ai-service"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件输出 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/aioj-ai-service.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/aioj-ai-service.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 错误日志单独输出 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/aioj-ai-service-error.log</file>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/aioj-ai-service-error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 日志级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
|
||||
<!-- SQL日志 -->
|
||||
<logger name="cn.meowrain.aioj.backend.aiservice.dao.mapper" level="DEBUG"/>
|
||||
</configuration>
|
||||
@@ -1,106 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
<parent>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>ai-oj</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>aioj-backend-auth</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<description>AIOJ 认证授权服务</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-feign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -38,6 +38,10 @@
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 工具类 ==================== -->
|
||||
<dependency>
|
||||
@@ -70,26 +74,7 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== JWT ==================== -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- JWT 和 Spring Security 依赖已在 common-security 中包含 -->
|
||||
|
||||
<!-- ==================== Feign 客户端 ==================== -->
|
||||
<dependency>
|
||||
|
||||
@@ -6,13 +6,13 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "user-service", path = "/api/v1/user")
|
||||
@FeignClient(name = "aioj-user-service", path = "/api/v1/user")
|
||||
public interface UserClient {
|
||||
|
||||
@GetMapping("/inner/get-by-username")
|
||||
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
|
||||
|
||||
@GetMapping("/inner/get-by-userid")
|
||||
Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userId);
|
||||
Result<UserAuthRespDTO> getUserById(@RequestParam("userId") Long userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
package cn.meowrain.aioj.backend.auth.config;
|
||||
|
||||
import cn.meowrain.aioj.backend.auth.filter.JwtAuthenticationFilter;
|
||||
import cn.meowrain.aioj.backend.framework.security.filter.JwtAuthenticationFilter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* Auth Service Security 配置
|
||||
* 覆盖 common-security 的默认 filterChain,添加 auth 服务自定义白名单
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfiguration {
|
||||
public class AuthSecurityConfiguration {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
public SecurityFilterChain authSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Auth 服务自定义白名单
|
||||
.requestMatchers("/v1/auth/**", "/oauth2/**", "/.well-known/**", "/doc.html", "/swagger-ui/**",
|
||||
"/swagger-resources/**", "/webjars/**", "/v3/api-docs/**", "/v3/api-docs", "/favicon.ico","/v1/user/email/send-code")
|
||||
"/swagger-resources/**", "/webjars/**", "/v3/api-docs/**", "/v3/api-docs", "/favicon.ico",
|
||||
"/v1/user/email/send-code")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated())
|
||||
@@ -56,15 +56,9 @@ public class AuthController {
|
||||
}
|
||||
|
||||
@GetMapping("/getUserInfo")
|
||||
public Result<UserAuthRespDTO> getUserInfo(@RequestHeader(value = "Authorization", required = false) String authorization) {
|
||||
String token = null;
|
||||
if(authorization != null && authorization.startsWith("Bearer ")){
|
||||
token = authorization.substring(7);
|
||||
}
|
||||
if(token != null && sessionService.isTokenBlacklisted(token)) {
|
||||
return Results.success(null);
|
||||
}
|
||||
return Results.success(authService.getUserInfo(token));
|
||||
public Result<UserAuthRespDTO> getUserInfo() {
|
||||
|
||||
return Results.success(authService.getUserInfo());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package cn.meowrain.aioj.backend.auth.dto.resp;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户认证响应体
|
||||
*/
|
||||
@Data
|
||||
public class UserAuthRespDTO {
|
||||
public class UserAuthRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* id
|
||||
@@ -24,7 +25,6 @@ public class UserAuthRespDTO {
|
||||
* 用户密码
|
||||
*/
|
||||
private String userPassword;
|
||||
|
||||
/**
|
||||
* 开放平台id
|
||||
*/
|
||||
@@ -43,7 +43,7 @@ public class UserAuthRespDTO {
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
private Long userAvatar;
|
||||
|
||||
/**
|
||||
* 用户简介
|
||||
@@ -55,6 +55,16 @@ public class UserAuthRespDTO {
|
||||
*/
|
||||
private String userRole;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String userEmail;
|
||||
|
||||
/**
|
||||
* 用户邮箱是否验证 0 未验证 1已验证
|
||||
*/
|
||||
private Integer userEmailVerified;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@@ -65,4 +75,8 @@ public class UserAuthRespDTO {
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import cn.meowrain.aioj.backend.auth.oauth2.entity.OAuth2Client;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.exception.OAuth2Exception;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.service.OAuth2ClientService;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.service.OAuth2SessionService;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.framework.security.utils.JwtUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@@ -5,7 +5,7 @@ import cn.meowrain.aioj.backend.auth.clients.UserClient;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.dto.UserInfoResponse;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.exception.OAuth2Exception;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.framework.security.utils.JwtUtil;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import io.jsonwebtoken.Claims;
|
||||
@@ -64,7 +64,7 @@ public class OAuth2UserInfoController {
|
||||
Long userId = Long.parseLong(userIdStr);
|
||||
|
||||
// 4. 调用 user-service 获取用户信息
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult == null || userResult.getData() == null) {
|
||||
log.error("获取用户信息失败: userId={}", userId);
|
||||
throw new OAuth2Exception("server_error", "获取用户信息失败", 500);
|
||||
@@ -80,7 +80,7 @@ public class OAuth2UserInfoController {
|
||||
.preferredUsername(user.getUserAccount()) // 用户名
|
||||
.email(null) // TODO: 从用户信息中获取邮箱
|
||||
.emailVerified(false) // TODO: 从用户信息中获取邮箱验证状态
|
||||
.picture(user.getUserAvatar()) // 用户头像
|
||||
.picture(null) // 用户头像
|
||||
.role(user.getUserRole()) // 用户角色
|
||||
.build();
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.framework.security.utils.JwtUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -5,7 +5,7 @@ import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.dto.OAuth2TokenResponse;
|
||||
import cn.meowrain.aioj.backend.auth.oauth2.entity.OAuth2Client;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.auth.utils.AuthServiceJwtUtil;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequiredArgsConstructor
|
||||
public class OAuth2TokenService {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
private final AuthServiceJwtUtil jwtUtil;
|
||||
|
||||
private final UserClient userClient;
|
||||
|
||||
@@ -45,7 +45,7 @@ public class OAuth2TokenService {
|
||||
*/
|
||||
public OAuth2TokenResponse generateTokenResponse(OAuth2Client client, Long userId, String scope, String nonce) {
|
||||
// 1. 调用 user-service 获取用户信息
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult == null || userResult.getData() == null) {
|
||||
throw new RuntimeException("获取用户信息失败");
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class OAuth2TokenService {
|
||||
}
|
||||
|
||||
// 4. 调用 user-service 获取最新用户信息
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(Long.valueOf(userId));
|
||||
if (userResult == null || userResult.getData() == null) {
|
||||
throw new RuntimeException("获取用户信息失败");
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ public interface AuthService {
|
||||
|
||||
/**
|
||||
* 根据accessToken获取用户信息
|
||||
* @param accessToken
|
||||
* @return {@link Result<UserAuthRespDTO>}
|
||||
*/
|
||||
UserAuthRespDTO getUserInfo(String accessToken);
|
||||
UserAuthRespDTO getUserInfo();
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import cn.hutool.crypto.digest.BCrypt;
|
||||
import cn.meowrain.aioj.backend.auth.clients.UserClient;
|
||||
import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
|
||||
import cn.meowrain.aioj.backend.auth.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.auth.config.properties.JwtPropertiesConfiguration;
|
||||
import cn.meowrain.aioj.backend.framework.core.utils.ContextHolderUtils;
|
||||
import cn.meowrain.aioj.backend.framework.security.properties.JwtPropertiesConfiguration;
|
||||
import cn.meowrain.aioj.backend.auth.dto.chains.context.UserLoginRequestParamVerifyContext;
|
||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO;
|
||||
import cn.meowrain.aioj.backend.auth.service.AuthService;
|
||||
import cn.meowrain.aioj.backend.auth.utils.JwtUtil;
|
||||
import cn.meowrain.aioj.backend.auth.utils.AuthServiceJwtUtil;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ServiceException;
|
||||
@@ -28,7 +29,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
private final AuthServiceJwtUtil jwtUtil;
|
||||
|
||||
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
||||
|
||||
@@ -106,12 +107,12 @@ public class AuthServiceImpl implements AuthService {
|
||||
String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (cacheValue == null || !cacheValue.equals(refreshToken)) {
|
||||
throw new ServiceException("Refresh Token 已失效");
|
||||
throw new ServiceException(ErrorCode.NO_AUTH_ERROR);
|
||||
}
|
||||
|
||||
// 再次签发新的 Access Token
|
||||
// 此处你需要查用户,拿 userName, role
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(String.valueOf(userId));
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
if (userResult.isFail()) {
|
||||
log.error("通过id查找用户失败:{}", userResult.getMessage());
|
||||
throw new ServiceException(ErrorCode.SYSTEM_ERROR);
|
||||
@@ -156,7 +157,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
|
||||
// 4. 验证用户是否存在(可选,增加安全性)
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
|
||||
Result<UserAuthRespDTO> userResult = userClient.getUserById(Long.valueOf(userId));
|
||||
if (userResult.isFail() || userResult.getData() == null) {
|
||||
log.warn("User not found for id: {}", userId);
|
||||
return false;
|
||||
@@ -171,37 +172,13 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuthRespDTO getUserInfo(String accessToken) {
|
||||
public UserAuthRespDTO getUserInfo() {
|
||||
Long currentUserId = ContextHolderUtils.getCurrentUserId();
|
||||
|
||||
// 1. 参数校验
|
||||
if (accessToken == null || accessToken.isBlank()) {
|
||||
log.warn("Access token is null or empty");
|
||||
throw new ClientException(ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 2. token 校验
|
||||
if (!jwtUtil.isTokenValid(accessToken)) {
|
||||
log.warn("Access token is invalid or expired");
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 3. 解析 token
|
||||
String userId;
|
||||
try {
|
||||
userId = jwtUtil.parseClaims(accessToken).getSubject();
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse access token", e);
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
throw new ClientException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// 4. 查询用户信息(IO 操作)
|
||||
// 查询用户信息(IO 操作)
|
||||
Result<UserAuthRespDTO> userResult;
|
||||
try {
|
||||
userResult = userClient.getUserById(userId);
|
||||
userResult = userClient.getUserById(currentUserId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to call user service", e);
|
||||
throw new ClientException(ErrorCode.SYSTEM_ERROR);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.meowrain.aioj.backend.auth.utils;
|
||||
|
||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||
import cn.meowrain.aioj.backend.framework.security.utils.JwtUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Auth Service 专用的 JWT 工具类
|
||||
* 包装 common-security 中的 JwtUtil,提供接受 UserAuthRespDTO 的便捷方法
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthServiceJwtUtil {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
/**
|
||||
* 生成 Access Token
|
||||
* @param user 用户信息
|
||||
* @return JWT Token
|
||||
*/
|
||||
public String generateAccessToken(UserAuthRespDTO user) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", user.getId());
|
||||
claims.put("userName", user.getUserName());
|
||||
claims.put("role", user.getUserRole());
|
||||
return jwtUtil.generateAccessToken(user.getId(), claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Refresh Token
|
||||
* @param userId 用户ID
|
||||
* @return JWT Token
|
||||
*/
|
||||
public String generateRefreshToken(Long userId) {
|
||||
return jwtUtil.generateRefreshToken(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 OIDC ID Token
|
||||
* @param user 用户信息
|
||||
* @param clientId 客户端ID(aud)
|
||||
* @param nonce 防重放参数
|
||||
* @return ID Token
|
||||
*/
|
||||
public String generateIdToken(UserAuthRespDTO user, String clientId, String nonce) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("aud", clientId);
|
||||
claims.put("name", user.getUserName());
|
||||
claims.put("preferred_username", user.getUserAccount());
|
||||
if (nonce != null) {
|
||||
claims.put("nonce", nonce);
|
||||
}
|
||||
return jwtUtil.generateAccessToken(user.getId(), claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Token 是否过期
|
||||
*/
|
||||
public boolean isTokenValid(String token) {
|
||||
return jwtUtil.isTokenValid(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token
|
||||
*/
|
||||
public io.jsonwebtoken.Claims parseClaims(String token) {
|
||||
return jwtUtil.parseClaims(token);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
spring:
|
||||
application:
|
||||
name: auth-service
|
||||
name: aioj-auth-service
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: auth-service
|
||||
name: aioj-auth-service
|
||||
profiles:
|
||||
active: @env@
|
||||
devtools:
|
||||
livereload:
|
||||
enabled: true
|
||||
server:
|
||||
port: 10011
|
||||
port: 18081
|
||||
servlet:
|
||||
context-path: /api
|
||||
springdoc:
|
||||
@@ -33,6 +33,10 @@ mybatis-plus:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
jwt:
|
||||
enabled: true
|
||||
secret: "12345678901234567890123456789012" # 至少32字节!!
|
||||
access-expire: 900000 # 24小时
|
||||
refresh-expire: 604800000 # 7天
|
||||
refresh-expire: 604800000 # 7天
|
||||
logging:
|
||||
file:
|
||||
path: ./logs/${spring.application.name}
|
||||
|
||||
110
aioj-backend-auth/src/main/resources/logback-spring.xml
Normal file
110
aioj-backend-auth/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--debug="false" 表示关闭 Logback 框架自身的内部状态信息打印。-->
|
||||
<configuration scan="true" scanPeriod="10 seconds" debug="false">
|
||||
<!-- 引入 spring boot 默认日志颜色和基础配置-->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<!-- 定义变量 APP_NAME,从 Spring 环境变量中获取 spring.application.name 的值-->
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
|
||||
|
||||
<!-- 定义时间格式,yyyy-MM 表示年-月,yyyy-MM-dd 表示年-月-日-->
|
||||
<timestamp key="time-month" datePattern="yyyy-MM"/>
|
||||
<timestamp key="time-month-day" datePattern="yyyy-MM-dd"/>
|
||||
<!-- 定义变量 LOG_FILE_PATH,默认值为 ./logs/${APP_NAME},可以通过环境变量 LOG_PATH 覆盖 日志存储路径-->
|
||||
<property name="LOG_FILE_PATH" value="${LOG_PATH:-./logs/${APP_NAME}}"/>
|
||||
<!-- 定义日志格式
|
||||
格式说明:
|
||||
%d{yyyy-MM-dd HH:mm:ss.SSS}:日志记录时间,格式为年-月-日 时:分:秒.毫秒
|
||||
[%thread]:日志记录线程名称
|
||||
%-5level:日志级别,左对齐,占用 5 个字符宽度
|
||||
%logger{50}:日志记录器名称,最多显示 50 个字符
|
||||
-[%X{traceId:-}]:从 MDC 中获取 traceId 变量值,如果不存在则显示为空
|
||||
%msg%n:日志消息内容,换行符
|
||||
-->
|
||||
<property name="FILE_LOG_PATTERN"
|
||||
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -[%X{traceId:-}] %msg%n"/>
|
||||
|
||||
<!-- appender 控制台输出-->
|
||||
<!--
|
||||
<appender>: 这是 Logback 配置的根标签之一,用于定义一个日志输出目的地。
|
||||
name="CONSOLE": 为这个 Appender 赋予一个唯一的名称,方便在 <root> 或 <logger> 标签中引用它。
|
||||
class="ch.qos.logback.core.ConsoleAppender": 指定使用的实现类。
|
||||
ConsoleAppender 是 Logback 库中专门用于将日志事件写入 System.out (标准输出) 或 System.err (标准错误) 的类。
|
||||
这是我们在本地开发和测试时最常用的 Appender。
|
||||
CONSOLE_LOG_PATTERN 是上面include的默认日志格式,这里直接引用即可
|
||||
-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 全量info日志-->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>31</maxHistory>
|
||||
<totalSizeCap>100GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<!-- 只记录 INFO 级别以及以上的日志的日志 -->
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>31</maxHistory>
|
||||
<totalSizeCap>100GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 只记录 ERROR 级别以及以上的日志的日志 -->
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 异步写入日志-->
|
||||
<appender name="ASYNC_INFO"
|
||||
class="ch.qos.logback.classic.AsyncAppender">
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<queueSize>512</queueSize>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
</appender>
|
||||
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<queueSize>512</queueSize>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</appender>
|
||||
|
||||
|
||||
<!-- =========== 环境配置 打印到控制台 ===========-->
|
||||
<springProfile name="dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="ASYNC_ERROR"/>
|
||||
<appender-ref ref="ASYNC_INFO"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<springProfile name="prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="ASYNC_ERROR"/>
|
||||
<appender-ref ref="ASYNC_INFO"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
219
aioj-backend-blog-service/README.md
Normal file
219
aioj-backend-blog-service/README.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# AIOJ 博客服务模块
|
||||
|
||||
## 模块说明
|
||||
|
||||
`aioj-backend-blog-service` 是 AIOJ 系统的博客服务模块,用于用户发帖、分享技术经验、写文章。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 文章管理:发布、编辑、删除、草稿箱
|
||||
- 分类管理:支持多级分类
|
||||
- 标签系统:文章标签分类和关联
|
||||
- 评论系统:支持多级评论和回复
|
||||
- 点赞/收藏:文章和评论点赞、收藏功能
|
||||
- 浏览统计:文章浏览记录和统计
|
||||
- Markdown 支持:原生支持 Markdown 编辑和渲染
|
||||
- 搜索功能:全文搜索文章标题和内容
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Spring Boot 3.5.7
|
||||
- MyBatis Plus
|
||||
- MySQL 8.0
|
||||
- Redis
|
||||
- Nacos 服务发现
|
||||
- FlexMark (Markdown 处理)
|
||||
- Knife4j (API 文档)
|
||||
|
||||
## 端口配置
|
||||
|
||||
- 开发环境: 18086
|
||||
- 上下文路径: /api
|
||||
|
||||
## 数据库
|
||||
|
||||
数据库名称: `aioj_blog`
|
||||
|
||||
初始化 SQL 脚本: `../../db/blog.sql`
|
||||
|
||||
### 核心表结构
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| blog_article | 文章表 |
|
||||
| blog_category | 文章分类表 |
|
||||
| blog_tag | 文章标签表 |
|
||||
| blog_article_tag | 文章标签关联表 |
|
||||
| blog_comment | 评论表 |
|
||||
| blog_like | 点赞表 |
|
||||
| blog_favorite | 收藏表 |
|
||||
| blog_view | 浏览记录表 |
|
||||
| blog_draft | 草稿箱表 |
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 初始化数据库
|
||||
|
||||
```bash
|
||||
mysql -u root -p < ../../db/blog.sql
|
||||
```
|
||||
|
||||
### 2. 配置数据库连接
|
||||
|
||||
编辑 `src/main/resources/application-dev.yml`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/aioj_blog
|
||||
username: your_username
|
||||
password: your_password
|
||||
```
|
||||
|
||||
### 3. 启动服务
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
或直接运行主类: `BlogServiceApplication.java`
|
||||
|
||||
### 4. 访问 API 文档
|
||||
|
||||
启动服务后访问: http://localhost:18086/api/doc.html
|
||||
|
||||
## API 接口
|
||||
|
||||
### 文章相关
|
||||
|
||||
- `POST /api/article` - 创建文章
|
||||
- `PUT /api/article/{id}` - 更新文章
|
||||
- `DELETE /api/article/{id}` - 删除文章
|
||||
- `GET /api/article/{id}` - 获取文章详情
|
||||
- `GET /api/article/list` - 获取文章列表
|
||||
- `POST /api/article/publish` - 发布文章
|
||||
|
||||
### 分类相关
|
||||
|
||||
- `GET /api/category/list` - 获取分类列表
|
||||
- `POST /api/category` - 创建分类(管理员)
|
||||
|
||||
### 标签相关
|
||||
|
||||
- `GET /api/tag/list` - 获取标签列表
|
||||
- `GET /api/tag/hot` - 获取热门标签
|
||||
|
||||
### 评论相关
|
||||
|
||||
- `POST /api/comment` - 发表评论
|
||||
- `GET /api/comment/article/{articleId}` - 获取文章评论列表
|
||||
- `DELETE /api/comment/{id}` - 删除评论
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 代码结构
|
||||
|
||||
```
|
||||
aioj-backend-blog-service/
|
||||
├── src/main/java/cn/meowrain/aioj/backend/blogservice/
|
||||
│ ├── controller/ # 控制器层
|
||||
│ ├── service/ # 服务层
|
||||
│ │ └── impl/ # 服务实现层
|
||||
│ ├── dao/ # 数据访问层
|
||||
│ │ ├── mapper/ # MyBatis Mapper
|
||||
│ │ └── model/ # 数据模型
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ ├── vo/ # 视图对象
|
||||
│ ├── common/ # 公共类
|
||||
│ ├── config/ # 配置类
|
||||
│ └── BlogServiceApplication.java
|
||||
└── src/main/resources/
|
||||
├── mapper/ # MyBatis XML 映射文件
|
||||
├── application.yml # 主配置文件
|
||||
├── application-dev.yml
|
||||
├── application-test.yml
|
||||
├── application-prod.yml
|
||||
└── logback-spring.xml # 日志配置
|
||||
```
|
||||
|
||||
### 待实现功能
|
||||
|
||||
- [ ] 文章 CRUD 接口
|
||||
- [ ] 分类管理接口
|
||||
- [ ] 标签管理接口
|
||||
- [ ] 评论系统接口
|
||||
- [ ] 点赞/收藏接口
|
||||
- [ ] 文章搜索接口
|
||||
- [ ] Markdown 渲染服务
|
||||
- [ ] 文章定时发布
|
||||
- [ ] 文章审核功能
|
||||
- [ ] 用户关注和动态
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2025-01-20 - 初始创建
|
||||
|
||||
**创建者**: Claude Code
|
||||
|
||||
**工作内容**:
|
||||
|
||||
1. **模块结构搭建**
|
||||
- 创建完整的 Maven 模块目录结构
|
||||
- 配置 `pom.xml`,引入所需依赖
|
||||
- 创建主应用类 `BlogServiceApplication.java`
|
||||
- 更新父 `pom.xml`,添加新模块注册
|
||||
|
||||
2. **配置文件创建**
|
||||
- `application.yml` - 主配置文件
|
||||
- 服务端口: 18086
|
||||
- 上下文路径: /api
|
||||
- MyBatis Plus 配置
|
||||
- Knife4j API 文档配置
|
||||
- `application-dev.yml` - 开发环境配置
|
||||
- `application-test.yml` - 测试环境配置
|
||||
- `application-prod.yml` - 生产环境配置
|
||||
- `logback-spring.xml` - 日志配置
|
||||
|
||||
3. **数据库设计**
|
||||
- 创建 `db/blog.sql` 数据库初始化脚本
|
||||
- 设计 9 张核心表:
|
||||
- `blog_article` - 文章表(支持草稿、发布、下架等状态)
|
||||
- `blog_category` - 分类表(支持多级分类)
|
||||
- `blog_tag` - 标签表
|
||||
- `blog_article_tag` - 文章标签关联表
|
||||
- `blog_comment` - 评论表(支持多级回复)
|
||||
- `blog_like` - 点赞表
|
||||
- `blog_favorite` - 收藏表
|
||||
- `blog_view` - 浏览记录表
|
||||
- `blog_draft` - 草稿箱表
|
||||
- 添加初始分类和标签数据
|
||||
|
||||
4. **依赖说明**
|
||||
- Spring Boot 3.5.7
|
||||
- Spring Cloud & Nacos(服务发现)
|
||||
- MyBatis Plus(ORM)
|
||||
- FlexMark(Markdown 处理)
|
||||
- Knife4j(API 文档)
|
||||
- Redis(缓存)
|
||||
- MySQL(数据库)
|
||||
|
||||
5. **待实现功能**
|
||||
- [ ] 文章 CRUD 接口开发
|
||||
- [ ] 分类管理接口
|
||||
- [ ] 标签管理接口
|
||||
- [ ] 评论系统接口
|
||||
- [ ] 点赞/收藏接口
|
||||
- [ ] 文章搜索接口
|
||||
- [ ] Markdown 渲染服务
|
||||
- [ ] 文章定时发布
|
||||
- [ ] 文章审核功能
|
||||
|
||||
**注意事项**:
|
||||
- 确保先执行 `db/blog.sql` 初始化数据库
|
||||
- 检查 `application-dev.yml` 中的数据库连接配置
|
||||
- JWT 配置需与 `aioj-backend-auth` 保持一致
|
||||
- 启动前确保 Nacos 服务已运行
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2025 AIOJ Project
|
||||
99
aioj-backend-blog-service/pom.xml
Normal file
99
aioj-backend-blog-service/pom.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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>cn.meowrain.aioj</groupId>
|
||||
<artifactId>ai-oj</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>aioj-backend-blog-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<description>AIOJ 博客服务 - 用于用户发帖、分享技术经验、写文章</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- ==================== API文档 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 内部模块 ==================== -->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-feign</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Web ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 工具类 ==================== -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Markdown 处理 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.vladsch.flexmark</groupId>
|
||||
<artifactId>flexmark-all</artifactId>
|
||||
<version>0.64.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Redis & 缓存 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 数据库 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Spring Cloud 服务发现 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 测试 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.meowrain.aioj.backend.blogservice;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* AIOJ 博客服务应用启动类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@MapperScan("cn.meowrain.aioj.backend.blogservice.dao.mapper")
|
||||
@SpringBootApplication(scanBasePackages = {
|
||||
"cn.meowrain.aioj.backend.blogservice",
|
||||
"cn.meowrain.aioj.backend.framework"
|
||||
})
|
||||
public class BlogServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BlogServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.ArticleCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.ArticleQueryRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.ArticleUpdateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.ArticleListResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.ArticleResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.ArticleService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 文章管理控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/article")
|
||||
@Tag(name = "文章管理", description = "文章的创建、查询、更新、删除等接口")
|
||||
public class ArticleController {
|
||||
|
||||
private final ArticleService articleService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建文章", description = "创建新文章,支持Markdown格式")
|
||||
public Result<Long> createArticle(
|
||||
@Parameter(description = "文章创建信息", required = true)
|
||||
@Valid @RequestBody ArticleCreateRequestDTO request) {
|
||||
Long articleId = articleService.createArticle(request);
|
||||
return Results.success(articleId);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新文章", description = "更新已存在的文章信息")
|
||||
public Result<Void> updateArticle(
|
||||
@Parameter(description = "文章更新信息", required = true)
|
||||
@Valid @RequestBody ArticleUpdateRequestDTO request) {
|
||||
articleService.updateArticle(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{articleId}")
|
||||
@Operation(summary = "删除文章", description = "逻辑删除指定文章")
|
||||
public Result<Void> deleteArticle(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
articleService.deleteArticle(articleId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{articleId}")
|
||||
@Operation(summary = "查询文章详情", description = "根据文章ID查询文章详细信息")
|
||||
public Result<ArticleResponseDTO> getArticleById(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
ArticleResponseDTO article = articleService.getArticleById(articleId);
|
||||
return Results.success(article);
|
||||
}
|
||||
|
||||
@GetMapping("/slug/{slug}")
|
||||
@Operation(summary = "根据slug查询文章", description = "根据文章别名查询文章详细信息")
|
||||
public Result<ArticleResponseDTO> getArticleBySlug(
|
||||
@Parameter(description = "文章别名", required = true)
|
||||
@PathVariable("slug") String slug) {
|
||||
ArticleResponseDTO article = articleService.getArticleBySlug(slug);
|
||||
return Results.success(article);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "分页查询文章列表", description = "支持多条件查询和排序")
|
||||
public Result<IPage<ArticleListResponseDTO>> getArticleList(
|
||||
@Parameter(description = "查询条件")
|
||||
@RequestBody ArticleQueryRequestDTO request) {
|
||||
IPage<ArticleListResponseDTO> page = articleService.getArticleList(request);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@PutMapping("/publish/{articleId}")
|
||||
@Operation(summary = "发布文章", description = "将草稿状态的文章发布")
|
||||
public Result<Void> publishArticle(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
articleService.publishArticle(articleId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@PutMapping("/unpublish/{articleId}")
|
||||
@Operation(summary = "取消发布文章", description = "将已发布的文章转为草稿状态")
|
||||
public Result<Void> unpublishArticle(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
articleService.unpublishArticle(articleId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@PostMapping("/view/{articleId}")
|
||||
@Operation(summary = "增加文章浏览量", description = "记录文章浏览并增加浏览计数")
|
||||
public Result<Void> incrementViewCount(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
articleService.incrementViewCount(articleId);
|
||||
return Results.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CategoryCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CategoryUpdateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.CategoryResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.CategoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章分类控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/category")
|
||||
@Tag(name = "文章分类管理", description = "文章分类的创建、查询、更新、删除等接口")
|
||||
public class CategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建分类", description = "创建新的文章分类")
|
||||
public Result<Long> createCategory(
|
||||
@Parameter(description = "分类创建信息", required = true)
|
||||
@Valid @RequestBody CategoryCreateRequestDTO request) {
|
||||
Long categoryId = categoryService.createCategory(request);
|
||||
return Results.success(categoryId);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新分类", description = "更新已存在的分类信息")
|
||||
public Result<Void> updateCategory(
|
||||
@Parameter(description = "分类更新信息", required = true)
|
||||
@Valid @RequestBody CategoryUpdateRequestDTO request) {
|
||||
categoryService.updateCategory(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{categoryId}")
|
||||
@Operation(summary = "删除分类", description = "删除指定分类")
|
||||
public Result<Void> deleteCategory(
|
||||
@Parameter(description = "分类ID", required = true)
|
||||
@PathVariable("categoryId") Long categoryId) {
|
||||
categoryService.deleteCategory(categoryId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{categoryId}")
|
||||
@Operation(summary = "查询分类详情", description = "根据分类ID查询分类详细信息")
|
||||
public Result<CategoryResponseDTO> getCategoryById(
|
||||
@Parameter(description = "分类ID", required = true)
|
||||
@PathVariable("categoryId") Long categoryId) {
|
||||
CategoryResponseDTO category = categoryService.getCategoryById(categoryId);
|
||||
return Results.success(category);
|
||||
}
|
||||
|
||||
@GetMapping("/slug/{slug}")
|
||||
@Operation(summary = "根据slug查询分类", description = "根据分类别名查询分类详细信息")
|
||||
public Result<CategoryResponseDTO> getCategoryBySlug(
|
||||
@Parameter(description = "分类别名", required = true)
|
||||
@PathVariable("slug") String slug) {
|
||||
CategoryResponseDTO category = categoryService.getCategoryBySlug(slug);
|
||||
return Results.success(category);
|
||||
}
|
||||
|
||||
@GetMapping("/list/all")
|
||||
@Operation(summary = "查询所有分类", description = "获取所有分类列表")
|
||||
public Result<List<CategoryResponseDTO>> getAllCategories() {
|
||||
List<CategoryResponseDTO> categories = categoryService.getAllCategories();
|
||||
return Results.success(categories);
|
||||
}
|
||||
|
||||
@GetMapping("/list/enabled")
|
||||
@Operation(summary = "查询启用的分类", description = "获取所有启用状态的分类列表")
|
||||
public Result<List<CategoryResponseDTO>> getEnabledCategories() {
|
||||
List<CategoryResponseDTO> categories = categoryService.getEnabledCategories();
|
||||
return Results.success(categories);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CollectionFolderCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CollectionRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.ArticleListResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.CollectionFolderResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.CollectionService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 收藏管理控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/collection")
|
||||
@Tag(name = "收藏管理", description = "文章收藏、收藏夹管理等接口")
|
||||
public class CollectionController {
|
||||
|
||||
private final CollectionService collectionService;
|
||||
|
||||
@PostMapping("/collect")
|
||||
@Operation(summary = "收藏文章", description = "将文章添加到收藏夹")
|
||||
public Result<Void> collectArticle(
|
||||
@Parameter(description = "收藏信息", required = true)
|
||||
@Valid @RequestBody CollectionRequestDTO request) {
|
||||
collectionService.collectArticle(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/uncollect/{articleId}")
|
||||
@Operation(summary = "取消收藏文章", description = "取消收藏指定文章")
|
||||
public Result<Void> uncollectArticle(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
collectionService.uncollectArticle(articleId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/check/{articleId}")
|
||||
@Operation(summary = "查询收藏状态", description = "查询当前用户是否收藏了指定文章")
|
||||
public Result<Boolean> isCollected(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
Long currentUserId = cn.meowrain.aioj.backend.framework.core.utils.ContextHolderUtils.getCurrentUserId();
|
||||
Boolean collected = collectionService.isCollected(currentUserId, articleId);
|
||||
return Results.success(collected);
|
||||
}
|
||||
|
||||
@PostMapping("/folder/create")
|
||||
@Operation(summary = "创建收藏夹", description = "创建新的收藏夹")
|
||||
public Result<Long> createCollectionFolder(
|
||||
@Parameter(description = "收藏夹创建信息", required = true)
|
||||
@Valid @RequestBody CollectionFolderCreateRequestDTO request) {
|
||||
Long folderId = collectionService.createCollectionFolder(request);
|
||||
return Results.success(folderId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/folder/delete/{folderId}")
|
||||
@Operation(summary = "删除收藏夹", description = "删除指定收藏夹")
|
||||
public Result<Void> deleteCollectionFolder(
|
||||
@Parameter(description = "收藏夹ID", required = true)
|
||||
@PathVariable("folderId") Long folderId) {
|
||||
collectionService.deleteCollectionFolder(folderId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/folder/list")
|
||||
@Operation(summary = "查询收藏夹列表", description = "获取当前用户的所有收藏夹")
|
||||
public Result<List<CollectionFolderResponseDTO>> getCollectionFolders() {
|
||||
List<CollectionFolderResponseDTO> folders = collectionService.getCollectionFolders();
|
||||
return Results.success(folders);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页查询收藏列表", description = "获取当前用户收藏的文章列表")
|
||||
public Result<IPage<ArticleListResponseDTO>> getCollectedArticles(
|
||||
@Parameter(description = "页码", required = true)
|
||||
@RequestParam("current") Integer current,
|
||||
@Parameter(description = "每页数量", required = true)
|
||||
@RequestParam("size") Integer size,
|
||||
@Parameter(description = "收藏夹ID(可选)")
|
||||
@RequestParam(value = "folderId", required = false) Long folderId) {
|
||||
IPage<ArticleListResponseDTO> page = collectionService.getCollectedArticles(current, size, folderId);
|
||||
return Results.success(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CommentCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.CommentQueryRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.CommentResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.CommentService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 评论管理控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/comment")
|
||||
@Tag(name = "评论管理", description = "评论的发表、查询、删除等接口")
|
||||
public class CommentController {
|
||||
|
||||
private final CommentService commentService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "发表评论", description = "发表文章评论,支持Markdown格式")
|
||||
public Result<Long> createComment(
|
||||
@Parameter(description = "评论创建信息", required = true)
|
||||
@Valid @RequestBody CommentCreateRequestDTO request) {
|
||||
Long commentId = commentService.createComment(request);
|
||||
return Results.success(commentId);
|
||||
}
|
||||
|
||||
@PostMapping("/reply")
|
||||
@Operation(summary = "回复评论", description = "回复指定评论")
|
||||
public Result<Long> replyComment(
|
||||
@Parameter(description = "评论回复信息", required = true)
|
||||
@Valid @RequestBody CommentCreateRequestDTO request) {
|
||||
Long commentId = commentService.replyComment(request);
|
||||
return Results.success(commentId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{commentId}")
|
||||
@Operation(summary = "删除评论", description = "删除指定评论")
|
||||
public Result<Void> deleteComment(
|
||||
@Parameter(description = "评论ID", required = true)
|
||||
@PathVariable("commentId") Long commentId) {
|
||||
commentService.deleteComment(commentId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{commentId}")
|
||||
@Operation(summary = "查询评论详情", description = "根据评论ID查询评论详细信息")
|
||||
public Result<CommentResponseDTO> getCommentById(
|
||||
@Parameter(description = "评论ID", required = true)
|
||||
@PathVariable("commentId") Long commentId) {
|
||||
CommentResponseDTO comment = commentService.getCommentById(commentId);
|
||||
return Results.success(comment);
|
||||
}
|
||||
|
||||
@GetMapping("/article/{articleId}")
|
||||
@Operation(summary = "查询文章评论", description = "根据文章ID查询该文章的所有评论(树形结构)")
|
||||
public Result<List<CommentResponseDTO>> getCommentsByArticleId(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
List<CommentResponseDTO> comments = commentService.getCommentsByArticleId(articleId);
|
||||
return Results.success(comments);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "分页查询评论列表", description = "支持多条件查询")
|
||||
public Result<IPage<CommentResponseDTO>> getCommentList(
|
||||
@Parameter(description = "查询条件")
|
||||
@RequestBody CommentQueryRequestDTO request) {
|
||||
IPage<CommentResponseDTO> page = commentService.getCommentList(request);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@GetMapping("/children/{parentId}")
|
||||
@Operation(summary = "查询子评论", description = "查询指定评论的所有子评论")
|
||||
public Result<List<CommentResponseDTO>> getChildComments(
|
||||
@Parameter(description = "父评论ID", required = true)
|
||||
@PathVariable("parentId") Long parentId) {
|
||||
List<CommentResponseDTO> comments = commentService.getChildComments(parentId);
|
||||
return Results.success(comments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.DraftSaveRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.DraftResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.DraftService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 草稿箱控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/draft")
|
||||
@Tag(name = "草稿箱管理", description = "草稿保存、查询、删除等接口")
|
||||
public class DraftController {
|
||||
|
||||
private final DraftService draftService;
|
||||
|
||||
@PostMapping("/save")
|
||||
@Operation(summary = "保存草稿", description = "保存或更新文章草稿")
|
||||
public Result<Long> saveDraft(
|
||||
@Parameter(description = "草稿信息", required = true)
|
||||
@Valid @RequestBody DraftSaveRequestDTO request) {
|
||||
Long draftId = draftService.saveDraft(request);
|
||||
return Results.success(draftId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{draftId}")
|
||||
@Operation(summary = "删除草稿", description = "删除指定草稿")
|
||||
public Result<Void> deleteDraft(
|
||||
@Parameter(description = "草稿ID", required = true)
|
||||
@PathVariable("draftId") Long draftId) {
|
||||
draftService.deleteDraft(draftId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{draftId}")
|
||||
@Operation(summary = "查询草稿详情", description = "根据草稿ID查询草稿详细信息")
|
||||
public Result<DraftResponseDTO> getDraftById(
|
||||
@Parameter(description = "草稿ID", required = true)
|
||||
@PathVariable("draftId") Long draftId) {
|
||||
DraftResponseDTO draft = draftService.getDraftById(draftId);
|
||||
return Results.success(draft);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页查询草稿列表", description = "获取当前用户的草稿列表")
|
||||
public Result<IPage<DraftResponseDTO>> getDraftList(
|
||||
@Parameter(description = "页码", required = true)
|
||||
@RequestParam("current") Integer current,
|
||||
@Parameter(description = "每页数量", required = true)
|
||||
@RequestParam("size") Integer size) {
|
||||
IPage<DraftResponseDTO> page = draftService.getDraftList(current, size);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@GetMapping("/latest")
|
||||
@Operation(summary = "查询最新草稿", description = "获取最新的自动保存草稿")
|
||||
public Result<DraftResponseDTO> getLatestDraft() {
|
||||
DraftResponseDTO draft = draftService.getLatestDraft();
|
||||
return Results.success(draft);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.FollowRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.FollowService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户关注控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/follow")
|
||||
@Tag(name = "用户关注管理", description = "关注、取消关注、查询关注列表等接口")
|
||||
public class FollowController {
|
||||
|
||||
private final FollowService followService;
|
||||
|
||||
@PostMapping("/follow")
|
||||
@Operation(summary = "关注用户", description = "关注指定用户")
|
||||
public Result<Void> followUser(
|
||||
@Parameter(description = "关注信息", required = true)
|
||||
@Valid @RequestBody FollowRequestDTO request) {
|
||||
followService.followUser(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/unfollow/{followingId}")
|
||||
@Operation(summary = "取消关注用户", description = "取消关注指定用户")
|
||||
public Result<Void> unfollowUser(
|
||||
@Parameter(description = "被关注者ID", required = true)
|
||||
@PathVariable("followingId") Long followingId) {
|
||||
followService.unfollowUser(followingId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/check/{followingId}")
|
||||
@Operation(summary = "查询关注状态", description = "查询当前用户是否关注了指定用户")
|
||||
public Result<Boolean> isFollowing(
|
||||
@Parameter(description = "被关注者ID", required = true)
|
||||
@PathVariable("followingId") Long followingId) {
|
||||
Long currentUserId = cn.meowrain.aioj.backend.framework.core.utils.ContextHolderUtils.getCurrentUserId();
|
||||
Boolean following = followService.isFollowing(currentUserId, followingId);
|
||||
return Results.success(following);
|
||||
}
|
||||
|
||||
@GetMapping("/following/list")
|
||||
@Operation(summary = "查询关注列表", description = "分页查询当前用户关注的人")
|
||||
public Result<IPage<Long>> getFollowingList(
|
||||
@Parameter(description = "用户ID", required = true)
|
||||
@RequestParam("userId") Long userId,
|
||||
@Parameter(description = "页码", required = true)
|
||||
@RequestParam("current") Integer current,
|
||||
@Parameter(description = "每页数量", required = true)
|
||||
@RequestParam("size") Integer size) {
|
||||
IPage<Long> page = followService.getFollowingList(userId, current, size);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@GetMapping("/followers/list")
|
||||
@Operation(summary = "查询粉丝列表", description = "分页查询当前用户的粉丝")
|
||||
public Result<IPage<Long>> getFollowerList(
|
||||
@Parameter(description = "用户ID", required = true)
|
||||
@RequestParam("userId") Long userId,
|
||||
@Parameter(description = "页码", required = true)
|
||||
@RequestParam("current") Integer current,
|
||||
@Parameter(description = "每页数量", required = true)
|
||||
@RequestParam("size") Integer size) {
|
||||
IPage<Long> page = followService.getFollowerList(userId, current, size);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@GetMapping("/following/count")
|
||||
@Operation(summary = "查询关注数", description = "获取用户的关注数量")
|
||||
public Result<Long> getFollowingCount(
|
||||
@Parameter(description = "用户ID", required = true)
|
||||
@RequestParam("userId") Long userId) {
|
||||
Long count = followService.getFollowingCount(userId);
|
||||
return Results.success(count);
|
||||
}
|
||||
|
||||
@GetMapping("/followers/count")
|
||||
@Operation(summary = "查询粉丝数", description = "获取用户的粉丝数量")
|
||||
public Result<Long> getFollowerCount(
|
||||
@Parameter(description = "用户ID", required = true)
|
||||
@RequestParam("userId") Long userId) {
|
||||
Long count = followService.getFollowerCount(userId);
|
||||
return Results.success(count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.LikeRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.LikeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 点赞管理控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/like")
|
||||
@Tag(name = "点赞管理", description = "文章和评论的点赞、取消点赞等接口")
|
||||
public class LikeController {
|
||||
|
||||
private final LikeService likeService;
|
||||
|
||||
@PostMapping("/like")
|
||||
@Operation(summary = "点赞", description = "对文章或评论进行点赞")
|
||||
public Result<Void> like(
|
||||
@Parameter(description = "点赞信息", required = true)
|
||||
@Valid @RequestBody LikeRequestDTO request) {
|
||||
likeService.like(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@PostMapping("/unlike")
|
||||
@Operation(summary = "取消点赞", description = "取消对文章或评论的点赞")
|
||||
public Result<Void> unlike(
|
||||
@Parameter(description = "点赞信息", required = true)
|
||||
@Valid @RequestBody LikeRequestDTO request) {
|
||||
likeService.unlike(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/check")
|
||||
@Operation(summary = "查询点赞状态", description = "查询当前用户对指定目标的点赞状态")
|
||||
public Result<Boolean> isLiked(
|
||||
@Parameter(description = "目标ID", required = true)
|
||||
@RequestParam("targetId") Long targetId,
|
||||
@Parameter(description = "目标类型:1=文章,2=评论", required = true)
|
||||
@RequestParam("targetType") Integer targetType) {
|
||||
Long currentUserId = cn.meowrain.aioj.backend.framework.core.utils.ContextHolderUtils.getCurrentUserId();
|
||||
Boolean liked = likeService.isLiked(currentUserId, targetId, targetType);
|
||||
return Results.success(liked);
|
||||
}
|
||||
|
||||
@PostMapping("/toggle")
|
||||
@Operation(summary = "切换点赞状态", description = "点赞或取消点赞,根据当前状态自动切换")
|
||||
public Result<Void> toggleLike(
|
||||
@Parameter(description = "点赞信息", required = true)
|
||||
@Valid @RequestBody LikeRequestDTO request) {
|
||||
likeService.toggleLike(request);
|
||||
return Results.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.NotificationQueryRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.NotificationResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.NotificationService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 通知管理控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/notification")
|
||||
@Tag(name = "通知管理", description = "通知查询、标记已读、清空通知等接口")
|
||||
public class NotificationController {
|
||||
|
||||
private final NotificationService notificationService;
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "分页查询通知列表", description = "支持多条件查询")
|
||||
public Result<IPage<NotificationResponseDTO>> getNotificationList(
|
||||
@Parameter(description = "查询条件")
|
||||
@RequestBody NotificationQueryRequestDTO request) {
|
||||
IPage<NotificationResponseDTO> page = notificationService.getNotificationList(request);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
@PutMapping("/read/{notificationId}")
|
||||
@Operation(summary = "标记通知为已读", description = "将指定通知标记为已读状态")
|
||||
public Result<Void> markAsRead(
|
||||
@Parameter(description = "通知ID", required = true)
|
||||
@PathVariable("notificationId") Long notificationId) {
|
||||
notificationService.markAsRead(notificationId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@PutMapping("/read/batch")
|
||||
@Operation(summary = "批量标记通知为已读", description = "将多个通知批量标记为已读状态")
|
||||
public Result<Void> batchMarkAsRead(
|
||||
@Parameter(description = "通知ID列表", required = true)
|
||||
@RequestBody java.util.List<Long> notificationIds) {
|
||||
notificationService.batchMarkAsRead(notificationIds);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@PutMapping("/read/all")
|
||||
@Operation(summary = "标记所有通知为已读", description = "将当前用户的所有未读通知标记为已读")
|
||||
public Result<Void> markAllAsRead() {
|
||||
notificationService.markAllAsRead();
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/clear/all")
|
||||
@Operation(summary = "清空所有通知", description = "清空当前用户的所有通知")
|
||||
public Result<Void> clearAllNotifications() {
|
||||
notificationService.clearAllNotifications();
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/unread/count")
|
||||
@Operation(summary = "查询未读通知数量", description = "获取当前用户的未读通知数量")
|
||||
public Result<Long> getUnreadCount() {
|
||||
Long count = notificationService.getUnreadCount();
|
||||
return Results.success(count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.TagCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.req.TagUpdateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.dto.resp.TagResponseDTO;
|
||||
import cn.meowrain.aioj.backend.blogservice.service.TagService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章标签控制器
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/tag")
|
||||
@Tag(name = "文章标签管理", description = "文章标签的创建、查询、更新、删除等接口")
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建标签", description = "创建新的文章标签")
|
||||
public Result<Long> createTag(
|
||||
@Parameter(description = "标签创建信息", required = true)
|
||||
@Valid @RequestBody TagCreateRequestDTO request) {
|
||||
Long tagId = tagService.createTag(request);
|
||||
return Results.success(tagId);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新标签", description = "更新已存在的标签信息")
|
||||
public Result<Void> updateTag(
|
||||
@Parameter(description = "标签更新信息", required = true)
|
||||
@Valid @RequestBody TagUpdateRequestDTO request) {
|
||||
tagService.updateTag(request);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{tagId}")
|
||||
@Operation(summary = "删除标签", description = "删除指定标签")
|
||||
public Result<Void> deleteTag(
|
||||
@Parameter(description = "标签ID", required = true)
|
||||
@PathVariable("tagId") Long tagId) {
|
||||
tagService.deleteTag(tagId);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
@GetMapping("/{tagId}")
|
||||
@Operation(summary = "查询标签详情", description = "根据标签ID查询标签详细信息")
|
||||
public Result<TagResponseDTO> getTagById(
|
||||
@Parameter(description = "标签ID", required = true)
|
||||
@PathVariable("tagId") Long tagId) {
|
||||
TagResponseDTO tag = tagService.getTagById(tagId);
|
||||
return Results.success(tag);
|
||||
}
|
||||
|
||||
@GetMapping("/slug/{slug}")
|
||||
@Operation(summary = "根据slug查询标签", description = "根据标签别名查询标签详细信息")
|
||||
public Result<TagResponseDTO> getTagBySlug(
|
||||
@Parameter(description = "标签别名", required = true)
|
||||
@PathVariable("slug") String slug) {
|
||||
TagResponseDTO tag = tagService.getTagBySlug(slug);
|
||||
return Results.success(tag);
|
||||
}
|
||||
|
||||
@GetMapping("/list/all")
|
||||
@Operation(summary = "查询所有标签", description = "获取所有标签列表")
|
||||
public Result<List<TagResponseDTO>> getAllTags() {
|
||||
List<TagResponseDTO> tags = tagService.getAllTags();
|
||||
return Results.success(tags);
|
||||
}
|
||||
|
||||
@GetMapping("/list/enabled")
|
||||
@Operation(summary = "查询启用的标签", description = "获取所有启用状态的标签列表")
|
||||
public Result<List<TagResponseDTO>> getEnabledTags() {
|
||||
List<TagResponseDTO> tags = tagService.getEnabledTags();
|
||||
return Results.success(tags);
|
||||
}
|
||||
|
||||
@GetMapping("/article/{articleId}")
|
||||
@Operation(summary = "查询文章的标签", description = "根据文章ID查询该文章的所有标签")
|
||||
public Result<List<TagResponseDTO>> getTagsByArticleId(
|
||||
@Parameter(description = "文章ID", required = true)
|
||||
@PathVariable("articleId") Long articleId) {
|
||||
List<TagResponseDTO> tags = tagService.getTagsByArticleId(articleId);
|
||||
return Results.success(tags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_article")
|
||||
@Accessors(chain = true)
|
||||
public class Article implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 文章标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 文章别名(URL友好标识,用于SEO)
|
||||
*/
|
||||
private String slug;
|
||||
|
||||
/**
|
||||
* 文章摘要
|
||||
*/
|
||||
private String summary;
|
||||
|
||||
/**
|
||||
* 文章内容(Markdown格式)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 渲染后的HTML内容(可选缓存)
|
||||
*/
|
||||
private String contentHtml;
|
||||
|
||||
/**
|
||||
* 封面图片URL
|
||||
*/
|
||||
private String coverImage;
|
||||
|
||||
/**
|
||||
* 作者用户ID
|
||||
*/
|
||||
private Long authorId;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 浏览次数
|
||||
*/
|
||||
private Integer viewCount;
|
||||
|
||||
/**
|
||||
* 点赞数(冗余字段,便于查询)
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 评论数(冗余字段,便于查询)
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 收藏数(冗余字段,便于查询)
|
||||
*/
|
||||
private Integer collectCount;
|
||||
|
||||
/**
|
||||
* 是否置顶(1=置顶,0=普通)
|
||||
*/
|
||||
private Integer isTop;
|
||||
|
||||
/**
|
||||
* 是否精华(1=精华,0=普通)
|
||||
*/
|
||||
private Integer isEssence;
|
||||
|
||||
/**
|
||||
* 是否发布(1=已发布,0=草稿)
|
||||
*/
|
||||
private Integer isPublished;
|
||||
|
||||
/**
|
||||
* 文章状态:1=正常,2=审核中,3=已关闭,4=已删除
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
private Date publishTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章标签关联实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_article_tag")
|
||||
@Accessors(chain = true)
|
||||
public class ArticleTag implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 文章ID
|
||||
*/
|
||||
private Long articleId;
|
||||
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private Long tagId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章分类实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_category")
|
||||
@Accessors(chain = true)
|
||||
public class Category implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 分类别名(URL友好标识)
|
||||
*/
|
||||
private String slug;
|
||||
|
||||
/**
|
||||
* 分类描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 分类图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 排序序号(越小越靠前)
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 父分类ID(0表示顶级分类)
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 该分类下的文章数量
|
||||
*/
|
||||
private Integer articleCount;
|
||||
|
||||
/**
|
||||
* 是否启用(1=启用,0=禁用)
|
||||
*/
|
||||
private Integer isEnabled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 收藏实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_collection")
|
||||
@Accessors(chain = true)
|
||||
public class Collection implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 文章ID
|
||||
*/
|
||||
private Long articleId;
|
||||
|
||||
/**
|
||||
* 收藏夹ID(0表示默认收藏夹)
|
||||
*/
|
||||
private Long folderId;
|
||||
|
||||
/**
|
||||
* 收藏备注
|
||||
*/
|
||||
private String note;
|
||||
|
||||
/**
|
||||
* 是否已取消(1=已取消,0=有效)
|
||||
*/
|
||||
private Integer isCancelled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 收藏夹实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_collection_folder")
|
||||
@Accessors(chain = true)
|
||||
public class CollectionFolder implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 收藏夹名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 收藏夹描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否公开(1=公开,0=私有)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 收藏数量
|
||||
*/
|
||||
private Integer collectCount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 评论实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_comment")
|
||||
@Accessors(chain = true)
|
||||
public class Comment implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 文章ID
|
||||
*/
|
||||
private Long articleId;
|
||||
|
||||
/**
|
||||
* 评论用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 父评论ID(0表示一级评论)
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 回复的评论ID(用于@提醒)
|
||||
*/
|
||||
private Long replyToId;
|
||||
|
||||
/**
|
||||
* 评论内容(支持Markdown)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 渲染后的HTML内容
|
||||
*/
|
||||
private String contentHtml;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 回复数
|
||||
*/
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 是否为作者评论(1=是,0=否)
|
||||
*/
|
||||
private Integer isAuthor;
|
||||
|
||||
/**
|
||||
* 状态:1=正常,2=待审核,3=已删除
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 用户代理
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 草稿箱实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_draft")
|
||||
@Accessors(chain = true)
|
||||
public class Draft implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 文章标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 文章内容(Markdown格式)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 封面图片URL
|
||||
*/
|
||||
private String coverImage;
|
||||
|
||||
/**
|
||||
* 是否自动保存(1=是,0=否)
|
||||
*/
|
||||
private Integer autoSave;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户关注实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_follow")
|
||||
@Accessors(chain = true)
|
||||
public class Follow implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 关注者ID
|
||||
*/
|
||||
private Long followerId;
|
||||
|
||||
/**
|
||||
* 被关注者ID
|
||||
*/
|
||||
private Long followingId;
|
||||
|
||||
/**
|
||||
* 是否已取消(1=已取消,0=有效)
|
||||
*/
|
||||
private Integer isCancelled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 点赞实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_like")
|
||||
@Accessors(chain = true)
|
||||
public class Like implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 目标ID(文章ID或评论ID)
|
||||
*/
|
||||
private Long targetId;
|
||||
|
||||
/**
|
||||
* 目标类型:1=文章,2=评论
|
||||
*/
|
||||
private Integer targetType;
|
||||
|
||||
/**
|
||||
* 是否已取消(1=已取消,0=有效)
|
||||
*/
|
||||
private Integer isCancelled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 通知实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_notification")
|
||||
@Accessors(chain = true)
|
||||
public class Notification implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接收用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 通知类型:1=评论,2=点赞,3=收藏,4=关注,5=回复,6=系统通知
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 跳转链接
|
||||
*/
|
||||
private String linkUrl;
|
||||
|
||||
/**
|
||||
* 发送者ID(系统通知为NULL)
|
||||
*/
|
||||
private Long senderId;
|
||||
|
||||
/**
|
||||
* 关联目标ID(文章ID、评论ID等)
|
||||
*/
|
||||
private Long targetId;
|
||||
|
||||
/**
|
||||
* 是否已读(1=已读,0=未读)
|
||||
*/
|
||||
private Integer isRead;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文章标签实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_tag")
|
||||
@Accessors(chain = true)
|
||||
public class Tag implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 标签别名(URL友好标识)
|
||||
*/
|
||||
private String slug;
|
||||
|
||||
/**
|
||||
* 标签描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 标签颜色(十六进制)
|
||||
*/
|
||||
private String color;
|
||||
|
||||
/**
|
||||
* 使用该标签的文章数量
|
||||
*/
|
||||
private Integer articleCount;
|
||||
|
||||
/**
|
||||
* 是否启用(1=启用,0=禁用)
|
||||
*/
|
||||
private Integer isEnabled;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户社区统计实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_user_stat")
|
||||
@Accessors(chain = true)
|
||||
public class UserStat implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 发布文章数
|
||||
*/
|
||||
private Integer articleCount;
|
||||
|
||||
/**
|
||||
* 发表评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 获得点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 获得收藏数
|
||||
*/
|
||||
private Integer collectCount;
|
||||
|
||||
/**
|
||||
* 文章被浏览数
|
||||
*/
|
||||
private Integer viewCount;
|
||||
|
||||
/**
|
||||
* 粉丝数
|
||||
*/
|
||||
private Integer followerCount;
|
||||
|
||||
/**
|
||||
* 关注数
|
||||
*/
|
||||
private Integer followingCount;
|
||||
|
||||
/**
|
||||
* 用户等级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 经验值
|
||||
*/
|
||||
private Integer experience;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 浏览记录实体类
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "blog_view")
|
||||
@Accessors(chain = true)
|
||||
public class View implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 文章ID
|
||||
*/
|
||||
private Long articleId;
|
||||
|
||||
/**
|
||||
* 用户ID(NULL表示游客)
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 用户代理
|
||||
*/
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 浏览时长(秒)
|
||||
*/
|
||||
private Integer duration;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Article;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 文章Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface ArticleMapper extends BaseMapper<Article> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.ArticleTag;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 文章标签关联Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface ArticleTagMapper extends BaseMapper<ArticleTag> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Category;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 文章分类Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface CategoryMapper extends BaseMapper<Category> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.CollectionFolder;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 收藏夹Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface CollectionFolderMapper extends BaseMapper<CollectionFolder> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Collection;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 收藏Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface CollectionMapper extends BaseMapper<Collection> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Comment;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 评论Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface CommentMapper extends BaseMapper<Comment> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Draft;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 草稿箱Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface DraftMapper extends BaseMapper<Draft> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Follow;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户关注Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface FollowMapper extends BaseMapper<Follow> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Like;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 点赞Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface LikeMapper extends BaseMapper<Like> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Notification;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 通知Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface NotificationMapper extends BaseMapper<Notification> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.Tag;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 文章标签Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface TagMapper extends BaseMapper<Tag> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.UserStat;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户社区统计Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserStatMapper extends BaseMapper<UserStat> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.blogservice.dao.entity.View;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 浏览记录Mapper接口
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Mapper
|
||||
public interface ViewMapper extends BaseMapper<View> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章创建请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "文章创建请求")
|
||||
public class ArticleCreateRequestDTO {
|
||||
|
||||
@NotBlank(message = "文章标题不能为空")
|
||||
@Schema(description = "文章标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "Spring Boot 3.5.7 新特性详解")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "文章别名(URL友好标识)", example = "spring-boot-3-5-7-features")
|
||||
private String slug;
|
||||
|
||||
@Schema(description = "文章摘要", example = "本文详细介绍Spring Boot 3.5.7版本的新特性...")
|
||||
private String summary;
|
||||
|
||||
@NotBlank(message = "文章内容不能为空")
|
||||
@Schema(description = "文章内容(Markdown格式)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String content;
|
||||
|
||||
@Schema(description = "封面图片URL", example = "https://example.com/images/cover.jpg")
|
||||
private String coverImage;
|
||||
|
||||
@NotNull(message = "分类ID不能为空")
|
||||
@Schema(description = "分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "标签ID列表", example = "[1, 2, 3]")
|
||||
private List<Long> tagIds;
|
||||
|
||||
@Schema(description = "是否置顶", example = "0")
|
||||
private Integer isTop;
|
||||
|
||||
@Schema(description = "是否精华", example = "0")
|
||||
private Integer isEssence;
|
||||
|
||||
@Schema(description = "是否发布(1=发布,0=草稿)", example = "1")
|
||||
private Integer isPublished;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 文章查询请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "文章查询请求")
|
||||
public class ArticleQueryRequestDTO {
|
||||
|
||||
@Schema(description = "页码", example = "1")
|
||||
private Integer current = 1;
|
||||
|
||||
@Schema(description = "每页数量", example = "10")
|
||||
private Integer size = 10;
|
||||
|
||||
@Schema(description = "文章ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "文章标题(模糊查询)", example = "Spring Boot")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "分类ID", example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "标签ID", example = "1")
|
||||
private Long tagId;
|
||||
|
||||
@Schema(description = "作者ID", example = "1")
|
||||
private Long authorId;
|
||||
|
||||
@Schema(description = "是否置顶", example = "0")
|
||||
private Integer isTop;
|
||||
|
||||
@Schema(description = "是否精华", example = "0")
|
||||
private Integer isEssence;
|
||||
|
||||
@Schema(description = "是否发布(1=发布,0=草稿)", example = "1")
|
||||
private Integer isPublished;
|
||||
|
||||
@Schema(description = "文章状态:1=正常,2=审核中,3=已关闭,4=已删除", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "排序字段(view_count/like_count/comment_count/publish_time)", example = "publish_time")
|
||||
private String sortField;
|
||||
|
||||
@Schema(description = "排序方式(asc/desc)", example = "desc")
|
||||
private String sortOrder;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文章更新请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "文章更新请求")
|
||||
public class ArticleUpdateRequestDTO {
|
||||
|
||||
@NotNull(message = "文章ID不能为空")
|
||||
@Schema(description = "文章ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "文章标题不能为空")
|
||||
@Schema(description = "文章标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "Spring Boot 3.5.7 新特性详解(更新版)")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "文章别名(URL友好标识)", example = "spring-boot-3-5-7-features")
|
||||
private String slug;
|
||||
|
||||
@Schema(description = "文章摘要", example = "本文详细介绍Spring Boot 3.5.7版本的新特性...")
|
||||
private String summary;
|
||||
|
||||
@NotBlank(message = "文章内容不能为空")
|
||||
@Schema(description = "文章内容(Markdown格式)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String content;
|
||||
|
||||
@Schema(description = "封面图片URL", example = "https://example.com/images/cover.jpg")
|
||||
private String coverImage;
|
||||
|
||||
@NotNull(message = "分类ID不能为空")
|
||||
@Schema(description = "分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "标签ID列表", example = "[1, 2, 3]")
|
||||
private List<Long> tagIds;
|
||||
|
||||
@Schema(description = "是否置顶", example = "0")
|
||||
private Integer isTop;
|
||||
|
||||
@Schema(description = "是否精华", example = "0")
|
||||
private Integer isEssence;
|
||||
|
||||
@Schema(description = "是否发布(1=发布,0=草稿)", example = "1")
|
||||
private Integer isPublished;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分类创建请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "分类创建请求")
|
||||
public class CategoryCreateRequestDTO {
|
||||
|
||||
@NotBlank(message = "分类名称不能为空")
|
||||
@Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "算法题解")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "分类别名不能为空")
|
||||
@Schema(description = "分类别名(URL友好标识)", requiredMode = Schema.RequiredMode.REQUIRED, example = "algorithm-solutions")
|
||||
private String slug;
|
||||
|
||||
@Schema(description = "分类描述", example = "算法题目解题思路和代码分享")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "分类图标", example = "💡")
|
||||
private String icon;
|
||||
|
||||
@NotNull(message = "排序序号不能为空")
|
||||
@Schema(description = "排序序号(越小越靠前)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Schema(description = "父分类ID(0表示顶级分类)", example = "0")
|
||||
private Long parentId = 0L;
|
||||
|
||||
@Schema(description = "是否启用(1=启用,0=禁用)", example = "1")
|
||||
private Integer isEnabled = 1;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分类更新请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "分类更新请求")
|
||||
public class CategoryUpdateRequestDTO {
|
||||
|
||||
@NotNull(message = "分类ID不能为空")
|
||||
@Schema(description = "分类ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "分类名称不能为空")
|
||||
@Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "算法题解")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "分类别名不能为空")
|
||||
@Schema(description = "分类别名(URL友好标识)", requiredMode = Schema.RequiredMode.REQUIRED, example = "algorithm-solutions")
|
||||
private String slug;
|
||||
|
||||
@Schema(description = "分类描述", example = "算法题目解题思路和代码分享")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "分类图标", example = "💡")
|
||||
private String icon;
|
||||
|
||||
@NotNull(message = "排序序号不能为空")
|
||||
@Schema(description = "排序序号(越小越靠前)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Schema(description = "父分类ID(0表示顶级分类)", example = "0")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "是否启用(1=启用,0=禁用)", example = "1")
|
||||
private Integer isEnabled;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 收藏夹创建请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "收藏夹创建请求")
|
||||
public class CollectionFolderCreateRequestDTO {
|
||||
|
||||
@NotBlank(message = "收藏夹名称不能为空")
|
||||
@Schema(description = "收藏夹名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我的技术收藏")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "收藏夹描述", example = "收集技术相关的优质文章")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "是否公开(1=公开,0=私有)", example = "0")
|
||||
private Integer isPublic = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 收藏请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "收藏请求")
|
||||
public class CollectionRequestDTO {
|
||||
|
||||
@NotNull(message = "文章ID不能为空")
|
||||
@Schema(description = "文章ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long articleId;
|
||||
|
||||
@Schema(description = "收藏夹ID(0表示默认收藏夹)", example = "0")
|
||||
private Long folderId = 0L;
|
||||
|
||||
@Schema(description = "收藏备注", example = "很好的文章,值得学习")
|
||||
private String note;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.meowrain.aioj.backend.blogservice.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 评论创建请求DTO
|
||||
*
|
||||
* @author AIOJ
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "评论创建请求")
|
||||
public class CommentCreateRequestDTO {
|
||||
|
||||
@NotNull(message = "文章ID不能为空")
|
||||
@Schema(description = "文章ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long articleId;
|
||||
|
||||
@NotBlank(message = "评论内容不能为空")
|
||||
@Schema(description = "评论内容(支持Markdown)", requiredMode = Schema.RequiredMode.REQUIRED, example = "这篇文章写得很好!")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "父评论ID(0表示一级评论)", example = "0")
|
||||
private Long parentId = 0L;
|
||||
|
||||
@Schema(description = "回复的评论ID(用于@提醒)", example = "0")
|
||||
private Long replyToId = 0L;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user