Compare commits
No commits in common. 'main' and 'zwz' have entirely different histories.
@ -0,0 +1 @@
|
||||
Subproject commit fa9627a9dee077d927bb59a23291fb718ab8a807
|
Binary file not shown.
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 121 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="common" />
|
||||
<module name="service" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="common" options="-parameters" />
|
||||
<module name="luojia_channel" options="" />
|
||||
<module name="service" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="0@192.168.59.129" uuid="ad808a5f-004d-4f31-9402-19010c1be1ab">
|
||||
<driver-ref>redis</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
|
||||
<jdbc-url>jdbc:redis://192.168.59.129:6379/0</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="luojia_channel@192.168.59.129" uuid="666814a4-5179-4ab7-96f4-c2a810ed102b">
|
||||
<driver-ref>mysql.8</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<remarks>$PROJECT_DIR$/service/src/main/resources/application.yaml</remarks>
|
||||
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://192.168.59.129:3306/luojia_channel?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai</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>
|
||||
</component>
|
||||
</project>
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/service/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -1,17 +0,0 @@
|
||||
|
||||
{
|
||||
"luatools_path": "~/go/bin",
|
||||
"dto_dir": "./ngx_conf/dto",
|
||||
"swagger.docs.path": "./docs",
|
||||
"swagger.excludes": "./client",
|
||||
"swagger.file.type": "json",
|
||||
"swagger.main.lua.path": "./main.lua",
|
||||
"swagger.name": "swagger",
|
||||
"swagger.search_dirs": "./ngx_conf,./config/cn/online",
|
||||
"validator_dir": "./ngx_conf/validator",
|
||||
"yapi.config.file": "docs/swagger-yapi.json",
|
||||
"yapi.config.mode": "mergin",
|
||||
"yapi.config.server": "https://api.yapi.net",
|
||||
"yapi.config.token": "xxxxxxxxx"
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/service/src/main/resources/db/luojia_channel.sql" dialect="MySQL" />
|
||||
<file url="PROJECT" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@
|
||||
<?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
|
||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.luojia</groupId>
|
||||
<artifactId>luojia_channel</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Redisson分布式锁 -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类库 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>2.13.0</version>
|
||||
</dependency>
|
||||
<!-- JWT 依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,22 +0,0 @@
|
||||
package com.luojia_channel.common.advice;
|
||||
|
||||
|
||||
import com.luojia_channel.common.domain.Result;
|
||||
import com.luojia_channel.common.exception.BaseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
// 全局异常处理
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
// 处理自定义业务异常
|
||||
@ExceptionHandler(BaseException.class)
|
||||
public Object handleBadRequestException(BaseException e) {
|
||||
log.error("自定义异常 -> {} , 异常原因:{} ",e.getClass().getName(), e.getMessage());
|
||||
log.debug("", e);
|
||||
return Result.fail(e.getErrorCode(), e.getErrorMessage());
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.luojia_channel.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisConfig {
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.luojia_channel.common.config;
|
||||
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("珞珈岛API文档")
|
||||
.description("珞珈岛社交平台API接口文档")
|
||||
.version("1.0")
|
||||
);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.luojia_channel.common.config;
|
||||
|
||||
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
// 设置key和value的序列化规则
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new GenericFastJsonRedisSerializer());
|
||||
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
// Redisson分布式锁
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String host;
|
||||
@Value("${spring.data.redis.port}")
|
||||
private String port;
|
||||
@Value("${spring.data.redis.password}")
|
||||
private String password;
|
||||
@Bean
|
||||
public RedissonClient redissonClient(){
|
||||
//配置
|
||||
Config config=new Config();
|
||||
config.useSingleServer().setAddress("redis://"+host+":"+port).setPassword(password);
|
||||
//创建对并且返回
|
||||
return Redisson.create(config);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.luojia_channel.common.config;
|
||||
|
||||
import com.luojia_channel.common.interceptor.AuthInterceptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
private final AuthInterceptor authInterceptor;
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 拦截器
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.excludePathPatterns("/user/login",
|
||||
"/user/register",
|
||||
"/user/captcha",
|
||||
"/user/verify-captcha",
|
||||
"/post/list",
|
||||
"/post/detail",
|
||||
"/comment/list",
|
||||
"/comment/list/reply",
|
||||
"/openapi/luojia-channel",
|
||||
"/swagger-ui.html"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.luojia_channel.common.constants;
|
||||
|
||||
public class RedisConstant {
|
||||
// redis存储的refreshToken前缀
|
||||
public static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
|
||||
// redis存储的黑名单前缀
|
||||
public static final String BLACKLIST_PREFIX = "blacklist:";
|
||||
// 重建缓存分布式锁前缀
|
||||
public static final String SAFE_GET_LOCK_KEY_PREFIX = "safe_get_lock_key_prefix:";
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package com.luojia_channel.common.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
// 统一返回前端的结果
|
||||
@Data
|
||||
@Schema(description = "统一返回前端的结果")
|
||||
public class Result<T> {
|
||||
@Schema(description = "状态码")
|
||||
private int code;
|
||||
|
||||
@Schema(description = "提示消息")
|
||||
private String msg;
|
||||
|
||||
@Schema(description = "响应数据")
|
||||
private T data;
|
||||
public Result(int code, String msg, T data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "success", data);
|
||||
}
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "success", null);
|
||||
}
|
||||
public static <T> Result<T> fail(String msg) {
|
||||
return new Result<>(500, msg, null);
|
||||
}
|
||||
public static <T> Result<T> fail(int code, String msg) {
|
||||
return new Result<>(code, msg, null);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.luojia_channel.common.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class UserDTO {
|
||||
private Long userId;
|
||||
private String username;
|
||||
private String avatar;
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.luojia_channel.common.domain.page;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PageRequest {
|
||||
// 普通分页参数
|
||||
private Long current = 1L;
|
||||
private Long size = 10L;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.luojia_channel.common.domain.page;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageResponse<T> {
|
||||
// 普通分页参数
|
||||
private Long current; // 当前页数,适用于普通分页
|
||||
private Long total;
|
||||
private Long size = 10L;
|
||||
private List<T> records = Collections.emptyList();
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.luojia_channel.common.domain.page;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ScrollPageRequest {
|
||||
private Long lastVal; // 上次查询的最小值(用于游标分页)
|
||||
private Integer offset = 0; // 偏移量(用于分页位置标记)
|
||||
private Long size = 10L; // 每页数量
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.luojia_channel.common.domain.page;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// 滚动分页请求
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ScrollPageResponse<T> {
|
||||
private Long lastVal; // 上次查询的最小值(用于游标分页)
|
||||
private Integer offset = 0; // 偏移量(用于分页位置标记)
|
||||
private Long size = 10L; // 每页数量
|
||||
private List<T> records; // 数据列表
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package com.luojia_channel.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BaseException extends RuntimeException {
|
||||
private final int errorCode;
|
||||
private final String errorMessage;
|
||||
|
||||
public BaseException(int errorCode, String errorMessage) {
|
||||
super(errorMessage);
|
||||
this.errorCode = errorCode;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.luojia_channel.common.exception;
|
||||
|
||||
public class FileException extends BaseException{
|
||||
public FileException(String msg){
|
||||
super(500, msg);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.luojia_channel.common.exception;
|
||||
|
||||
public class PostException extends BaseException{
|
||||
public PostException(String msg){
|
||||
super(500, msg);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.luojia_channel.common.exception;
|
||||
|
||||
public class UserException extends BaseException{
|
||||
public UserException(String msg){
|
||||
super(500, msg);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package com.luojia_channel.common.interceptor;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.luojia_channel.common.domain.Result;
|
||||
import com.luojia_channel.common.domain.UserDTO;
|
||||
import com.luojia_channel.common.exception.UserException;
|
||||
import com.luojia_channel.common.utils.JWTUtil;
|
||||
import com.luojia_channel.common.utils.UserContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final JWTUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
// 获取请求中的双Token
|
||||
String accessToken = request.getHeader("Authorization");
|
||||
String refreshToken = request.getHeader("X-Refresh-Token");
|
||||
try {
|
||||
// 验证Token并处理自动刷新
|
||||
UserDTO user = jwtUtil.checkLogin(accessToken, refreshToken);
|
||||
// 将新Token写入响应头
|
||||
if (user.getAccessToken() != null) {
|
||||
response.setHeader("New-Access-Token", JWTUtil.TOKEN_PREFIX + user.getAccessToken());
|
||||
response.setHeader("New-Refresh-Token", user.getRefreshToken());
|
||||
}
|
||||
UserContext.setUser(user);
|
||||
return true;
|
||||
} catch (UserException ex) {
|
||||
// Token验证失败处理
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
response.getWriter().write(
|
||||
JSON.toJSONString(Result.fail(ex.getMessage()))
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package com.luojia_channel.common.utils;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.luojia_channel.common.domain.page.PageRequest;
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PageUtil {
|
||||
public static <R,T> PageResponse<R> convert(IPage<T> page, Function<? super T,? extends R> mapper) {
|
||||
List<R> targetList = page.getRecords().stream()
|
||||
.map(mapper)
|
||||
.collect(Collectors.toList());
|
||||
return PageResponse.<R>builder()
|
||||
.current(page.getCurrent())
|
||||
.size(page.getSize())
|
||||
.records(targetList)
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <R,T> PageResponse<R> convert(IPage<T> page, Class<R> clazz) {
|
||||
List<R> targetList = page.getRecords().stream()
|
||||
.map(each -> BeanUtil.copyProperties(each, clazz))
|
||||
.collect(Collectors.toList());
|
||||
return PageResponse.<R>builder()
|
||||
.current(page.getCurrent())
|
||||
.size(page.getSize())
|
||||
.records(targetList)
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Page convert(PageRequest request){
|
||||
return new Page(request.getCurrent(), request.getSize());
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
package com.luojia_channel.common.utils;
|
||||
|
||||
import com.luojia_channel.common.domain.page.PageRequest;
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageRequest;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.luojia_channel.common.constants.RedisConstant.SAFE_GET_LOCK_KEY_PREFIX;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisUtil {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final RedissonClient redissonClient;
|
||||
private static final Long DEFAULT_TIMEOUT = 30000L;
|
||||
private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
// 获取redisTemplate,因为复杂的redis操作不便于封装
|
||||
public RedisTemplate<String, Object> getInstance(){
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* redis基础操作
|
||||
*/
|
||||
|
||||
public <T> T get(String key, Class<T> type) {
|
||||
Object value = redisTemplate.opsForValue().get(key);
|
||||
return value != null ? type.cast(value) : null;
|
||||
}
|
||||
|
||||
// 安全地从缓存中取值
|
||||
public <T> T safeGet(String key, Class<T> type, Supplier<T> cacheLoader,
|
||||
long timeout, TimeUnit timeUnit) {
|
||||
T result = get(key, type);
|
||||
if(result != null){
|
||||
return result;
|
||||
}
|
||||
// 重建缓存
|
||||
RLock lock = redissonClient.getLock(SAFE_GET_LOCK_KEY_PREFIX + key);
|
||||
lock.lock();
|
||||
try{
|
||||
if(get(key, type) == null){
|
||||
T dbResult = cacheLoader.get();
|
||||
if(dbResult == null){
|
||||
return null;
|
||||
}
|
||||
set(key, dbResult, timeout, timeUnit);
|
||||
return dbResult;
|
||||
}
|
||||
}finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return get(key, type);
|
||||
}
|
||||
|
||||
// 封装基于redis zset的滚动分页查询
|
||||
public <T> ScrollPageResponse<T> scrollPageQuery(String key, Class<T> type,
|
||||
ScrollPageRequest pageRequest,
|
||||
Function<List<Long>, List<T>> dbFallback) {
|
||||
long max = pageRequest.getLastVal();
|
||||
long offset = pageRequest.getOffset();
|
||||
long size = pageRequest.getSize();
|
||||
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet()
|
||||
.reverseRangeByScoreWithScores(key, 0, max, offset, size);
|
||||
if(typedTuples == null || typedTuples.isEmpty()){
|
||||
return ScrollPageResponse.<T>builder().build();
|
||||
}
|
||||
// 获取返回的offset与minTime
|
||||
List<Long> ids = new ArrayList<>();
|
||||
int returnOffset = 1;
|
||||
long min = 0;
|
||||
for (ZSetOperations.TypedTuple<Object> tuple : typedTuples) {
|
||||
Long id = (Long)tuple.getValue();
|
||||
ids.add(id);
|
||||
long lastVal = tuple.getScore().longValue();
|
||||
if(lastVal == min){
|
||||
returnOffset++;
|
||||
}else{
|
||||
returnOffset = 1;
|
||||
min = lastVal;
|
||||
}
|
||||
}
|
||||
List<T> dbList = dbFallback.apply(ids);
|
||||
return ScrollPageResponse.<T>builder()
|
||||
.records(dbList)
|
||||
.size(pageRequest.getSize())
|
||||
.offset(returnOffset)
|
||||
.lastVal(min)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);
|
||||
}
|
||||
|
||||
public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
|
||||
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
|
||||
}
|
||||
|
||||
public void expire(String key, long time, TimeUnit timeUnit) {
|
||||
redisTemplate.expire(key, time, timeUnit);
|
||||
}
|
||||
|
||||
public void delete(String key) {
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
public boolean hasKey(String key) {
|
||||
Boolean result = redisTemplate.hasKey(key);
|
||||
return result != null && result;
|
||||
}
|
||||
|
||||
public Long getExpire(String key, TimeUnit timeUnit){
|
||||
return redisTemplate.getExpire(key, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* set操作
|
||||
*/
|
||||
private <T> List<T> convertSetToList(Set<Object> set) {
|
||||
if(set == null || set.isEmpty()){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return set.stream().map(obj -> (T) obj).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void sAdd(String key, Object... values) {
|
||||
redisTemplate.opsForSet().add(key, values);
|
||||
}
|
||||
|
||||
public void sRemove(String key, Object... values) {
|
||||
redisTemplate.opsForSet().remove(key, values);
|
||||
}
|
||||
|
||||
public <T> List<T> sGet(String key) {
|
||||
Set<Object> members = redisTemplate.opsForSet().members(key);
|
||||
return convertSetToList(members);
|
||||
}
|
||||
|
||||
public Boolean sIsMember(String key, Object value){
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
}
|
||||
|
||||
public <T> List<T> sCommon(String key, String otherKey){
|
||||
Set<Object> intersect = redisTemplate.opsForSet().intersect(key, otherKey);
|
||||
return convertSetToList(intersect);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* zSet操作
|
||||
*/
|
||||
// 带分数的结果包装类
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class ZSetItem<T> {
|
||||
private T value;
|
||||
private Double score;
|
||||
}
|
||||
private <T> List<ZSetItem<T>> convertTuples(Set<ZSetOperations.TypedTuple<Object>> tuples) {
|
||||
if(tuples == null || tuples.isEmpty()){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return tuples.stream()
|
||||
.map(tuple -> new ZSetItem<T>(
|
||||
(T) tuple.getValue(),
|
||||
tuple.getScore() != null ? tuple.getScore() : 0.0
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 添加元素
|
||||
public Boolean zAdd(String key, Object value, double score) {
|
||||
return redisTemplate.opsForZSet().add(key, value, score);
|
||||
}
|
||||
|
||||
public Boolean zAdd(String key, Object value, double score, long timeout, TimeUnit unit) {
|
||||
Boolean result = redisTemplate.opsForZSet().add(key, value, score);
|
||||
if (Boolean.TRUE.equals(result)) {
|
||||
redisTemplate.expire(key, timeout, unit);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取范围
|
||||
public <T> List<T> zRange(String key, long start, long end) {
|
||||
Set<Object> values = redisTemplate.opsForZSet().range(key, start, end);
|
||||
return convertSetToList(values);
|
||||
}
|
||||
|
||||
public <T> List<T> zRevRange(String key, long start, long end) {
|
||||
Set<Object> values = redisTemplate.opsForZSet().reverseRange(key, start, end);
|
||||
return convertSetToList(values);
|
||||
}
|
||||
|
||||
// 带分数查询(用于热度计算)
|
||||
public <T> List<ZSetItem<T>> zRangeWithScores(String key, long start, long end) {
|
||||
Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
|
||||
return convertTuples(typedTuples);
|
||||
}
|
||||
|
||||
// 增减分数(用于点赞数统计)
|
||||
public Double zIncrScore(String key, Object value, double delta) {
|
||||
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
|
||||
}
|
||||
|
||||
// 删除元素
|
||||
public Long zRemove(String key, Object... values) {
|
||||
return redisTemplate.opsForZSet().remove(key, values);
|
||||
}
|
||||
|
||||
// 获取排名(用于热榜)
|
||||
public Long zRank(String key, Object value) {
|
||||
return redisTemplate.opsForZSet().rank(key, value);
|
||||
}
|
||||
|
||||
public Long zRevRank(String key, Object value) {
|
||||
return redisTemplate.opsForZSet().reverseRank(key, value);
|
||||
}
|
||||
|
||||
// 获取集合大小
|
||||
public Long zCard(String key) {
|
||||
return redisTemplate.opsForZSet().zCard(key);
|
||||
}
|
||||
|
||||
|
||||
public <T> List<ZSetItem<T>> zRevRangeWithScores(String key, long count) {
|
||||
return zRevRangeWithScores(key, 0, count - 1);
|
||||
}
|
||||
|
||||
|
||||
public <T> List<ZSetItem<T>> zRevRangeWithScores(String key, long start, long end) {
|
||||
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
|
||||
return convertTuples(tuples);
|
||||
}
|
||||
|
||||
|
||||
public <T> T zRevMaxValue(String key) {
|
||||
List<ZSetItem<T>> items = zRevRangeWithScores(key, 1);
|
||||
return items.isEmpty() ? null : items.get(0).getValue();
|
||||
}
|
||||
|
||||
|
||||
public <T> ZSetItem<T> zRevMaxItem(String key) {
|
||||
List<ZSetItem<T>> items = zRevRangeWithScores(key, 1);
|
||||
return items.isEmpty() ? null : items.get(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.luojia_channel.common.utils;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import com.luojia_channel.common.domain.UserDTO;
|
||||
import com.luojia_channel.common.exception.UserException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
// 用户上下文
|
||||
public final class UserContext {
|
||||
|
||||
private static final ThreadLocal<UserDTO> USER_THREAD_LOCAL = new TransmittableThreadLocal<>();
|
||||
public static void setUser(UserDTO user) {
|
||||
USER_THREAD_LOCAL.set(user);
|
||||
}
|
||||
|
||||
public static Long getUserId() {
|
||||
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
|
||||
return Optional.ofNullable(userInfoDTO).map(UserDTO::getUserId).orElse(null);
|
||||
}
|
||||
|
||||
public static String getUsername() {
|
||||
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
|
||||
return Optional.ofNullable(userInfoDTO).map(UserDTO::getUsername).orElse(null);
|
||||
}
|
||||
|
||||
public static String getAvatar() {
|
||||
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
|
||||
return Optional.ofNullable(userInfoDTO).map(UserDTO::getAvatar).orElse(null);
|
||||
}
|
||||
|
||||
public static String getAccessToken() {
|
||||
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
|
||||
return Optional.ofNullable(userInfoDTO).map(UserDTO::getAccessToken).orElse(null);
|
||||
}
|
||||
|
||||
public static String getRefreshToken() {
|
||||
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
|
||||
return Optional.ofNullable(userInfoDTO).map(UserDTO::getRefreshToken).orElse(null);
|
||||
}
|
||||
|
||||
public static void removeUser() {
|
||||
USER_THREAD_LOCAL.remove();
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,19 +0,0 @@
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\advice\GlobalExceptionHandler.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\config\MybatisConfig.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\config\OpenApiConfig.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\config\RedisConfig.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\config\WebMvcConfig.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\constants\RedisConstant.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\domain\page\PageRequest.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\domain\page\PageResponse.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\domain\Result.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\domain\UserDTO.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\exception\BaseException.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\exception\FileException.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\exception\PostException.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\exception\UserException.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\interceptor\AuthInterceptor.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\utils\JWTUtil.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\utils\PageUtil.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\utils\RedisUtil.java
|
||||
D:\yys\学习资料?\大二下\软工\大作业\software_teamwork\珞珈岛-项目相关文件\luojia-island\common\src\main\java\com\luojia_channel\common\utils\UserContext.java
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "luojia-island",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
<?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
|
||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.luojia</groupId>
|
||||
<artifactId>luojia_channel</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.0.0</version>
|
||||
<modules>
|
||||
<module>common</module>
|
||||
<module>service</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-cloud.version>2024.0.0</spring-cloud.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<redisson.version>3.29.0</redisson.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- Spring Cloud BOM -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- springboot3配套的MybatisPlus-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redisson -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Driver -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- Lombok (全局管理) -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- fastjson -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis-Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- hutool -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.24</version>
|
||||
</dependency>
|
||||
|
||||
<!-- minio -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>8.5.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.15</version>
|
||||
</dependency>
|
||||
|
||||
<!-- websocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- openAPI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.8.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- es -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,69 +0,0 @@
|
||||
<?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
|
||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.luojia</groupId>
|
||||
<artifactId>luojia_channel</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>service</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- 公共模块依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.luojia</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RabbitMQ -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL驱动 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 分布式系统支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</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-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,13 +0,0 @@
|
||||
package com.luojia_channel;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LuojiaChannelApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LuojiaChannelApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package com.luojia_channel.modules.file.config;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MinioConfig {
|
||||
@Value("${minio.endpoint}")
|
||||
private String endpoint;
|
||||
@Value("${minio.accessKey}")
|
||||
private String accessKey;
|
||||
@Value("${minio.secretKey}")
|
||||
private String secretKey;
|
||||
|
||||
@Bean
|
||||
public MinioClient minioClient() {
|
||||
try {
|
||||
MinioClient minioClient = MinioClient.builder()
|
||||
.endpoint(endpoint)
|
||||
.credentials(accessKey, secretKey)
|
||||
.build();
|
||||
return minioClient;
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.luojia_channel.modules.file.constants;
|
||||
|
||||
public class FileConstant {
|
||||
public static final String CHUNK_BUCKET = "chunks";
|
||||
public static final String CHUNK_PREFIX = "file:chunks:";
|
||||
public static final long MAX_UPLOAD_SIZE = 10*1024*1024;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.luojia_channel.modules.file.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
// 合并分片DTO
|
||||
@Data
|
||||
@Builder
|
||||
public class CompleteUploadDTO {
|
||||
private String fileMd5;
|
||||
|
||||
private Integer totalChunks;
|
||||
|
||||
private String fileType;
|
||||
|
||||
private String fileName;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.luojia_channel.modules.file.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.luojia_channel.modules.file.entity.LjFile;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LjFileMapper extends BaseMapper<LjFile> {
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.luojia_channel.modules.file.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.luojia_channel.modules.file.dto.CompleteUploadDTO;
|
||||
import com.luojia_channel.modules.file.dto.UploadChunkDTO;
|
||||
import com.luojia_channel.modules.file.dto.UploadFileDTO;
|
||||
import com.luojia_channel.modules.file.entity.LjFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface FileService extends IService<LjFile> {
|
||||
Boolean createBucket(String name);
|
||||
Boolean deleteBucket(String name);
|
||||
String uploadFile(MultipartFile file);
|
||||
Long uploadFileAndGetFileId(UploadFileDTO uploadFileDTO);
|
||||
Boolean uploadChunk(UploadChunkDTO chunkDTO);
|
||||
Long completeUpload(CompleteUploadDTO completeDTO);
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package com.luojia_channel.modules.file.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.luojia_channel.common.exception.FileException;
|
||||
import com.luojia_channel.common.utils.RedisUtil;
|
||||
import com.luojia_channel.modules.file.dto.UploadChunkDTO;
|
||||
import com.luojia_channel.modules.file.dto.UploadFileDTO;
|
||||
import com.luojia_channel.modules.file.entity.LjFile;
|
||||
import com.luojia_channel.modules.file.mapper.LjFileMapper;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.StatObjectArgs;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.luojia_channel.modules.file.constants.FileConstant.*;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ValidateFileUtil {
|
||||
private final LjFileMapper ljFileMapper;
|
||||
private final RedisUtil redisUtil;
|
||||
private final GeneratePathUtil generatePathUtil;
|
||||
private final MinioClient minioClient;
|
||||
// 验证分片参数
|
||||
public void validateChunk(UploadChunkDTO chunkDTO) {
|
||||
if (chunkDTO.getFile().isEmpty()) {
|
||||
throw new FileException("分片数据不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(chunkDTO.getFileMd5())) {
|
||||
throw new FileException("文件MD5缺失");
|
||||
}
|
||||
if (chunkDTO.getChunkNumber() < 0) {
|
||||
throw new FileException("分片编号不能为负数");
|
||||
}
|
||||
if (chunkDTO.getTotalChunks() <= 0) {
|
||||
throw new FileException("总分片数必须大于0");
|
||||
}
|
||||
if(chunkDTO.getChunkNumber() >= chunkDTO.getTotalChunks()){
|
||||
throw new FileException("分片序号不能大于分片总数");
|
||||
}
|
||||
}
|
||||
// 校验普通文件上传
|
||||
public void validateFile(UploadFileDTO dto) {
|
||||
if (dto.getFile().isEmpty()) {
|
||||
throw new FileException("文件不能为空");
|
||||
}
|
||||
// MD5校验
|
||||
if (StrUtil.isBlank(dto.getFileMd5())) {
|
||||
throw new FileException("文件MD5缺失");
|
||||
}
|
||||
// 类型校验
|
||||
if (!Arrays.asList("image", "video").contains(dto.getFileType())) {
|
||||
throw new FileException("不支持的文件类型");
|
||||
}
|
||||
// 大小校验
|
||||
if(dto.getFile().getSize() > MAX_UPLOAD_SIZE){
|
||||
throw new FileException("上传文件大小过大");
|
||||
}
|
||||
}
|
||||
|
||||
// 秒传检查
|
||||
public Long getExistedFileId(String fileMd5) {
|
||||
LjFile file = ljFileMapper.selectOne(Wrappers.<LjFile>lambdaQuery()
|
||||
.eq(LjFile::getFileMd5, fileMd5));
|
||||
if(file == null){
|
||||
return null;
|
||||
}
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
// 检查分片是否已上传
|
||||
public boolean isChunkUploaded(String fileMd5, int chunkNumber) {
|
||||
String objectName = generatePathUtil.getChunkObjectName(fileMd5, chunkNumber);
|
||||
String redisKey = CHUNK_PREFIX + fileMd5;
|
||||
// 检查redis中是否已记录该分片
|
||||
if (redisUtil.sIsMember(redisKey, chunkNumber)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
minioClient.statObject(
|
||||
StatObjectArgs.builder()
|
||||
.bucket(CHUNK_BUCKET)
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查所有分片是否已上传
|
||||
public boolean areAllChunksUploaded(String fileMd5, int totalChunks) {
|
||||
String key = CHUNK_PREFIX + fileMd5;
|
||||
List<Integer> uploadedChunks = redisUtil.sGet(key);
|
||||
return uploadedChunks.size() == totalChunks;
|
||||
}
|
||||
|
||||
public String getExistedFileUrl(String fileMd5) {
|
||||
LjFile file = ljFileMapper.selectOne(Wrappers.<LjFile>lambdaQuery()
|
||||
.eq(LjFile::getFileMd5, fileMd5));
|
||||
if(file == null){
|
||||
return null;
|
||||
}
|
||||
return file.getFileUrl();
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.controller;
|
||||
|
||||
import com.luojia_channel.common.domain.Result;
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import com.luojia_channel.modules.interact.dto.ChatItemDTO;
|
||||
import com.luojia_channel.modules.interact.dto.ChatPageQueryDTO;
|
||||
import com.luojia_channel.modules.interact.service.ChatService;
|
||||
import com.luojia_channel.modules.message.dto.MessageResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/message")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "聊天模块", description = "好友聊天模块相关接口")
|
||||
public class ChatController {
|
||||
private final ChatService chatService;
|
||||
|
||||
@Operation(
|
||||
summary = "聊天列表",
|
||||
description = "传入分页参数,查询私信用户列表(带最新消息)",
|
||||
tags = {"聊天模块"}
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "获取成功"),
|
||||
@ApiResponse(responseCode = "500", description = "获取失败,请稍后重试")
|
||||
})
|
||||
@GetMapping("/chat-list")
|
||||
public Result<ScrollPageResponse<ChatItemDTO>> getChatList(@RequestBody ChatPageQueryDTO chatPageQueryDTO) {
|
||||
return Result.success(chatService.getChatList(chatPageQueryDTO));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "历史记录",
|
||||
description = "传入分页参数,获取与特定用户的完整聊天记录",
|
||||
tags = {"聊天模块"}
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "获取成功"),
|
||||
@ApiResponse(responseCode = "500", description = "获取失败,请稍后重试")
|
||||
})
|
||||
@GetMapping("/history")
|
||||
public Result<ScrollPageResponse<MessageResponse>> getChatHistory(@RequestBody ChatPageQueryDTO chatPageQueryDTO) {
|
||||
return Result.success(chatService.getChatHistory(chatPageQueryDTO));
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "聊天列表项DTO")
|
||||
public class ChatItemDTO {
|
||||
@Schema(
|
||||
description = "聊天对象的用户ID",
|
||||
required = true,
|
||||
example = "123456"
|
||||
)
|
||||
private Long chatUserId;
|
||||
|
||||
@Schema(
|
||||
description = "聊天对象的头像URL",
|
||||
example = "https://example.com/avatar.jpg"
|
||||
)
|
||||
private String avatar;
|
||||
|
||||
@Schema(
|
||||
description = "聊天对象的用户名",
|
||||
required = true,
|
||||
example = "张三"
|
||||
)
|
||||
private String username;
|
||||
|
||||
@Schema(
|
||||
description = "最新消息内容",
|
||||
required = true,
|
||||
maxLength = 500,
|
||||
example = "今天下午开会"
|
||||
)
|
||||
private String latestMessage;
|
||||
|
||||
@Schema(
|
||||
description = "最新消息时间",
|
||||
required = true,
|
||||
example = "2023-10-15T14:30:00"
|
||||
)
|
||||
private LocalDateTime latestTime;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.dto;
|
||||
|
||||
import com.luojia_channel.common.domain.page.ScrollPageRequest;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChatPageQueryDTO extends ScrollPageRequest {
|
||||
private Long chatUserId;
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("follow")
|
||||
@Schema(description = "关注表")
|
||||
public class Follow implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@Schema(
|
||||
description = "用户id"
|
||||
)
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 关联的用户id
|
||||
*/
|
||||
@Schema(
|
||||
description = "关联的用户id"
|
||||
)
|
||||
private Long followUserId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(
|
||||
description = "创建时间"
|
||||
)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.mapper;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.luojia_channel.modules.interact.entity.Follow;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface FollowMapper extends BaseMapper<Follow> {
|
||||
@Delete("delete from follow where user_id = #{userId} and follow_user_id = #{followUserId}")
|
||||
boolean delete(Long userId, Long followUserId);
|
||||
|
||||
@Select("select count(*) from follow where user_id = #{userId} and follow_user_id = #{followUserId}")
|
||||
Integer queryCount(Long userId, Long followUserId);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.service;
|
||||
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import com.luojia_channel.modules.interact.dto.ChatItemDTO;
|
||||
import com.luojia_channel.modules.interact.dto.ChatPageQueryDTO;
|
||||
import com.luojia_channel.modules.message.dto.MessageResponse;
|
||||
import com.luojia_channel.modules.message.entity.MessageDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ChatService {
|
||||
|
||||
ScrollPageResponse<ChatItemDTO> getChatList(ChatPageQueryDTO chatPageQueryDTO);
|
||||
|
||||
ScrollPageResponse<MessageResponse> getChatHistory(ChatPageQueryDTO chatPageQueryDTO);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.luojia_channel.common.domain.UserDTO;
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import com.luojia_channel.modules.interact.entity.Follow;
|
||||
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
|
||||
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface FollowService extends IService<Follow> {
|
||||
|
||||
void follow(Long followUserId, Boolean isFollow);
|
||||
|
||||
boolean isFollow(Long followUserId);
|
||||
|
||||
List<UserDTO> followCommons(Long id);
|
||||
|
||||
ScrollPageResponse<PostBasicInfoDTO> queryPostFollow(PostPageQueryDTO postPageQueryDTO);
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import com.luojia_channel.common.utils.PageUtil;
|
||||
import com.luojia_channel.common.utils.RedisUtil;
|
||||
import com.luojia_channel.common.utils.UserContext;
|
||||
import com.luojia_channel.modules.interact.dto.ChatItemDTO;
|
||||
import com.luojia_channel.modules.interact.dto.ChatPageQueryDTO;
|
||||
import com.luojia_channel.modules.interact.service.ChatService;
|
||||
import com.luojia_channel.modules.message.dto.MessageResponse;
|
||||
import com.luojia_channel.modules.message.entity.MessageDO;
|
||||
import com.luojia_channel.modules.message.mapper.MessageMapper;
|
||||
import com.luojia_channel.modules.user.entity.User;
|
||||
import com.luojia_channel.modules.user.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
|
||||
private final MessageMapper messageMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public ScrollPageResponse<ChatItemDTO> getChatList(ChatPageQueryDTO chatPageQueryDTO) {
|
||||
/*
|
||||
Long userId = UserContext.getUserId();
|
||||
IPage<ChatItemDTO> chatPage = messageMapper.selectChatList(PageUtil.convert(chatPageQueryDTO), userId);
|
||||
return PageResponse.<ChatItemDTO>builder()
|
||||
.current(chatPage.getCurrent())
|
||||
.size(chatPage.getSize())
|
||||
.total(chatPage.getTotal())
|
||||
.records(chatPage.getRecords())
|
||||
.build();
|
||||
*/
|
||||
Long userId = UserContext.getUserId();
|
||||
String key = "chat:user_list:" + userId;
|
||||
|
||||
return redisUtil.scrollPageQuery(key, ChatItemDTO.class, chatPageQueryDTO,
|
||||
(chatUserIds) -> {
|
||||
List<ChatItemDTO> chatItems = new ArrayList<>();
|
||||
List<Long> latestMessageIds = new ArrayList<>();
|
||||
List<User> users = userMapper.selectByIdsOrderByField(chatUserIds);
|
||||
for(Long chatUserId : chatUserIds){
|
||||
String messageKey = "chat:history:" + Math.min(userId, chatUserId) + ":" +Math.max(userId, chatUserId);
|
||||
// 获取zset中最新的messageId
|
||||
Long latestMessageId = redisUtil.zRevMaxValue(messageKey);
|
||||
latestMessageIds.add(latestMessageId);
|
||||
}
|
||||
List<MessageDO> messageDOS = messageMapper.selectByIdsOrderByField(latestMessageIds);
|
||||
int i=0;
|
||||
for(User user : users){
|
||||
ChatItemDTO chatItemDTO = ChatItemDTO.builder()
|
||||
.chatUserId(user.getId())
|
||||
.avatar(user.getAvatar())
|
||||
.username(user.getUsername())
|
||||
.latestMessage(messageDOS.get(i).getContent())
|
||||
.latestTime(messageDOS.get(i).getCreateTime())
|
||||
.build();
|
||||
chatItems.add(chatItemDTO);
|
||||
i++;
|
||||
}
|
||||
|
||||
return chatItems;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScrollPageResponse<MessageResponse> getChatHistory(ChatPageQueryDTO chatPageQueryDTO) {
|
||||
/*
|
||||
Long userId = UserContext.getUserId();
|
||||
Long chatUserId = chatPageQueryDTO.getChatUserId();
|
||||
LambdaQueryWrapper<MessageDO> queryWrapper = Wrappers.lambdaQuery(MessageDO.class)
|
||||
.eq(MessageDO::getSenderId, userId)
|
||||
.eq(MessageDO::getReceiverId, chatUserId)
|
||||
.or()
|
||||
.eq(MessageDO::getReceiverId, userId)
|
||||
.eq(MessageDO::getSenderId, chatUserId)
|
||||
.orderByDesc(MessageDO::getCreateTime);
|
||||
// 查询的是私信消息
|
||||
queryWrapper.eq(MessageDO::getMessageType, 1);
|
||||
IPage<MessageDO> page = messageMapper.selectPage(PageUtil.convert(chatPageQueryDTO), queryWrapper);
|
||||
User chatUser = userMapper.selectById(chatUserId);
|
||||
return PageUtil.convert(page, (message) -> {
|
||||
MessageResponse messageResponse = BeanUtil.copyProperties(message, MessageResponse.class);
|
||||
if(messageResponse.getSenderId().equals(userId)) {
|
||||
messageResponse.setSenderAvatar(UserContext.getAvatar());
|
||||
messageResponse.setSenderName(UserContext.getUsername());
|
||||
}else{
|
||||
messageResponse.setSenderAvatar(chatUser.getAvatar());
|
||||
messageResponse.setSenderName(chatUser.getUsername());
|
||||
}
|
||||
return messageResponse;
|
||||
});
|
||||
*/
|
||||
|
||||
// 改成滚动分页查询
|
||||
Long userId = UserContext.getUserId();
|
||||
Long chatUserId = chatPageQueryDTO.getChatUserId();
|
||||
String key = "chat:history:" + Math.min(userId, chatUserId) + ":" +Math.max(userId, chatUserId);
|
||||
return redisUtil.scrollPageQuery(key, MessageResponse.class, chatPageQueryDTO,
|
||||
(messageIds) -> {
|
||||
List<MessageDO> messageDOS = messageMapper.selectByIdsOrderByField(messageIds);
|
||||
User chatUser = userMapper.selectById(chatUserId);
|
||||
List<MessageResponse> messageResponses = new ArrayList<>();
|
||||
for(MessageDO message : messageDOS){
|
||||
MessageResponse messageResponse = BeanUtil.copyProperties(message, MessageResponse.class);
|
||||
if(messageResponse.getSenderId().equals(userId)) {
|
||||
messageResponse.setSenderAvatar(UserContext.getAvatar());
|
||||
messageResponse.setSenderName(UserContext.getUsername());
|
||||
}else{
|
||||
messageResponse.setSenderAvatar(chatUser.getAvatar());
|
||||
messageResponse.setSenderName(chatUser.getUsername());
|
||||
}
|
||||
messageResponses.add(messageResponse);
|
||||
}
|
||||
return messageResponses;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package com.luojia_channel.modules.interact.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.luojia_channel.common.domain.UserDTO;
|
||||
import com.luojia_channel.common.domain.page.PageResponse;
|
||||
import com.luojia_channel.common.domain.page.ScrollPageResponse;
|
||||
import com.luojia_channel.common.utils.RedisUtil;
|
||||
import com.luojia_channel.common.utils.UserContext;
|
||||
import com.luojia_channel.modules.interact.entity.Follow;
|
||||
import com.luojia_channel.modules.interact.mapper.FollowMapper;
|
||||
import com.luojia_channel.modules.interact.service.FollowService;
|
||||
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
|
||||
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
|
||||
import com.luojia_channel.modules.post.entity.Post;
|
||||
import com.luojia_channel.modules.post.mapper.PostMapper;
|
||||
import com.luojia_channel.modules.user.entity.User;
|
||||
import com.luojia_channel.modules.user.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements FollowService {
|
||||
private final FollowMapper followMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final RedisUtil redisUtil;
|
||||
private final PostMapper postMapper;
|
||||
|
||||
@Override
|
||||
public void follow(Long followUserId, Boolean isFollow) {
|
||||
Long userId = UserContext.getUserId();
|
||||
String key = "follows:" + userId;
|
||||
if(isFollow){
|
||||
Follow follow = new Follow();
|
||||
follow.setUserId(userId);
|
||||
follow.setFollowUserId(followUserId);
|
||||
follow.setCreateTime(LocalDateTime.now());
|
||||
boolean isSuccess = save(follow);
|
||||
if(isSuccess){
|
||||
redisTemplate.opsForSet().add(key, followUserId);
|
||||
}
|
||||
}else{
|
||||
boolean isSuccess = followMapper.delete(userId, followUserId);
|
||||
if(isSuccess){
|
||||
redisTemplate.opsForSet().remove(key, followUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFollow(Long followUserId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
Integer count = followMapper.queryCount(userId, followUserId);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserDTO> followCommons(Long id) {
|
||||
Long userId = UserContext.getUserId();
|
||||
String userKey = "follows:" + userId;
|
||||
String followKey = "follows:" + id;
|
||||
Set<Object> intersect = redisTemplate.opsForSet().intersect(userKey, followKey);
|
||||
if(intersect == null || intersect.isEmpty()){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Long> ids = intersect.stream().map(obj -> (Long)obj).collect(Collectors.toList());
|
||||
List<UserDTO> userDTOS = userMapper.selectBatchIds(ids).stream()
|
||||
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
|
||||
.toList();
|
||||
|
||||
return userDTOS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScrollPageResponse<PostBasicInfoDTO> queryPostFollow(PostPageQueryDTO postPageQueryDTO) {
|
||||
Long userId = UserContext.getUserId();
|
||||
String key = "post:follow_of:" + userId;
|
||||
return redisUtil.scrollPageQuery(key, PostBasicInfoDTO.class, postPageQueryDTO,
|
||||
(postIds) -> {
|
||||
List<Post> posts = postMapper.selectBatchIds(postIds);
|
||||
List<Long> userIds = posts.stream().map(Post::getUserId).toList();
|
||||
List<User> users = userMapper.selectBatchIds(userIds);
|
||||
Map<Long, User> userMap = users.stream()
|
||||
.collect(Collectors.toMap(User::getId, user -> user));
|
||||
List<PostBasicInfoDTO> postBasicInfoDTOS = new ArrayList<>();
|
||||
for(Post post : posts){
|
||||
User user = userMap.getOrDefault(post.getUserId(), new User());
|
||||
PostBasicInfoDTO postBasicInfoDTO = BeanUtil.copyProperties(post, PostBasicInfoDTO.class);
|
||||
postBasicInfoDTO.setUserName(user.getUsername());
|
||||
postBasicInfoDTO.setUserAvatar(user.getAvatar());
|
||||
postBasicInfoDTOS.add(postBasicInfoDTO);
|
||||
}
|
||||
// 按照发布时间倒序排序
|
||||
postBasicInfoDTOS.sort(Comparator.comparing(PostBasicInfoDTO::getCreateTime).reversed());
|
||||
return postBasicInfoDTOS;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.luojia_channel.modules.message.config;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
import org.springframework.web.util.WebAppRootListener;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements ServletContextInitializer {
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) {
|
||||
servletContext.addListener(WebAppRootListener.class);
|
||||
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", (1024 * 200) + "");
|
||||
servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", (1024 * 200) + "");
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue