Compare commits
No commits in common. "main" and "master" have entirely different histories.
|
@ -0,0 +1,41 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### config ###
|
||||||
|
*.yml
|
||||||
|
|
||||||
|
### upload-dir ###
|
||||||
|
/public/chunk/
|
||||||
|
/public/img/cover/
|
||||||
|
/public/video/
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 寻鹿
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.9 MiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 625 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 415 KiB |
After Width: | Height: | Size: 2.7 MiB |
After Width: | Height: | Size: 2.2 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 179 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 2.2 MiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 395 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 1.1 MiB |
|
@ -0,0 +1,243 @@
|
||||||
|
<?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>2.7.15</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- Generated by https://start.springboot.io -->
|
||||||
|
<!-- 优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn -->
|
||||||
|
<groupId>com.teriteri</groupId>
|
||||||
|
<artifactId>backend</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>backend</name>
|
||||||
|
<description>backend</description>
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<elasticsearch.version>7.17.16</elasticsearch.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!--高性能数据库连接池-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-starter</artifactId>
|
||||||
|
<version>1.2.19</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
<version>1.2.19</version>
|
||||||
|
</dependency>
|
||||||
|
<!--druid依赖log4j包-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
<version>1.2.17</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--简化代码,帮写什么get、setter函数-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.24</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>8.0.31</version>
|
||||||
|
</dependency>
|
||||||
|
<!--省很多手写语句-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>3.5.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-generator</artifactId>
|
||||||
|
<version>3.5.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 安全认证 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
<version>2.7.5</version>
|
||||||
|
</dependency>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!--JSON解析器-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2</artifactId>
|
||||||
|
<version>2.0.40</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--redis-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>io.lettuce</groupId>
|
||||||
|
<artifactId>lettuce-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>redis.clients</groupId>
|
||||||
|
<artifactId>jedis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--一些常用工具类的包装-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--文件上传相关-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-fileupload</groupId>
|
||||||
|
<artifactId>commons-fileupload</artifactId>
|
||||||
|
<version>1.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--阿里云OSS对象存储-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
<version>3.17.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
<version>1.9.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--配置注解处理器-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--rabbitmq-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- netty -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.66.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-handler</artifactId>
|
||||||
|
<version>4.1.66.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 加入ssl证书认证-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.bouncycastle</groupId>-->
|
||||||
|
<!-- <artifactId>bcprov-jdk15on</artifactId>-->
|
||||||
|
<!-- <version>1.69</version> <!– 使用最新版本 –>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.bouncycastle</groupId>-->
|
||||||
|
<!-- <artifactId>bcpkix-jdk15on</artifactId>-->
|
||||||
|
<!-- <version>1.69</version> <!– 使用最新版本 –>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!--elasticsearch-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>7.17.16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.12.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish</groupId>
|
||||||
|
<artifactId>jakarta.json</artifactId>
|
||||||
|
<version>2.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--websocket-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</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-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-data-elasticsearch</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||||
|
<version>3.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.teriteri.backend;
|
||||||
|
|
||||||
|
import com.teriteri.backend.im.IMServer;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
|
||||||
|
// Generated by https://start.springboot.io
|
||||||
|
// 优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableScheduling // 启用定时任务
|
||||||
|
public class BackendApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(BackendApplication.class, args);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
new IMServer().start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package com.teriteri.backend.component.danmu;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.teriteri.backend.mapper.DanmuMapper;
|
||||||
|
import com.teriteri.backend.mapper.VideoStatsMapper;
|
||||||
|
import com.teriteri.backend.pojo.Danmu;
|
||||||
|
import com.teriteri.backend.pojo.User;
|
||||||
|
import com.teriteri.backend.pojo.VideoStats;
|
||||||
|
import com.teriteri.backend.service.video.VideoStatsService;
|
||||||
|
import com.teriteri.backend.utils.JwtUtil;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.websocket.*;
|
||||||
|
import javax.websocket.server.PathParam;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@ServerEndpoint(value = "/ws/danmu/{vid}")
|
||||||
|
public class DanmuWebSocketServer {
|
||||||
|
|
||||||
|
// 由于每个连接都不是共享一个WebSocketServer,所以要静态注入
|
||||||
|
private static JwtUtil jwtUtil;
|
||||||
|
private static RedisUtil redisUtil;
|
||||||
|
private static DanmuMapper danmuMapper;
|
||||||
|
private static VideoStatsService videoStatsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setDependencies(JwtUtil jwtUtil, RedisUtil redisUtil, DanmuMapper danmuMapper, VideoStatsService videoStatsService) {
|
||||||
|
DanmuWebSocketServer.jwtUtil = jwtUtil;
|
||||||
|
DanmuWebSocketServer.redisUtil = redisUtil;
|
||||||
|
DanmuWebSocketServer.danmuMapper = danmuMapper;
|
||||||
|
DanmuWebSocketServer.videoStatsService = videoStatsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对每个视频存储该视频下的session集合
|
||||||
|
private static final Map<String, Set<Session>> videoConnectionMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接建立时触发,记录session到map
|
||||||
|
* @param session 会话
|
||||||
|
* @param vid 视频的ID
|
||||||
|
*/
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session, @PathParam("vid") String vid) {
|
||||||
|
if (videoConnectionMap.get(vid) == null) {
|
||||||
|
Set<Session> set = new HashSet<>();
|
||||||
|
set.add(session);
|
||||||
|
videoConnectionMap.put(vid, set);
|
||||||
|
} else {
|
||||||
|
videoConnectionMap.get(vid).add(session);
|
||||||
|
}
|
||||||
|
sendMessage(vid, "当前观看人数" + videoConnectionMap.get(vid).size());
|
||||||
|
// System.out.println("建立连接,当前观看人数: " + videoConnectionMap.get(vid).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收到消息时触发,记录到数据库并转发到对应的全部连接
|
||||||
|
* @param session 当前会话
|
||||||
|
* @param message 信息体(包含"token"、"vid"、"data"字段)
|
||||||
|
* @param vid 视频ID
|
||||||
|
*/
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(Session session, String message, @PathParam("vid") String vid) {
|
||||||
|
try {
|
||||||
|
JSONObject msg = JSON.parseObject(message);
|
||||||
|
|
||||||
|
// token鉴权
|
||||||
|
String token = msg.getString("token");
|
||||||
|
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
|
||||||
|
session.getBasicRemote().sendText("登录已过期");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
token = token.substring(7);
|
||||||
|
boolean verifyToken = jwtUtil.verifyToken(token);
|
||||||
|
if (!verifyToken) {
|
||||||
|
session.getBasicRemote().sendText("登录已过期");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String userId = JwtUtil.getSubjectFromToken(token);
|
||||||
|
String role = JwtUtil.getClaimFromToken(token, "role");
|
||||||
|
User user = redisUtil.getObject("security:" + role + ":" + userId, User.class);
|
||||||
|
if (user == null) {
|
||||||
|
session.getBasicRemote().sendText("登录已过期");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写库
|
||||||
|
JSONObject data = msg.getJSONObject("data");
|
||||||
|
// System.out.println(data);
|
||||||
|
Danmu danmu = new Danmu(
|
||||||
|
null,
|
||||||
|
Integer.parseInt(vid),
|
||||||
|
user.getUid(),
|
||||||
|
data.getString("content"),
|
||||||
|
data.getInteger("fontsize"),
|
||||||
|
data.getInteger("mode"),
|
||||||
|
data.getString("color"),
|
||||||
|
data.getDouble("timePoint"),
|
||||||
|
1,
|
||||||
|
new Date()
|
||||||
|
);
|
||||||
|
danmuMapper.insert(danmu);
|
||||||
|
videoStatsService.updateStats(Integer.parseInt(vid), "danmu", true, 1);
|
||||||
|
redisUtil.addMember("danmu_idset:" + vid, danmu.getId()); // 加入对应视频的ID集合,以便查询
|
||||||
|
|
||||||
|
// 广播弹幕
|
||||||
|
String dmJson = JSON.toJSONString(danmu);
|
||||||
|
sendMessage(vid, dmJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接关闭时执行
|
||||||
|
* @param session 当前会话
|
||||||
|
* @param vid 视频ID
|
||||||
|
*/
|
||||||
|
@OnClose
|
||||||
|
public void onClose(Session session, @PathParam("vid") String vid) {
|
||||||
|
// 从缓存中移除连接记录
|
||||||
|
videoConnectionMap.get(vid).remove(session);
|
||||||
|
if (videoConnectionMap.get(vid).size() == 0) {
|
||||||
|
// 如果没人了就直接移除这个视频
|
||||||
|
videoConnectionMap.remove(vid);
|
||||||
|
} else {
|
||||||
|
// 否则更新在线人数
|
||||||
|
sendMessage(vid, "当前观看人数" + videoConnectionMap.get(vid).size());
|
||||||
|
}
|
||||||
|
// System.out.println("关闭连接,当前观看人数: " + videoConnectionMap.get(vid).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Throwable error) {
|
||||||
|
log.error("websocket发生错误");
|
||||||
|
error.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 往对应的全部连接发送消息
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @param text 消息内容,对象需转成JSON字符串
|
||||||
|
*/
|
||||||
|
public void sendMessage(String vid, String text) {
|
||||||
|
Set<Session> set = videoConnectionMap.get(vid);
|
||||||
|
// 使用并行流往各客户端发送数据
|
||||||
|
set.parallelStream().forEach(session -> {
|
||||||
|
try {
|
||||||
|
session.getBasicRemote().sendText(text);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig implements Filter {
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||||
|
HttpServletResponse response = (HttpServletResponse) res;
|
||||||
|
HttpServletRequest request = (HttpServletRequest) req;
|
||||||
|
|
||||||
|
String origin = request.getHeader("Origin");
|
||||||
|
if(origin!=null) {
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
String headers = request.getHeader("Access-Control-Request-Headers");
|
||||||
|
if(headers!=null) {
|
||||||
|
response.setHeader("Access-Control-Allow-Headers", headers);
|
||||||
|
response.setHeader("Access-Control-Expose-Headers", headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setHeader("Access-Control-Allow-Methods", "*");
|
||||||
|
response.setHeader("Access-Control-Max-Age", "3600");
|
||||||
|
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
|
import com.alibaba.druid.support.http.ResourceServlet;
|
||||||
|
import com.alibaba.druid.support.http.StatViewServlet;
|
||||||
|
import com.alibaba.druid.support.http.WebStatFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class DruidConfig {
|
||||||
|
|
||||||
|
@Value("${spring.datasource.username}")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Value("${spring.datasource.password}")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "spring.datasource")
|
||||||
|
@Bean
|
||||||
|
public DataSource druid(){
|
||||||
|
return new DruidDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
配置一个druid的监控
|
||||||
|
1、配置一个druid的后台 管理servlet
|
||||||
|
2、配置一个druid的filter
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 配置 Druid 监控管理后台的Servlet;
|
||||||
|
// 内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
|
||||||
|
@Bean
|
||||||
|
public ServletRegistrationBean servletRegistrationBean(){
|
||||||
|
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
|
||||||
|
|
||||||
|
/*
|
||||||
|
这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
|
||||||
|
的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
|
||||||
|
*/
|
||||||
|
Map<String, String> initParams = new HashMap<>();
|
||||||
|
initParams.put("loginUsername", username); //后台管理界面的登录账号
|
||||||
|
initParams.put("loginPassword", password); //后台管理界面的登录密码
|
||||||
|
|
||||||
|
//后台允许谁可以访问
|
||||||
|
//initParams.put("allow", "localhost"):表示只有本机可以访问
|
||||||
|
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
|
||||||
|
initParams.put("allow", "");
|
||||||
|
//deny:Druid 后台拒绝谁访问
|
||||||
|
//initParams.put("listen", "192.168.1.20");表示禁止此ip访问
|
||||||
|
|
||||||
|
//设置初始化参数
|
||||||
|
bean.setInitParameters(initParams);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置 Druid 监控 之 web 监控的 filter
|
||||||
|
// WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean webStatFilter(){
|
||||||
|
FilterRegistrationBean bean = new FilterRegistrationBean();
|
||||||
|
bean.setFilter(new WebStatFilter());
|
||||||
|
|
||||||
|
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
|
||||||
|
Map<String, String> initParams = new HashMap<>();
|
||||||
|
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
|
||||||
|
bean.setInitParameters(initParams);
|
||||||
|
|
||||||
|
//"/*" 表示过滤所有请求
|
||||||
|
bean.setUrlPatterns(Arrays.asList("/*"));
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.auth.AuthScope;
|
||||||
|
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||||
|
import org.apache.http.client.CredentialsProvider;
|
||||||
|
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||||
|
import org.apache.http.impl.nio.reactor.IOReactorConfig;
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ElasticSearchConfig {
|
||||||
|
|
||||||
|
@Value("${elasticsearch.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@Value("${elasticsearch.port}")
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@Value("${elasticsearch.username}")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Value("${elasticsearch.password}")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Bean(destroyMethod = "close")
|
||||||
|
public RestClient restClient() {
|
||||||
|
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||||
|
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
|
||||||
|
return RestClient
|
||||||
|
.builder(new HttpHost(host, port, "http"))
|
||||||
|
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
|
||||||
|
.setDefaultCredentialsProvider(credentialsProvider) // 身份验证
|
||||||
|
.setMaxConnTotal(100) // 最大总连接数
|
||||||
|
.setMaxConnPerRoute(50) // 每个路由的最大连接数
|
||||||
|
.setKeepAliveStrategy(((response, context) -> Duration.ofMinutes(5).toMillis())) // httpclient保活策略 5分钟不连接就关闭
|
||||||
|
.setDefaultIOReactorConfig(IOReactorConfig.custom().setSoKeepAlive(true).build()) // 开启tcp keepalive
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(destroyMethod = "close")
|
||||||
|
public ElasticsearchTransport transport() {
|
||||||
|
return new RestClientTransport(restClient(), new JacksonJsonpMapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ElasticsearchClient elasticsearchClient() {
|
||||||
|
return new ElasticsearchClient(transport());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.multipart.MultipartResolver;
|
||||||
|
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "file")
|
||||||
|
public class FileUploadConfig {
|
||||||
|
|
||||||
|
@Bean(name = "multipartResolver")
|
||||||
|
public MultipartResolver multipartResolver() {
|
||||||
|
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
|
||||||
|
multipartResolver.setDefaultEncoding("UTF-8");
|
||||||
|
multipartResolver.setMaxUploadSizePerFile(30 * 1024 * 1024); // 设置文件上传大小限制
|
||||||
|
multipartResolver.setResolveLazily(true);
|
||||||
|
return multipartResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import com.aliyun.oss.ClientBuilderConfiguration;
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class OSSConfig {
|
||||||
|
@Value("${oss.endpoint}")
|
||||||
|
private String OSS_ENDPOINT;
|
||||||
|
|
||||||
|
@Value("${oss.keyId}")
|
||||||
|
private String ACCESS_KEY_ID;
|
||||||
|
|
||||||
|
@Value("${oss.keySecret}")
|
||||||
|
private String ACCESS_KEY_SECRET;
|
||||||
|
|
||||||
|
@Value("${oss.idleTimeout}")
|
||||||
|
private long IDLE_TIMEOUT;
|
||||||
|
|
||||||
|
@Bean(destroyMethod = "shutdown")
|
||||||
|
public OSS ossClient() {
|
||||||
|
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
|
||||||
|
//连接空闲超时时间,超时则关闭
|
||||||
|
conf.setIdleConnectionTime(IDLE_TIMEOUT);
|
||||||
|
// 创建OSSClient实例
|
||||||
|
return new OSSClientBuilder().build(OSS_ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET, conf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.amqp.core.Binding;
|
||||||
|
import org.springframework.amqp.core.BindingBuilder;
|
||||||
|
import org.springframework.amqp.core.DirectExchange;
|
||||||
|
import org.springframework.amqp.core.Queue;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂时用不到,先注了
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RabbitMQConfig {
|
||||||
|
|
||||||
|
// // 1.声明 direct 模式的交换机
|
||||||
|
// /**
|
||||||
|
// *投稿相关的交换机
|
||||||
|
// */
|
||||||
|
// @Bean
|
||||||
|
// public DirectExchange directUploadExchange() {
|
||||||
|
// return new DirectExchange("direct_upload_exchange", true, false);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 2.声明队列
|
||||||
|
// /**
|
||||||
|
// * 视频投稿信息队列
|
||||||
|
// */
|
||||||
|
// @Bean
|
||||||
|
// public Queue videoUploadQueue() {
|
||||||
|
// return new Queue("videoUpload_direct_queue", true);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 3.队列和交换机完成绑定关系
|
||||||
|
// @Bean
|
||||||
|
// public Binding videoUploadBinding() {
|
||||||
|
// return BindingBuilder.bind(videoUploadQueue()).to(directUploadExchange()).with("videoUpload");
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.cache.interceptor.KeyGenerator;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.JedisPoolConfig;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching
|
||||||
|
public class RedisConfig extends CachingConfigurerSupport {
|
||||||
|
|
||||||
|
@Value("${spring.redis.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@Value("${spring.redis.port}")
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Value("${spring.redis.timeout}")
|
||||||
|
private int timeout;
|
||||||
|
|
||||||
|
@Value("${spring.redis.jedis.pool.max-idle}")
|
||||||
|
private int maxIdle;
|
||||||
|
|
||||||
|
@Value("${spring.redis.jedis.pool.max-wait}")
|
||||||
|
private long maxWaitMillis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并配置一个 Jedis 连接池
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public JedisPool redisPoolFactory() {
|
||||||
|
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||||
|
jedisPoolConfig.setMaxIdle(maxIdle);
|
||||||
|
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
|
||||||
|
return new JedisPool(jedisPoolConfig, host, port, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存键生成器,在这个项目中用不到,我更倾向自己手动命名,如 user:1:age
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public KeyGenerator keyGenerator() {
|
||||||
|
return new KeyGenerator() {
|
||||||
|
@Override
|
||||||
|
public Object generate(Object target, Method method, Object... params) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(target.getClass().getName());
|
||||||
|
sb.append(method.getName());
|
||||||
|
for (Object obj : params) {
|
||||||
|
sb.append(obj.toString());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis 缓存管理器
|
||||||
|
* @param connectionFactory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory).build();
|
||||||
|
return redisCacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编写自己的 redisTemplate,用于与 Redis 进行交互,配置了json格式存储,序列化与反序列化
|
||||||
|
* @param redisConnectionFactory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||||
|
// 为了方便自己开发,一般直接使用 <String, Object>
|
||||||
|
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
|
||||||
|
// 序列化配置
|
||||||
|
// json的序列化
|
||||||
|
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // 用json序列化任意对象类
|
||||||
|
ObjectMapper om = new ObjectMapper(); // 对象类序列化过程中用 ObjectMapper 进行转义
|
||||||
|
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||||
|
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||||
|
jackson2JsonRedisSerializer.setObjectMapper(om);
|
||||||
|
// String的序列化
|
||||||
|
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
||||||
|
|
||||||
|
// key采用了String的序列化方式
|
||||||
|
template.setKeySerializer(stringRedisSerializer);
|
||||||
|
// hash的key也采用String的序列化方式
|
||||||
|
template.setHashKeySerializer(stringRedisSerializer);
|
||||||
|
// value序列化方式采用jackson
|
||||||
|
template.setValueSerializer(jackson2JsonRedisSerializer);
|
||||||
|
// hash的value序列化方式采用jackson
|
||||||
|
template.setHashValueSerializer(jackson2JsonRedisSerializer);
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个专门用于操作 Redis 字符串类型的模板,它是 RedisTemplate 的子类,只支持字符串数据的存储和检索
|
||||||
|
* @param factory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
|
||||||
|
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
|
||||||
|
stringRedisTemplate.setConnectionFactory(factory);
|
||||||
|
return stringRedisTemplate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import com.teriteri.backend.config.filter.JwtAuthenticationTokenFilter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
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.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码BCrypt加密
|
||||||
|
* @return BCrypt加密后的密码
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名和密码验证
|
||||||
|
* @return Authentication对象
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationProvider authenticationProvider() {
|
||||||
|
return new AuthenticationProvider() {
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
// 从Authentication对象中获取用户名和身份凭证信息
|
||||||
|
String username = authentication.getName();
|
||||||
|
String password = authentication.getCredentials().toString();
|
||||||
|
|
||||||
|
UserDetails loginUser = userDetailsService.loadUserByUsername(username);
|
||||||
|
if (Objects.isNull(loginUser) || !passwordEncoder().matches(password, loginUser.getPassword())) {
|
||||||
|
// 密码匹配失败抛出异常
|
||||||
|
throw new BadCredentialsException("访问拒绝:用户名或密码错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.info("访问成功:" + loginUser);
|
||||||
|
return new UsernamePasswordAuthenticationToken(loginUser, password, loginUser.getAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return authentication.equals(UsernamePasswordAuthenticationToken.class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求接口过滤器,验证是否开放接口,如果不是开放接口请求头又没带 Authorization 属性会被直接拦截
|
||||||
|
* @param http
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
return http
|
||||||
|
// 基于 token,不需要 csrf
|
||||||
|
.csrf().disable()
|
||||||
|
// 基于 token,不需要 session
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
|
// 下面开始设置权限
|
||||||
|
.authorizeRequests(authorize -> authorize
|
||||||
|
// 放开Swagger路径
|
||||||
|
.antMatchers("/doc.html", "/webjars/**").permitAll()
|
||||||
|
.antMatchers("/swagger-resources/**").permitAll()
|
||||||
|
.antMatchers("/v2/api-docs/**").permitAll()
|
||||||
|
// 请求放开接口
|
||||||
|
.antMatchers("/druid/**","/favicon.ico",
|
||||||
|
"/user/account/register",
|
||||||
|
"/user/account/login",
|
||||||
|
"/admin/account/login",
|
||||||
|
"/category/getall",
|
||||||
|
"/video/random/visitor",
|
||||||
|
"/video/cumulative/visitor",
|
||||||
|
"/video/getone",
|
||||||
|
"/ws/danmu/**",
|
||||||
|
"/danmu-list/**",
|
||||||
|
"/msg/chat/outline",
|
||||||
|
"/video/play/visitor",
|
||||||
|
"/favorite/get-all/visitor",
|
||||||
|
"/search/**",
|
||||||
|
"/comment/get",
|
||||||
|
"/comment/reply/get-more",
|
||||||
|
"/comment/get-up-like",
|
||||||
|
"/user/info/get-one",
|
||||||
|
"/video/user-works-count",
|
||||||
|
"/video/user-works",
|
||||||
|
"/video/user-love",
|
||||||
|
"/video/user-collect").permitAll()
|
||||||
|
// 允许HTTP OPTIONS请求
|
||||||
|
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||||
|
// 其他地址的访问均需验证权限
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
// 添加 JWT 过滤器,JWT 过滤器在用户名密码认证过滤器之前
|
||||||
|
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync // 开启异步
|
||||||
|
public class ThreadPoolConfig {
|
||||||
|
|
||||||
|
@Bean("taskExecutor")
|
||||||
|
public Executor asyncServiceExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
//设置核心线程数
|
||||||
|
executor.setCorePoolSize(20);
|
||||||
|
//设置最大线程数
|
||||||
|
executor.setMaxPoolSize(100);
|
||||||
|
//配置队列大小
|
||||||
|
executor.setQueueCapacity(Integer.MAX_VALUE);
|
||||||
|
//设置线程活跃时间(秒)
|
||||||
|
executor.setKeepAliveSeconds(60);
|
||||||
|
//设置默认线程名称
|
||||||
|
executor.setThreadNamePrefix("teriteri");
|
||||||
|
//等待所有任务结束后再关闭线程池
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
//执行初始化
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||||
|
import springfox.documentation.builders.ApiInfoBuilder;
|
||||||
|
import springfox.documentation.builders.PathSelectors;
|
||||||
|
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||||
|
import springfox.documentation.service.ApiInfo;
|
||||||
|
import springfox.documentation.spi.DocumentationType;
|
||||||
|
import springfox.documentation.spring.web.plugins.Docket;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: 顾挽
|
||||||
|
* @description:
|
||||||
|
* @create: 2024-04-28 16:26
|
||||||
|
**/
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Docket docket() {
|
||||||
|
ApiInfo apiInfo = new ApiInfoBuilder()
|
||||||
|
.title("仿BiliBili后端接口文档")
|
||||||
|
.version("1.0")
|
||||||
|
.description("BiliBili后端接口文档")
|
||||||
|
.build();
|
||||||
|
Docket docket = new Docket(DocumentationType.SWAGGER_2)
|
||||||
|
.apiInfo(apiInfo)
|
||||||
|
.select()
|
||||||
|
.apis(RequestHandlerSelectors.basePackage("com.teriteri.backend.controller"))
|
||||||
|
.paths(PathSelectors.any())
|
||||||
|
.build();
|
||||||
|
return docket;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
|
||||||
|
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
package com.teriteri.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebSocketConfig {
|
||||||
|
|
||||||
|
|
||||||
|
// 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ServerEndpointExporter serverEndpointExporter() {
|
||||||
|
return new ServerEndpointExporter();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.teriteri.backend.config.filter;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.User;
|
||||||
|
import com.teriteri.backend.service.impl.user.UserDetailsImpl;
|
||||||
|
import com.teriteri.backend.utils.JwtUtil;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.security.sasl.AuthenticationException;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token 认证过滤器,任何请求访问服务器都会先被这里拦截验证token合法性
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param filterChain
|
||||||
|
* @throws ServletException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String token = request.getHeader("Authorization");
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
|
||||||
|
// 通过开放接口过滤器后,如果没有可解析的token就放行
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = token.substring(7);
|
||||||
|
|
||||||
|
// 解析token
|
||||||
|
boolean verifyToken = jwtUtil.verifyToken(token);
|
||||||
|
if (!verifyToken) {
|
||||||
|
// log.error("当前token已过期");
|
||||||
|
response.addHeader("message", "not login"); // 设置响应头信息,给前端判断用
|
||||||
|
response.setStatus(403);
|
||||||
|
// throw new AuthenticationException("当前token已过期");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String userId = JwtUtil.getSubjectFromToken(token);
|
||||||
|
String role = JwtUtil.getClaimFromToken(token, "role");
|
||||||
|
|
||||||
|
// 从redis中获取用户信息
|
||||||
|
User user = redisUtil.getObject("security:" + role + ":" + userId, User.class);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
// log.error("用户未登录");
|
||||||
|
response.addHeader("message", "not login"); // 设置响应头信息,给前端判断用
|
||||||
|
response.setStatus(403);
|
||||||
|
// throw new AuthenticationException("用户未登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存入SecurityContextHolder,这里建议只供读取uid用,其中的状态等非静态数据可能不准,所以建议redis另外存值
|
||||||
|
UserDetailsImpl loginUser = new UserDetailsImpl(user);
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(loginUser, null, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
|
||||||
|
// 放行
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.category.CategoryService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class CategoryController {
|
||||||
|
@Autowired
|
||||||
|
private CategoryService categoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全部分区接口
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/category/getall")
|
||||||
|
public CustomResponse getAll() {
|
||||||
|
return categoryService.getAll();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.message.ChatService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ChatController {
|
||||||
|
@Autowired
|
||||||
|
private ChatService chatService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建一个聊天,与其他用户首次聊天时调用
|
||||||
|
* @param uid 对方用户ID
|
||||||
|
* @return CustomResponse对象 message可能值:"新创建"/"已存在"/"未知用户"
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat/create/{uid}")
|
||||||
|
public CustomResponse createChat(@PathVariable("uid") Integer uid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> result = chatService.createChat(uid, currentUser.getUserId());
|
||||||
|
if (Objects.equals(result.get("msg").toString(), "新创建")) {
|
||||||
|
customResponse.setData(result); // 返回新创建的聊天
|
||||||
|
} else if (Objects.equals(result.get("msg").toString(), "未知用户")) {
|
||||||
|
customResponse.setCode(404);
|
||||||
|
}
|
||||||
|
customResponse.setMessage(result.get("msg").toString());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户最近的聊天列表
|
||||||
|
* @param offset 分页偏移量(前端查询了多少个聊天)
|
||||||
|
* @return CustomResponse对象 包含带用户信息和最近一条消息的聊天列表以及是否还有更多数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat/recent-list")
|
||||||
|
public CustomResponse getRecentList(@RequestParam("offset") Long offset) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("list", chatService.getChatListWithData(uid, offset));
|
||||||
|
// 检查是否还有更多
|
||||||
|
if (offset + 10 < redisUtil.zCard("chat_zset:" + uid)) {
|
||||||
|
map.put("more", true);
|
||||||
|
} else {
|
||||||
|
map.put("more", false);
|
||||||
|
}
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除聊天
|
||||||
|
* @param uid 对方用户ID
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat/delete/{uid}")
|
||||||
|
public CustomResponse deleteChat(@PathVariable("uid") Integer uid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
chatService.delChat(uid, currentUser.getUserId());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换窗口时 更新在线状态以及清除未读
|
||||||
|
* @param from 对方UID
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat/online")
|
||||||
|
public void updateWhisperOnline(@RequestParam("from") Integer from) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
chatService.updateWhisperOnline(from, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换窗口时 更新为离开状态 (该接口要放开,无需验证token,防止token过期导致用户一直在线)
|
||||||
|
* @param from 对方UID
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat/outline")
|
||||||
|
public void updateWhisperOutline(@RequestParam("from") Integer from, @RequestParam("to") Integer to) {
|
||||||
|
chatService.updateWhisperOutline(from, to);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.message.ChatDetailedService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ChatDetailedController {
|
||||||
|
@Autowired
|
||||||
|
private ChatDetailedService chatDetailedService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更多历史消息记录
|
||||||
|
* @param uid 聊天对象的UID
|
||||||
|
* @param offset 偏移量,即已经获取过的消息数量,从哪条开始获取更多
|
||||||
|
* @return CustomResponse对象,包含更多消息记录的map
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg/chat-detailed/get-more")
|
||||||
|
public CustomResponse getMoreChatDetails(@RequestParam("uid") Integer uid,
|
||||||
|
@RequestParam("offset") Long offset) {
|
||||||
|
Integer loginUid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(chatDetailedService.getDetails(uid, loginUid, offset));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除消息
|
||||||
|
* @param id 消息ID
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/msg/chat-detailed/delete")
|
||||||
|
public CustomResponse delDetail(@RequestParam("id") Integer id) {
|
||||||
|
Integer loginUid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
if (!chatDetailedService.deleteDetail(id, loginUid)) {
|
||||||
|
customResponse.setCode(500);
|
||||||
|
customResponse.setMessage("删除消息失败");
|
||||||
|
}
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CommentTree;
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.comment.CommentService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class CommentController {
|
||||||
|
@Autowired
|
||||||
|
private CommentService commentService;
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论树列表,每次查十条
|
||||||
|
* @param vid 对应视频ID
|
||||||
|
* @param offset 分页偏移量(已经获取到的评论树的数量)
|
||||||
|
* @param type 排序类型 1 按热度排序 2 按时间排序
|
||||||
|
* @return 评论树列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/comment/get")
|
||||||
|
public CustomResponse getCommentTreeByVid(@RequestParam("vid") Integer vid,
|
||||||
|
@RequestParam("offset") Long offset,
|
||||||
|
@RequestParam("type") Integer type) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
long count = redisUtil.zCard("comment_video:" + vid);
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
if (offset >= count) {
|
||||||
|
// 表示前端已经获取到全部根评论了,没必要继续
|
||||||
|
map.put("more", false);
|
||||||
|
map.put("comments", Collections.emptyList());
|
||||||
|
} else if (offset + 10 >= count){
|
||||||
|
// 表示这次查询会查完全部根评论
|
||||||
|
map.put("more", false);
|
||||||
|
map.put("comments", commentService.getCommentTreeByVid(vid, offset, type));
|
||||||
|
} else {
|
||||||
|
// 表示这次查的只是冰山一角,还有很多评论没查到
|
||||||
|
map.put("more", true);
|
||||||
|
map.put("comments", commentService.getCommentTreeByVid(vid, offset, type));
|
||||||
|
}
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展开更多回复评论
|
||||||
|
* @param id 根评论id
|
||||||
|
* @return 完整的一棵包含全部评论的评论树
|
||||||
|
*/
|
||||||
|
@GetMapping("/comment/reply/get-more")
|
||||||
|
public CommentTree getMoreCommentById(@RequestParam("id") Integer id) {
|
||||||
|
return commentService.getMoreCommentsById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发表评论
|
||||||
|
* @param vid 视频id
|
||||||
|
* @param rootId 根评论id
|
||||||
|
* @param parentId 被回复评论id
|
||||||
|
* @param toUserId 被回复者uid
|
||||||
|
* @param content 评论内容
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/comment/add")
|
||||||
|
public CustomResponse addComment(
|
||||||
|
@RequestParam("vid") Integer vid,
|
||||||
|
@RequestParam("root_id") Integer rootId,
|
||||||
|
@RequestParam("parent_id") Integer parentId,
|
||||||
|
@RequestParam("to_user_id") Integer toUserId,
|
||||||
|
@RequestParam("content") String content ) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
CommentTree commentTree = commentService.sendComment(vid, uid, rootId, parentId, toUserId, content);
|
||||||
|
if (commentTree == null) {
|
||||||
|
customResponse.setCode(500);
|
||||||
|
customResponse.setMessage("发送失败!");
|
||||||
|
}
|
||||||
|
customResponse.setData(commentTree);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论
|
||||||
|
* @param id 评论id
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/comment/delete")
|
||||||
|
public CustomResponse delComment(@RequestParam("id") Integer id) {
|
||||||
|
Integer loginUid = currentUser.getUserId();
|
||||||
|
return commentService.deleteComment(id, loginUid, currentUser.isAdmin());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.pojo.Danmu;
|
||||||
|
import com.teriteri.backend.service.danmu.DanmuService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class DanmuController {
|
||||||
|
@Autowired
|
||||||
|
private DanmuService danmuService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取弹幕列表
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/danmu-list/{vid}")
|
||||||
|
public CustomResponse getDanmuList(@PathVariable("vid") String vid) {
|
||||||
|
Set<Object> idset = redisUtil.getMembers("danmu_idset:" + vid);
|
||||||
|
List<Danmu> danmuList = danmuService.getDanmuListByIdset(idset);
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(danmuList);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除弹幕
|
||||||
|
* @param id 弹幕id
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/danmu/delete")
|
||||||
|
public CustomResponse deleteDanmu(@RequestParam("id") Integer id) {
|
||||||
|
Integer loginUid = currentUser.getUserId();
|
||||||
|
return danmuService.deleteDanmu(id, loginUid, currentUser.isAdmin());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.service.video.FavoriteService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class FavoriteController {
|
||||||
|
@Autowired
|
||||||
|
private FavoriteService favoriteService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站内用户请求某个用户的收藏夹列表(需要jwt鉴权)
|
||||||
|
* @param uid 被查看的用户ID
|
||||||
|
* @return 包含收藏夹列表的响应对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/favorite/get-all/user")
|
||||||
|
public CustomResponse getAllFavoritiesForUser(@RequestParam("uid") Integer uid) {
|
||||||
|
Integer loginUid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
if (Objects.equals(loginUid, uid)) {
|
||||||
|
customResponse.setData(favoriteService.getFavorites(uid, true));
|
||||||
|
} else {
|
||||||
|
customResponse.setData(favoriteService.getFavorites(uid, false));
|
||||||
|
}
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游客请求某个用户的收藏夹列表(不需要jwt鉴权)
|
||||||
|
* @param uid 被查看的用户ID
|
||||||
|
* @return 包含收藏夹列表的响应对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/favorite/get-all/visitor")
|
||||||
|
public CustomResponse getAllFavoritiesForVisitor(@RequestParam("uid") Integer uid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(favoriteService.getFavorites(uid, false));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个新的收藏夹
|
||||||
|
* @param title 标题 限80字(需前端做合法判断)
|
||||||
|
* @param desc 简介 限200字(需前端做合法判断)
|
||||||
|
* @param visible 是否公开 0否 1是
|
||||||
|
* @return 包含新创建的收藏夹信息的响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/favorite/create")
|
||||||
|
public CustomResponse createFavorite(@RequestParam("title") String title,
|
||||||
|
@RequestParam("desc") String desc,
|
||||||
|
@RequestParam("visible") Integer visible) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(favoriteService.addFavorite(uid, title, desc, visible));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.pojo.Favorite;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.service.video.FavoriteService;
|
||||||
|
import com.teriteri.backend.service.video.FavoriteVideoService;
|
||||||
|
import com.teriteri.backend.service.video.UserVideoService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class FavoriteVideoController {
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FavoriteService favoriteService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FavoriteVideoService favoriteVideoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserVideoService userVideoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏了该视频的收藏夹列表
|
||||||
|
* @param vid 视频id
|
||||||
|
* @return 收藏了该视频的收藏夹列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/collected-fids")
|
||||||
|
public CustomResponse getCollectedFids(@RequestParam("vid") Integer vid) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
Set<Integer> fids = findFidsOfUserFavorites(uid);
|
||||||
|
Set<Integer> collectedFids = favoriteVideoService.findFidsOfCollected(vid, fids);
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(collectedFids);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏或取消收藏某视频
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @param addArray 包含需要添加收藏的多个收藏夹ID组成的字符串,形式如 1,12,13,20 不能含有字符"["和"]"
|
||||||
|
* @param removeArray 包含需要移出收藏的多个收藏夹ID组成的字符串,形式如 1,12,13,20 不能含有字符"["和"]"
|
||||||
|
* @return 无数据返回
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/collect")
|
||||||
|
public CustomResponse collectVideo(@RequestParam("vid") Integer vid,
|
||||||
|
@RequestParam("adds") String[] addArray,
|
||||||
|
@RequestParam("removes") String[] removeArray) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
Set<Integer> fids = findFidsOfUserFavorites(uid);
|
||||||
|
Set<Integer> addSet = Arrays.stream(addArray).map(Integer::parseInt).collect(Collectors.toSet());
|
||||||
|
Set<Integer> removeSet = Arrays.stream(removeArray).map(Integer::parseInt).collect(Collectors.toSet());
|
||||||
|
boolean allElementsInFids = fids.containsAll(addSet) && fids.containsAll(removeSet); // 判断添加或移出的收藏夹是否都属于该用户
|
||||||
|
if (!allElementsInFids) {
|
||||||
|
customResponse.setCode(403);
|
||||||
|
customResponse.setMessage("无权操作该收藏夹");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
Set<Integer> collectedFids = favoriteVideoService.findFidsOfCollected(vid, fids); // 原本该用户已收藏该视频的收藏夹ID集合
|
||||||
|
if (addSet.size() > 0) {
|
||||||
|
favoriteVideoService.addToFav(uid, vid, addSet);
|
||||||
|
}
|
||||||
|
if (removeSet.size() > 0) {
|
||||||
|
favoriteVideoService.removeFromFav(uid, vid, removeSet);
|
||||||
|
}
|
||||||
|
boolean isCollect = addSet.size() > 0 && collectedFids.size() == 0;
|
||||||
|
boolean isCancel = addSet.size() == 0 && collectedFids.size() > 0 && collectedFids.size() == removeSet.size() && collectedFids.containsAll(removeSet);
|
||||||
|
if (isCollect) {
|
||||||
|
userVideoService.collectOrCancel(uid, vid, true);
|
||||||
|
} else if (isCancel) {
|
||||||
|
userVideoService.collectOrCancel(uid, vid, false);
|
||||||
|
}
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消单个视频在单个收藏夹的收藏
|
||||||
|
* @param vid 视频vid
|
||||||
|
* @param fid 收藏夹id
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/cancel-collect")
|
||||||
|
public CustomResponse cancelCollect(@RequestParam("vid") Integer vid, @RequestParam("fid") Integer fid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
Set<Integer> fids = findFidsOfUserFavorites(uid);
|
||||||
|
Set<Integer> removeSet = new HashSet<>();
|
||||||
|
removeSet.add(fid);
|
||||||
|
if (!fids.containsAll(removeSet)) {
|
||||||
|
customResponse.setCode(403);
|
||||||
|
customResponse.setMessage("无权操作该收藏夹");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
Set<Integer> collectedFids = favoriteVideoService.findFidsOfCollected(vid, fids); // 原本该用户已收藏该视频的收藏夹ID集合
|
||||||
|
favoriteVideoService.removeFromFav(uid, vid, removeSet);
|
||||||
|
// 判断是否是最后一个取消收藏的收藏夹,是就要标记视频为未收藏
|
||||||
|
boolean isCancel = collectedFids.size() > 0 && collectedFids.size() == removeSet.size() && collectedFids.containsAll(removeSet);
|
||||||
|
if (isCancel) {
|
||||||
|
userVideoService.collectOrCancel(uid, vid, false);
|
||||||
|
}
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取某用户的全部收藏夹信息的FID整合成集合
|
||||||
|
* @param uid 用户ID
|
||||||
|
* @return fid集合
|
||||||
|
*/
|
||||||
|
private Set<Integer> findFidsOfUserFavorites(Integer uid) {
|
||||||
|
List<Favorite> list = favoriteService.getFavorites(uid, true);
|
||||||
|
if (list == null) return new HashSet<>();
|
||||||
|
return list.stream()
|
||||||
|
.map(Favorite::getFid)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.message.MsgUnreadService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class MsgUnreadController {
|
||||||
|
@Autowired
|
||||||
|
private MsgUnreadService msgUnreadService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户全部消息未读数
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/msg-unread/all")
|
||||||
|
public CustomResponse getMsgUnread() {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(msgUnreadService.getUnread(uid));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除某一列的未读消息提示
|
||||||
|
* @param column msg_unread表列名 "reply"/"at"/"love"/"system"/"whisper"/"dynamic"
|
||||||
|
*/
|
||||||
|
@PostMapping("/msg-unread/clear")
|
||||||
|
public void clearUnread(@RequestParam("column") String column) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
msgUnreadService.clearUnread(uid, column);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.search.SearchService;
|
||||||
|
import com.teriteri.backend.service.user.UserService;
|
||||||
|
import com.teriteri.backend.service.video.VideoService;
|
||||||
|
import com.teriteri.backend.utils.ESUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class SearchController {
|
||||||
|
@Autowired
|
||||||
|
private SearchService searchService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ESUtil esUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoService videoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取热搜词条
|
||||||
|
* @return 热搜列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/hot/get")
|
||||||
|
public CustomResponse getHotSearch() {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(searchService.getHotSearch());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加搜索词或者给该搜索词热度加一
|
||||||
|
* @param keyword 搜索词
|
||||||
|
* @return 返回格式化后的搜索词,有可能为null
|
||||||
|
*/
|
||||||
|
@PostMapping("/search/word/add")
|
||||||
|
public CustomResponse addSearchWord(@RequestParam("keyword") String keyword) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(searchService.addSearchWord(keyword));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据输入内容获取相关搜索推荐词
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @return 包含推荐搜索词的列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/word/get")
|
||||||
|
public CustomResponse getSearchWord(@RequestParam("keyword") String keyword) throws UnsupportedEncodingException {
|
||||||
|
keyword = URLDecoder.decode(keyword, "UTF-8"); // 解码经过url传输的字符串
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
if (keyword.trim().length() == 0) {
|
||||||
|
customResponse.setData(Collections.emptyList());
|
||||||
|
} else {
|
||||||
|
customResponse.setData(searchService.getMatchingWord(keyword));
|
||||||
|
}
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取各种类型相关数据数量 视频&用户
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @return 包含视频数量和用户数量的顺序列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/count")
|
||||||
|
public CustomResponse getCount(@RequestParam("keyword") String keyword) throws UnsupportedEncodingException {
|
||||||
|
keyword = URLDecoder.decode(keyword, "UTF-8"); // 解码经过url传输的字符串
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(searchService.getCount(keyword));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索相关已过审视频
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param page 第几页
|
||||||
|
* @return 视频列表
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
@GetMapping("/search/video/only-pass")
|
||||||
|
public CustomResponse getMatchingVideo(@RequestParam("keyword") String keyword, @RequestParam("page") Integer page) throws UnsupportedEncodingException {
|
||||||
|
keyword = URLDecoder.decode(keyword, "UTF-8"); // 解码经过url传输的字符串
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
List<Integer> vids = esUtil.searchVideosByKeyword(keyword, page, 30, true);
|
||||||
|
customResponse.setData(videoService.getVideosWithDataByIdList(vids));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/search/user")
|
||||||
|
public CustomResponse getMatchingUser(@RequestParam("keyword") String keyword, @RequestParam("page") Integer page) throws UnsupportedEncodingException {
|
||||||
|
keyword = URLDecoder.decode(keyword, "UTF-8"); // 解码经过url传输的字符串
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
List<Integer> uids = esUtil.searchUsersByKeyword(keyword, page, 30);
|
||||||
|
customResponse.setData(userService.getUserByIdList(uids));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.user.UserAccountService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class UserAccountController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAccountService userAccountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册接口
|
||||||
|
* @param map 包含 username password confirmedPassword 的 map
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
// 前端使用axios传递的data是Content-Type: application.yml/json,需要用@RequestBody获取参数
|
||||||
|
@PostMapping("/user/account/register")
|
||||||
|
public CustomResponse register(@RequestBody Map<String, String> map) {
|
||||||
|
String username = map.get("username");
|
||||||
|
String password = map.get("password");
|
||||||
|
String confirmedPassword = map.get("confirmedPassword");
|
||||||
|
try {
|
||||||
|
return userAccountService.register(username, password, confirmedPassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setCode(500);
|
||||||
|
customResponse.setMessage("特丽丽被玩坏了");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录接口
|
||||||
|
* @param map 包含 username password 的 map
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/user/account/login")
|
||||||
|
public CustomResponse login(@RequestBody Map<String, String> map) {
|
||||||
|
String username = map.get("username");
|
||||||
|
String password = map.get("password");
|
||||||
|
return userAccountService.login(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员登录接口
|
||||||
|
* @param map 包含 username password 的 map
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/admin/account/login")
|
||||||
|
public CustomResponse adminLogin(@RequestBody Map<String, String> map) {
|
||||||
|
String username = map.get("username");
|
||||||
|
String password = map.get("password");
|
||||||
|
return userAccountService.adminLogin(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户信息接口
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/user/personal/info")
|
||||||
|
public CustomResponse personalInfo() {
|
||||||
|
return userAccountService.personalInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录管理员信息接口
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/admin/personal/info")
|
||||||
|
public CustomResponse adminPersonalInfo() {
|
||||||
|
return userAccountService.adminPersonalInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/user/account/logout")
|
||||||
|
public void logout() {
|
||||||
|
userAccountService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员退出登录接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/admin/account/logout")
|
||||||
|
public void adminLogout() {
|
||||||
|
userAccountService.adminLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改当前用户密码
|
||||||
|
* @param pw 就密码
|
||||||
|
* @param npw 新密码
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/user/password/update")
|
||||||
|
public CustomResponse updatePassword(@RequestParam("pw") String pw, @RequestParam("npw") String npw) {
|
||||||
|
return userAccountService.updatePassword(pw, npw);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.comment.UserCommentService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
public class UserCommentController {
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserCommentService userCommentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞点踩评论集合
|
||||||
|
*/
|
||||||
|
@GetMapping("/comment/get-like-and-dislike")
|
||||||
|
public CustomResponse getLikeAndDislike() {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
|
||||||
|
CustomResponse response = new CustomResponse();
|
||||||
|
response.setCode(200);
|
||||||
|
response.setData(userCommentService.getUserLikeAndDislike(uid));
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞或点踩某条评论
|
||||||
|
* @param id 评论id
|
||||||
|
* @param isLike true 赞 false 踩
|
||||||
|
* @param isSet true 点 false 取消
|
||||||
|
*/
|
||||||
|
@PostMapping("/comment/love-or-not")
|
||||||
|
public CustomResponse loveOrNot(@RequestParam("id") Integer id,
|
||||||
|
@RequestParam("isLike") boolean isLike,
|
||||||
|
@RequestParam("isSet") boolean isSet) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
userCommentService.userSetLikeOrUnlike(uid, id, isLike, isSet);
|
||||||
|
return new CustomResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取UP主觉得很淦的评论
|
||||||
|
* @param uid UP主uid
|
||||||
|
* @return 点赞的评论id列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/comment/get-up-like")
|
||||||
|
public CustomResponse getUpLike(@RequestParam("uid") Integer uid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> map = userCommentService.getUserLikeAndDislike(uid);
|
||||||
|
customResponse.setData(map.get("userLike"));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.user.UserService;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class UserController {
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户部分个人信息
|
||||||
|
* @param nickname 昵称
|
||||||
|
* @param desc 个性签名
|
||||||
|
* @param gender 性别:0 女 1 男 2 保密
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/user/info/update")
|
||||||
|
public CustomResponse updateUserInfo(@RequestParam("nickname") String nickname,
|
||||||
|
@RequestParam("description") String desc,
|
||||||
|
@RequestParam("gender") Integer gender) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
try {
|
||||||
|
return userService.updateUserInfo(uid, nickname, desc, gender);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setCode(500);
|
||||||
|
customResponse.setMessage("特丽丽被玩坏了");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户头像
|
||||||
|
* @param file 头像文件
|
||||||
|
* @return 成功则返回新头像url
|
||||||
|
*/
|
||||||
|
@PostMapping("/user/avatar/update")
|
||||||
|
public CustomResponse updateUserAvatar(@RequestParam("file") MultipartFile file) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
try {
|
||||||
|
return userService.updateUserAvatar(uid, file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new CustomResponse(500, "头像更新失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user/info/get-one")
|
||||||
|
public CustomResponse getOneUserInfo(@RequestParam("uid") Integer uid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(userService.getUserById(uid));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.service.video.UserVideoService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class UserVideoController {
|
||||||
|
@Autowired
|
||||||
|
private UserVideoService userVideoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录用户播放视频时更新播放次数,有30秒更新间隔(防止用户刷播放量)
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @return 返回用户与该视频的交互数据
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/play/user")
|
||||||
|
public CustomResponse newPlayWithLoginUser(@RequestParam("vid") Integer vid) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(userVideoService.updatePlay(uid, vid));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞或点踩
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @param isLove 赞还是踩 true赞 false踩
|
||||||
|
* @param isSet 点还是取消 true点 false取消
|
||||||
|
* @return 返回用户与该视频更新后的交互数据
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/love-or-not")
|
||||||
|
public CustomResponse loveOrNot(@RequestParam("vid") Integer vid,
|
||||||
|
@RequestParam("isLove") boolean isLove,
|
||||||
|
@RequestParam("isSet") boolean isSet) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
customResponse.setData(userVideoService.setLoveOrUnlove(uid, vid, isLove, isSet));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,303 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.teriteri.backend.mapper.FavoriteVideoMapper;
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.pojo.FavoriteVideo;
|
||||||
|
import com.teriteri.backend.pojo.Video;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.service.video.VideoService;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import org.apache.ibatis.session.ExecutorType;
|
||||||
|
import org.apache.ibatis.session.SqlSession;
|
||||||
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class VideoController {
|
||||||
|
@Autowired
|
||||||
|
private VideoService videoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FavoriteVideoMapper favoriteVideoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SqlSessionFactory sqlSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新视频状态,包括过审、不通过、删除,其中审核相关需要管理员权限,删除可以是管理员或者投稿用户
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @param status 要修改的状态,1通过 2不通过 3删除
|
||||||
|
* @return 无data返回 仅返回响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/change/status")
|
||||||
|
public CustomResponse updateStatus(@RequestParam("vid") Integer vid,
|
||||||
|
@RequestParam("status") Integer status) {
|
||||||
|
try {
|
||||||
|
return videoService.updateVideoStatus(vid, status);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new CustomResponse(500, "操作失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游客访问时的feed流随机推荐
|
||||||
|
* @return 返回11条随机推荐视频
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/random/visitor")
|
||||||
|
public CustomResponse randomVideosForVisitor() {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
int count = 11;
|
||||||
|
Set<Object> idSet = redisUtil.srandmember("video_status:1", count);
|
||||||
|
List<Map<String, Object>> videoList = videoService.getVideosWithDataByIds(idSet, 1, count);
|
||||||
|
// 随机打乱列表顺序
|
||||||
|
Collections.shuffle(videoList);
|
||||||
|
customResponse.setData(videoList);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 累加获取更多视频
|
||||||
|
* @param vids 曾经查询过的视频id列表,用于去重
|
||||||
|
* @return 每次返回新的10条视频,以及其id列表,并标注是否还有更多视频可以获取
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/cumulative/visitor")
|
||||||
|
public CustomResponse cumulativeVideosForVisitor(@RequestParam("vids") String vids) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
List<Integer> vidsList = new ArrayList<>();
|
||||||
|
if (vids.trim().length() > 0) {
|
||||||
|
vidsList = Arrays.stream(vids.split(","))
|
||||||
|
.map(Integer::parseInt)
|
||||||
|
.collect(Collectors.toList()); // 从字符串切分出id列表
|
||||||
|
}
|
||||||
|
Set<Object> set = redisUtil.getMembers("video_status:1");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (set == null) {
|
||||||
|
map.put("videos", new ArrayList<>());
|
||||||
|
map.put("vids", new ArrayList<>());
|
||||||
|
map.put("more", false);
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
vidsList.forEach(set::remove); // 去除已获取的元素
|
||||||
|
Set<Object> idSet = new HashSet<>(); // 存放将要返回的id集合
|
||||||
|
Random random = new Random();
|
||||||
|
// 随机获取10个vid
|
||||||
|
for (int i = 0; i < 10 && set.size() > 0; i++) {
|
||||||
|
Object[] arr = set.toArray();
|
||||||
|
int randomIndex = random.nextInt(set.size());
|
||||||
|
idSet.add(arr[randomIndex]);
|
||||||
|
set.remove(arr[randomIndex]); // 查过的元素移除
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> videoList = videoService.getVideosWithDataByIds(idSet, 1, 10);
|
||||||
|
Collections.shuffle(videoList); // 随机打乱列表顺序
|
||||||
|
map.put("videos", videoList);
|
||||||
|
map.put("vids", idSet);
|
||||||
|
if (set.size() > 0) {
|
||||||
|
map.put("more", true);
|
||||||
|
} else {
|
||||||
|
map.put("more", false);
|
||||||
|
}
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单条视频的信息
|
||||||
|
* @param vid 视频vid
|
||||||
|
* @return 视频信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/getone")
|
||||||
|
public CustomResponse getOneVideo(@RequestParam("vid") Integer vid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> map = videoService.getVideoWithDataById(vid);
|
||||||
|
if (map == null) {
|
||||||
|
customResponse.setCode(404);
|
||||||
|
customResponse.setMessage("特丽丽没找到个视频QAQ");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
Video video = (Video) map.get("video");
|
||||||
|
if (video.getStatus() != 1) {
|
||||||
|
customResponse.setCode(404);
|
||||||
|
customResponse.setMessage("特丽丽没找到个视频QAQ");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/video/user-works-count")
|
||||||
|
public CustomResponse getUserWorksCount(@RequestParam("uid") Integer uid) {
|
||||||
|
return new CustomResponse(200, "OK", redisUtil.zCard("user_video_upload:" + uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户视频投稿
|
||||||
|
* @param uid 用户id
|
||||||
|
* @param rule 排序方式 1 投稿日期 2 播放量 3 点赞数
|
||||||
|
* @param page 分页 从1开始
|
||||||
|
* @param quantity 每页查询数量
|
||||||
|
* @return 视频信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/user-works")
|
||||||
|
public CustomResponse getUserWorks(@RequestParam("uid") Integer uid,
|
||||||
|
@RequestParam("rule") Integer rule,
|
||||||
|
@RequestParam("page") Integer page,
|
||||||
|
@RequestParam("quantity") Integer quantity) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
Set<Object> set = redisUtil.zReverange("user_video_upload:" + uid, 0, -1);
|
||||||
|
if (set == null || set.isEmpty()) {
|
||||||
|
map.put("count", 0);
|
||||||
|
map.put("list", Collections.emptyList());
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
set.forEach(vid -> {
|
||||||
|
list.add((Integer) vid);
|
||||||
|
});
|
||||||
|
map.put("count", set.size());
|
||||||
|
switch (rule) {
|
||||||
|
case 1:
|
||||||
|
map.put("list", videoService.getVideosWithDataByIdsOrderByDesc(list, "upload_date", page, quantity));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
map.put("list", videoService.getVideosWithDataByIdsOrderByDesc(list, "play", page, quantity));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
map.put("list", videoService.getVideosWithDataByIdsOrderByDesc(list, "good", page, quantity));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
map.put("list", videoService.getVideosWithDataByIdsOrderByDesc(list, "upload_date", page, quantity));
|
||||||
|
}
|
||||||
|
customResponse.setData(map);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户最近点赞视频列表
|
||||||
|
* @param uid 用户uid
|
||||||
|
* @param offset 偏移量,即当前已查询到多少条视频
|
||||||
|
* @param quantity 查询数量
|
||||||
|
* @return 视频信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/user-love")
|
||||||
|
public CustomResponse getUserLoveMovies(@RequestParam("uid") Integer uid,
|
||||||
|
@RequestParam("offset") Integer offset,
|
||||||
|
@RequestParam("quantity") Integer quantity) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Set<Object> set = redisUtil.zReverange("love_video:" + uid, (long) offset, (long) offset + quantity - 1);
|
||||||
|
if (set == null || set.isEmpty()) {
|
||||||
|
customResponse.setData(Collections.emptyList());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
set.forEach(vid -> {
|
||||||
|
list.add((Integer) vid);
|
||||||
|
});
|
||||||
|
customResponse.setData(videoService.getVideosWithDataByIdsOrderByDesc(list, null, 1, list.size()));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户最近播放视频列表
|
||||||
|
* @param offset 偏移量,即当前已查询到多少条视频
|
||||||
|
* @param quantity 查询数量
|
||||||
|
* @return 视频信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/user-play")
|
||||||
|
public CustomResponse getUserPlayMovies(@RequestParam("offset") Integer offset,
|
||||||
|
@RequestParam("quantity") Integer quantity) {
|
||||||
|
Integer uid = currentUser.getUserId();
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Set<Object> set = redisUtil.zReverange("user_video_history:" + uid, (long) offset, (long) offset + quantity - 1);
|
||||||
|
if (set == null || set.isEmpty()) {
|
||||||
|
customResponse.setData(Collections.emptyList());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
set.forEach(vid -> {
|
||||||
|
list.add((Integer) vid);
|
||||||
|
});
|
||||||
|
customResponse.setData(videoService.getVideosWithDataByIdsOrderByDesc(list, null, 1, list.size()));
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某个收藏夹的视频
|
||||||
|
* @param fid 收藏夹ID
|
||||||
|
* @param rule 排序规则 1 最近收藏 2 最多播放 3 最新投稿
|
||||||
|
* @param page 分页 从1开始
|
||||||
|
* @param quantity 每页查询数量
|
||||||
|
* @return 视频信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/user-collect")
|
||||||
|
public CustomResponse getUserCollectVideos(@RequestParam("fid") Integer fid,
|
||||||
|
@RequestParam("rule") Integer rule,
|
||||||
|
@RequestParam("page") Integer page,
|
||||||
|
@RequestParam("quantity") Integer quantity) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
Set<Object> set;
|
||||||
|
if (rule == 1) {
|
||||||
|
set = redisUtil.zReverange("favorite_video:" + fid, (long) (page - 1) * quantity, (long) page * quantity);
|
||||||
|
} else {
|
||||||
|
set = redisUtil.zReverange("favorite_video:" + fid, 0, -1);
|
||||||
|
}
|
||||||
|
if (set == null || set.isEmpty()) {
|
||||||
|
customResponse.setData(Collections.emptyList());
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
set.forEach(vid -> {
|
||||||
|
list.add((Integer) vid);
|
||||||
|
});
|
||||||
|
List<Map<String, Object>> result;
|
||||||
|
switch (rule) {
|
||||||
|
case 1:
|
||||||
|
result = videoService.getVideosWithDataByIdsOrderByDesc(list, null, page, quantity);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
result = videoService.getVideosWithDataByIdsOrderByDesc(list, "play", page, quantity);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
result = videoService.getVideosWithDataByIdsOrderByDesc(list, "upload_date", page, quantity);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = videoService.getVideosWithDataByIdsOrderByDesc(list, null, page, quantity);
|
||||||
|
}
|
||||||
|
if (result.size() == 0) {
|
||||||
|
customResponse.setData(result);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
|
||||||
|
result.stream().parallel().forEach(map -> {
|
||||||
|
Video video = (Video) map.get("video");
|
||||||
|
QueryWrapper<FavoriteVideo> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("vid", video.getVid()).eq("fid", fid);
|
||||||
|
map.put("info", favoriteVideoMapper.selectOne(queryWrapper));
|
||||||
|
});
|
||||||
|
sqlSession.commit();
|
||||||
|
}
|
||||||
|
customResponse.setData(result);
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.utils.CurrentUser;
|
||||||
|
import com.teriteri.backend.service.video.VideoReviewService;
|
||||||
|
import com.teriteri.backend.service.video.VideoService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class VideoReviewController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoReviewService videoReviewService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoService videoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CurrentUser currentUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核 查询对应状态的视频数量
|
||||||
|
* @param status 状态 0待审核 1通过 2未通过
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/review/video/total")
|
||||||
|
public CustomResponse getTotal(@RequestParam("vstatus") Integer status) {
|
||||||
|
return videoReviewService.getTotalByStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核 分页查询对应状态视频
|
||||||
|
* @param status 状态 0待审核 1通过 2未通过
|
||||||
|
* @param page 当前页
|
||||||
|
* @param quantity 每页的数量
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/review/video/getpage")
|
||||||
|
public CustomResponse getVideos(@RequestParam("vstatus") Integer status,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "quantity", defaultValue = "10") Integer quantity) {
|
||||||
|
return videoReviewService.getVideosByPage(status, page, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核 查询单个视频详情
|
||||||
|
* @param vid 视频id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/review/video/getone")
|
||||||
|
public CustomResponse getOneVideo(@RequestParam("vid") Integer vid) {
|
||||||
|
CustomResponse customResponse = new CustomResponse();
|
||||||
|
if (!currentUser.isAdmin()) {
|
||||||
|
customResponse.setCode(403);
|
||||||
|
customResponse.setMessage("您不是管理员,无权访问");
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
Map<String, Object> map = videoService.getVideoWithDataById(vid);
|
||||||
|
customResponse.setData(map); // 如果是是空照样返回,前端自动处理
|
||||||
|
return customResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.service.video.VideoStatsService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class VideoStatsController {
|
||||||
|
@Autowired
|
||||||
|
private VideoStatsService videoStatsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游客观看视频时更新视频播放量数据,这个做不到时间间隔,就是说每次刷新都会播放数加一,有一个思路是使用浏览器指纹,但是我不会
|
||||||
|
* @param vid 视频ID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
//使用浏览器指纹
|
||||||
|
|
||||||
|
// 目前 : 新建一个指纹表
|
||||||
|
// 游客的话
|
||||||
|
// 指纹信息 写入
|
||||||
|
// 用户id 为空
|
||||||
|
// 绑定视频
|
||||||
|
// 如果 该视频下存在用户id为空指纹信息 不增加播放量
|
||||||
|
|
||||||
|
// 如果 该视频下存在用户id为空指纹信息
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/video/play/visitor")
|
||||||
|
public CustomResponse newPlayWithVisitor(@RequestParam("vid") Integer vid) {
|
||||||
|
videoStatsService.updateStats(vid, "play", true, 1);
|
||||||
|
return new CustomResponse();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.teriteri.backend.controller;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.pojo.dto.VideoUploadInfoDTO;
|
||||||
|
import com.teriteri.backend.service.video.VideoUploadService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class VideoUploadController {
|
||||||
|
@Autowired
|
||||||
|
private VideoUploadService videoUploadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前视频准备要上传的分片序号
|
||||||
|
* @param hash 视频的hash值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/ask-chunk")
|
||||||
|
public CustomResponse askChunk(@RequestParam("hash") String hash) {
|
||||||
|
return videoUploadService.askCurrentChunk(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传分片
|
||||||
|
* @param chunk 分片的blob文件
|
||||||
|
* @param hash 视频的hash值
|
||||||
|
* @param index 当前分片的序号
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/upload-chunk")
|
||||||
|
public CustomResponse uploadChunk(@RequestParam("chunk") MultipartFile chunk,
|
||||||
|
@RequestParam("hash") String hash,
|
||||||
|
@RequestParam("index") Integer index) throws IOException {
|
||||||
|
try {
|
||||||
|
return videoUploadService.uploadChunk(chunk, hash, index);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new CustomResponse(500, "分片上传失败", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消上传
|
||||||
|
* @param hash 视频的hash值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/video/cancel-upload")
|
||||||
|
public CustomResponse cancelUpload(@RequestParam("hash") String hash) {
|
||||||
|
return videoUploadService.cancelUpload(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加视频投稿
|
||||||
|
* @param cover 封面文件
|
||||||
|
* @param hash 视频的hash值
|
||||||
|
* @param title 投稿标题
|
||||||
|
* @param type 视频类型 1自制 2转载
|
||||||
|
* @param auth 作者声明 0不声明 1未经允许禁止转载
|
||||||
|
* @param duration 视频总时长
|
||||||
|
* @param mcid 主分区ID
|
||||||
|
* @param scid 子分区ID
|
||||||
|
* @param tags 标签
|
||||||
|
* @param descr 简介
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
@PostMapping("/video/add")
|
||||||
|
public CustomResponse addVideo(@RequestParam("cover") MultipartFile cover,
|
||||||
|
@RequestParam("hash") String hash,
|
||||||
|
@RequestParam("title") String title,
|
||||||
|
@RequestParam("type") Integer type,
|
||||||
|
@RequestParam("auth") Integer auth,
|
||||||
|
@RequestParam("duration") Double duration,
|
||||||
|
@RequestParam("mcid") String mcid,
|
||||||
|
@RequestParam("scid") String scid,
|
||||||
|
@RequestParam("tags") String tags,
|
||||||
|
@RequestParam("descr") String descr) {
|
||||||
|
VideoUploadInfoDTO videoUploadInfoDTO = new VideoUploadInfoDTO(null, hash, title, type, auth, duration, mcid, scid, tags, descr, null);
|
||||||
|
try {
|
||||||
|
return videoUploadService.addVideo(cover, videoUploadInfoDTO);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new CustomResponse(500, "封面上传失败", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.teriteri.backend.im;
|
||||||
|
|
||||||
|
import com.teriteri.backend.im.handler.TokenValidationHandler;
|
||||||
|
import com.teriteri.backend.im.handler.WebSocketHandler;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||||
|
import io.netty.handler.codec.http.HttpServerCodec;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class IMServer {
|
||||||
|
|
||||||
|
// 存储每个用户的全部连接
|
||||||
|
public static final Map<Integer, Set<Channel>> userChannel = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void start() throws InterruptedException {
|
||||||
|
|
||||||
|
// 主从结构
|
||||||
|
EventLoopGroup boss = new NioEventLoopGroup();
|
||||||
|
EventLoopGroup worker = new NioEventLoopGroup();
|
||||||
|
|
||||||
|
// 绑定监听端口
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
bootstrap.group(boss, worker)
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
|
||||||
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
||||||
|
ChannelPipeline pipeline = socketChannel.pipeline(); // 处理流
|
||||||
|
|
||||||
|
// 添加 Http 编码解码器
|
||||||
|
pipeline.addLast(new HttpServerCodec())
|
||||||
|
// 添加处理大数据的组件
|
||||||
|
.addLast(new ChunkedWriteHandler())
|
||||||
|
// 对 Http 消息做聚合操作方便处理,产生 FullHttpRequest 和 FullHttpResponse
|
||||||
|
// 1024 * 64 是单条信息最长字节数
|
||||||
|
.addLast(new HttpObjectAggregator(1024 * 64))
|
||||||
|
.addLast(new TokenValidationHandler())
|
||||||
|
// 添加 WebSocket 支持
|
||||||
|
.addLast(new WebSocketServerProtocolHandler("/im"))
|
||||||
|
.addLast(new WebSocketHandler());
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ChannelFuture future = bootstrap.bind(7071).sync();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
package com.teriteri.backend.im.handler;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.teriteri.backend.im.IMServer;
|
||||||
|
import com.teriteri.backend.mapper.ChatDetailedMapper;
|
||||||
|
import com.teriteri.backend.pojo.ChatDetailed;
|
||||||
|
import com.teriteri.backend.pojo.IMResponse;
|
||||||
|
import com.teriteri.backend.service.message.ChatService;
|
||||||
|
import com.teriteri.backend.service.user.UserService;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ChatHandler {
|
||||||
|
|
||||||
|
private static ChatService chatService;
|
||||||
|
private static ChatDetailedMapper chatDetailedMapper;
|
||||||
|
private static UserService userService;
|
||||||
|
private static RedisUtil redisUtil;
|
||||||
|
private static Executor taskExecutor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private void setDependencies(ChatService chatService,
|
||||||
|
ChatDetailedMapper chatDetailedMapper,
|
||||||
|
UserService userService,
|
||||||
|
RedisUtil redisUtil,
|
||||||
|
@Qualifier("taskExecutor") Executor taskExecutor) {
|
||||||
|
ChatHandler.chatService = chatService;
|
||||||
|
ChatHandler.chatDetailedMapper = chatDetailedMapper;
|
||||||
|
ChatHandler.userService = userService;
|
||||||
|
ChatHandler.redisUtil = redisUtil;
|
||||||
|
ChatHandler.taskExecutor = taskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
* @param ctx
|
||||||
|
* @param tx
|
||||||
|
*/
|
||||||
|
public static void send(ChannelHandlerContext ctx, TextWebSocketFrame tx) {
|
||||||
|
try {
|
||||||
|
ChatDetailed chatDetailed = JSONObject.parseObject(tx.text(), ChatDetailed.class);
|
||||||
|
// System.out.println("接收到聊天消息:" + chatDetailed);
|
||||||
|
|
||||||
|
// 从channel中获取当前用户id 封装写库
|
||||||
|
Integer user_id = (Integer) ctx.channel().attr(AttributeKey.valueOf("userId")).get();
|
||||||
|
chatDetailed.setUserId(user_id);
|
||||||
|
chatDetailed.setUserDel(0);
|
||||||
|
chatDetailed.setAnotherDel(0);
|
||||||
|
chatDetailed.setWithdraw(0);
|
||||||
|
chatDetailed.setTime(new Date());
|
||||||
|
chatDetailedMapper.insert(chatDetailed);
|
||||||
|
// "chat_detailed_zset:对方:自己"
|
||||||
|
redisUtil.zset("chat_detailed_zset:" + user_id + ":" + chatDetailed.getAnotherId(), chatDetailed.getId());
|
||||||
|
redisUtil.zset("chat_detailed_zset:" + chatDetailed.getAnotherId() + ":" + user_id, chatDetailed.getId());
|
||||||
|
boolean online = chatService.updateChat(user_id, chatDetailed.getAnotherId());
|
||||||
|
|
||||||
|
// 转发到发送者和接收者的全部channel
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("type", "接收");
|
||||||
|
map.put("online", online); // 对方是否在窗口
|
||||||
|
map.put("detail", chatDetailed);
|
||||||
|
CompletableFuture<Void> chatFuture = CompletableFuture.runAsync(() -> {
|
||||||
|
map.put("chat", chatService.getChat(user_id, chatDetailed.getAnotherId()));
|
||||||
|
}, taskExecutor);
|
||||||
|
CompletableFuture<Void> userFuture = CompletableFuture.runAsync(() -> {
|
||||||
|
map.put("user", userService.getUserById(user_id));
|
||||||
|
}, taskExecutor);
|
||||||
|
chatFuture.join();
|
||||||
|
userFuture.join();
|
||||||
|
|
||||||
|
// 发给自己的全部channel
|
||||||
|
Set<Channel> from = IMServer.userChannel.get(user_id);
|
||||||
|
if (from != null) {
|
||||||
|
for (Channel channel : from) {
|
||||||
|
channel.writeAndFlush(IMResponse.message("whisper", map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发给对方的全部channel
|
||||||
|
Set<Channel> to = IMServer.userChannel.get(chatDetailed.getAnotherId());
|
||||||
|
if (to != null) {
|
||||||
|
for (Channel channel : to) {
|
||||||
|
channel.writeAndFlush(IMResponse.message("whisper", map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送聊天信息时出错了:" + e);
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("发送消息时出错了 Σ(゚д゚;)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回消息
|
||||||
|
* @param ctx
|
||||||
|
* @param tx
|
||||||
|
*/
|
||||||
|
public static void withdraw(ChannelHandlerContext ctx, TextWebSocketFrame tx) {
|
||||||
|
try {
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(tx.text());
|
||||||
|
Integer id = jsonObject.getInteger("id");
|
||||||
|
Integer user_id = (Integer) ctx.channel().attr(AttributeKey.valueOf("userId")).get();
|
||||||
|
|
||||||
|
// 查询数据库
|
||||||
|
ChatDetailed chatDetailed = chatDetailedMapper.selectById(id);
|
||||||
|
if (chatDetailed == null) {
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("消息不存在"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(chatDetailed.getUserId(), user_id)) {
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("无权撤回此消息"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long diff = System.currentTimeMillis() - chatDetailed.getTime().getTime();
|
||||||
|
if (diff > 120000) {
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("发送时间超过两分钟不能撤回"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 更新 withdraw 字段
|
||||||
|
UpdateWrapper<ChatDetailed> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("id", id).setSql("withdraw = 1");
|
||||||
|
chatDetailedMapper.update(null, updateWrapper);
|
||||||
|
|
||||||
|
// 转发到发送者和接收者的全部channel
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("type", "撤回");
|
||||||
|
map.put("sendId", chatDetailed.getUserId());
|
||||||
|
map.put("acceptId", chatDetailed.getAnotherId());
|
||||||
|
map.put("id", id);
|
||||||
|
|
||||||
|
// 发给自己的全部channel
|
||||||
|
Set<Channel> from = IMServer.userChannel.get(user_id);
|
||||||
|
if (from != null) {
|
||||||
|
for (Channel channel : from) {
|
||||||
|
channel.writeAndFlush(IMResponse.message("whisper", map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发给对方的全部channel
|
||||||
|
Set<Channel> to = IMServer.userChannel.get(chatDetailed.getAnotherId());
|
||||||
|
if (to != null) {
|
||||||
|
for (Channel channel : to) {
|
||||||
|
channel.writeAndFlush(IMResponse.message("whisper", map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("撤回聊天信息时出错了:" + e);
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("撤回消息时出错了 Σ(゚д゚;)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.teriteri.backend.im.handler;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.teriteri.backend.im.IMServer;
|
||||||
|
import com.teriteri.backend.pojo.Command;
|
||||||
|
import com.teriteri.backend.pojo.IMResponse;
|
||||||
|
import com.teriteri.backend.pojo.User;
|
||||||
|
import com.teriteri.backend.utils.JwtUtil;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TokenValidationHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||||
|
|
||||||
|
private static JwtUtil jwtUtil;
|
||||||
|
private static RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setDependencies(JwtUtil jwtUtil, RedisUtil redisUtil) {
|
||||||
|
TokenValidationHandler.jwtUtil = jwtUtil;
|
||||||
|
TokenValidationHandler.redisUtil = redisUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenValidationHandler() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame tx) {
|
||||||
|
Command command = JSON.parseObject(tx.text(), Command.class);
|
||||||
|
String token = command.getContent();
|
||||||
|
|
||||||
|
Integer uid = isValidToken(token);
|
||||||
|
if (uid != null) {
|
||||||
|
// 将uid绑到ctx上
|
||||||
|
ctx.channel().attr(AttributeKey.valueOf("userId")).set(uid);
|
||||||
|
// 将channel存起来
|
||||||
|
if (IMServer.userChannel.get(uid) == null) {
|
||||||
|
Set<Channel> set = new HashSet<>();
|
||||||
|
set.add(ctx.channel());
|
||||||
|
IMServer.userChannel.put(uid, set);
|
||||||
|
} else {
|
||||||
|
IMServer.userChannel.get(uid).add(ctx.channel());
|
||||||
|
}
|
||||||
|
redisUtil.addMember("login_member", uid); // 将用户添加到在线用户集合
|
||||||
|
// System.out.println("该用户的全部连接状态:" + IMServer.userChannel.get(uid));
|
||||||
|
// System.out.println("当前在线人数:" + IMServer.userChannel.size());
|
||||||
|
// 移除token验证处理器,以便以后使用无需判断
|
||||||
|
ctx.pipeline().remove(TokenValidationHandler.class);
|
||||||
|
// 保持消息的引用计数,以确保消息不会被释放
|
||||||
|
tx.retain();
|
||||||
|
// 将消息传递给下一个处理器
|
||||||
|
ctx.fireChannelRead(tx);
|
||||||
|
} else {
|
||||||
|
ctx.channel().writeAndFlush(IMResponse.error("登录已过期"));
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行JWT验证
|
||||||
|
* @param token Bearer JWT
|
||||||
|
* @return 返回用户ID 验证不通过则返回null
|
||||||
|
*/
|
||||||
|
private Integer isValidToken(String token) {
|
||||||
|
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = token.substring(7);
|
||||||
|
|
||||||
|
// 解析token
|
||||||
|
boolean verifyToken = jwtUtil.verifyToken(token);
|
||||||
|
if (!verifyToken) {
|
||||||
|
log.error("当前token已过期");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String userId = JwtUtil.getSubjectFromToken(token);
|
||||||
|
String role = JwtUtil.getClaimFromToken(token, "role");
|
||||||
|
User user = redisUtil.getObject("security:" + role + ":" + userId, User.class);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
log.error("用户未登录");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.info("成功通过验证!");
|
||||||
|
return user.getUid();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.teriteri.backend.im.handler;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.teriteri.backend.im.IMServer;
|
||||||
|
import com.teriteri.backend.pojo.Command;
|
||||||
|
import com.teriteri.backend.pojo.CommandType;
|
||||||
|
import com.teriteri.backend.pojo.IMResponse;
|
||||||
|
import com.teriteri.backend.utils.RedisUtil;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||||
|
|
||||||
|
private static RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setDependencies(RedisUtil redisUtil) {
|
||||||
|
WebSocketHandler.redisUtil = redisUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame tx) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Command command = JSON.parseObject(tx.text(), Command.class);
|
||||||
|
// System.out.println("command: " + command);
|
||||||
|
// 根据code分发不同处理程序
|
||||||
|
switch (CommandType.match(command.getCode())) {
|
||||||
|
case CONNETION: // 如果是连接消息就不需要做任何操作了,因为连接上的话在token鉴权那就做了
|
||||||
|
break;
|
||||||
|
case CHAT_SEND:
|
||||||
|
ChatHandler.send(ctx, tx);
|
||||||
|
break;
|
||||||
|
case CHAT_WITHDRAW:
|
||||||
|
ChatHandler.withdraw(ctx, tx);
|
||||||
|
break;
|
||||||
|
default: ctx.channel().writeAndFlush(IMResponse.error("不支持的CODE " + command.getCode()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接断开时执行 将channel从集合中移除 如果集合为空则从Map中移除该用户 即离线状态
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) {
|
||||||
|
// 当连接断开时,从 userChannel 中移除对应的 Channel
|
||||||
|
Integer uid = (Integer) ctx.channel().attr(AttributeKey.valueOf("userId")).get();
|
||||||
|
Set<Channel> userChannels = IMServer.userChannel.get(uid);
|
||||||
|
// System.out.println("移除channel前的集合状态:" + userChannels);
|
||||||
|
if (userChannels != null) {
|
||||||
|
userChannels.remove(ctx.channel());
|
||||||
|
// System.out.println("移除channel后的集合状态:" + IMServer.userChannel.get(uid));
|
||||||
|
// 用户离线操作
|
||||||
|
if (IMServer.userChannel.get(uid).size() == 0) {
|
||||||
|
IMServer.userChannel.remove(uid);
|
||||||
|
// System.out.println("当前在线人数:" + IMServer.userChannel.size());
|
||||||
|
redisUtil.deleteKeysWithPrefix("whisper:" + uid + ":"); // 清除全部在聊天窗口的状态
|
||||||
|
redisUtil.delMember("login_member", uid); // 从在线用户集合中移除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 继续处理后续逻辑
|
||||||
|
ctx.fireChannelInactive();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Category;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CategoryMapper extends BaseMapper<Category> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.ChatDetailed;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ChatDetailedMapper extends BaseMapper<ChatDetailed> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Chat;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ChatMapper extends BaseMapper<Chat> {
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Comment;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CommentMapper extends BaseMapper<Comment> {
|
||||||
|
// @Select("SELECT * FROM comment WHERE root_id = #{rootId} AND vid = #{vid}")
|
||||||
|
// List<Comment> getChildCommentsByRootId(@Param("rootId") Integer rootId, @Param("vid") Integer vid);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM comment WHERE vid = #{vid} AND root_id = 0")
|
||||||
|
List<Comment> getRootCommentsByVid(@Param("vid") Integer vid);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Danmu;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface DanmuMapper extends BaseMapper<Danmu> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Favorite;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface FavoriteMapper extends BaseMapper<Favorite> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.FavoriteVideo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface FavoriteVideoMapper extends BaseMapper<FavoriteVideo> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.MsgUnread;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface MsgUnreadMapper extends BaseMapper<MsgUnread> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.User;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper extends BaseMapper<User> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.UserVideo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserVideoMapper extends BaseMapper<UserVideo> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.Video;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface VideoMapper extends BaseMapper<Video> {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.teriteri.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.teriteri.backend.pojo.VideoStats;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface VideoStatsMapper extends BaseMapper<VideoStats> {
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Category {
|
||||||
|
private String mcId;
|
||||||
|
private String scId;
|
||||||
|
private String mcName;
|
||||||
|
private String scName;
|
||||||
|
private String descr;
|
||||||
|
private String rcmTag;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Chat {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private Integer userId; // 发送者UID
|
||||||
|
private Integer anotherId; // 接收者UID
|
||||||
|
private Integer isDeleted; // 是否移除聊天 0否 1是
|
||||||
|
private Integer unread; // 消息未读数
|
||||||
|
private Date latestTime; // 最近接收消息的时间或最近打开聊天窗口的时间
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ChatDetailed {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id; // 消息id
|
||||||
|
private Integer userId; // 发送者uid
|
||||||
|
private Integer anotherId; // 接受者uid
|
||||||
|
private String content; // 消息内容
|
||||||
|
private Integer userDel; // 发送者是否删除
|
||||||
|
private Integer anotherDel; // 接受者者是否删除
|
||||||
|
private Integer withdraw; // 消息是否被撤回
|
||||||
|
private Date time; // 发送消息的时间
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Command {
|
||||||
|
private Integer code;
|
||||||
|
private String content;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommandType {
|
||||||
|
/**
|
||||||
|
* 建立连接
|
||||||
|
*/
|
||||||
|
CONNETION(100),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天功能 发送
|
||||||
|
*/
|
||||||
|
CHAT_SEND(101),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天功能 撤回
|
||||||
|
*/
|
||||||
|
CHAT_WITHDRAW(102),
|
||||||
|
|
||||||
|
ERROR(-1),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
public static CommandType match(Integer code) {
|
||||||
|
for (CommandType value: CommandType.values()) {
|
||||||
|
if (value.getCode().equals(code)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Comment {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private Integer vid;
|
||||||
|
private Integer uid;
|
||||||
|
private Integer rootId;
|
||||||
|
private Integer parentId;
|
||||||
|
private Integer toUserId;
|
||||||
|
private String content;
|
||||||
|
private Integer love;
|
||||||
|
private Integer bad;
|
||||||
|
private Date createTime;
|
||||||
|
private Integer isTop;
|
||||||
|
private Integer isDeleted;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.dto.UserDTO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CommentTree {
|
||||||
|
private Integer id;
|
||||||
|
private Integer vid;
|
||||||
|
private Integer rootId;
|
||||||
|
private Integer parentId;
|
||||||
|
private String content;
|
||||||
|
private UserDTO user;
|
||||||
|
private UserDTO toUser;
|
||||||
|
private Integer love;
|
||||||
|
private Integer bad;
|
||||||
|
private List<CommentTree> replies;
|
||||||
|
private Date createTime;
|
||||||
|
private Long count;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应包装类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
// 自定义响应对象
|
||||||
|
public class CustomResponse {
|
||||||
|
private int code = 200;
|
||||||
|
private String message = "OK";
|
||||||
|
private Object data;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Danmu {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id; // 弹幕ID
|
||||||
|
private Integer vid; // 视频ID
|
||||||
|
private Integer uid; // 用户ID
|
||||||
|
private String content; // 弹幕内容
|
||||||
|
private Integer fontsize; // 字体大小 默认25 小18
|
||||||
|
private Integer mode; // 模式 1滚动 2顶部 3底部
|
||||||
|
private String color; // 字体颜色 6位十六进制标准格式 #FFFFFF
|
||||||
|
private Double timePoint; // 弹幕在视频中的时间点位置(秒)
|
||||||
|
private Integer state; // 弹幕状态 1默认过审 2被举报审核中 3删除
|
||||||
|
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
|
||||||
|
private Date createDate; // 弹幕发送日期时间
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ESSearchWord {
|
||||||
|
private String content;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ESUser {
|
||||||
|
private Integer uid;
|
||||||
|
private String nickname;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ESVideo {
|
||||||
|
private Integer vid;
|
||||||
|
private Integer uid;
|
||||||
|
private String title;
|
||||||
|
private String mc_id;
|
||||||
|
private String sc_id;
|
||||||
|
private String tags;
|
||||||
|
private Integer status;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Favorite {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer fid; // 收藏夹ID
|
||||||
|
private Integer uid; // 所属用户ID
|
||||||
|
private Integer type; // 收藏夹类型 1默认收藏夹 2用户创建
|
||||||
|
private Integer visible; // 对外开放 0隐藏 1公开
|
||||||
|
private String cover; // 收藏夹封面url
|
||||||
|
private String title; // 收藏夹名称
|
||||||
|
private String description; // 简介
|
||||||
|
private Integer count; // 收藏夹中视频数量
|
||||||
|
private Integer isDelete; // 是否删除 1已删除
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class FavoriteVideo {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private Integer vid; // 视频ID
|
||||||
|
private Integer fid; // 收藏夹ID
|
||||||
|
private Date time; // 收藏时间
|
||||||
|
private Integer isRemove; // 是否移除 1已移出收藏夹
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class HotSearch {
|
||||||
|
private String content; // 内容
|
||||||
|
private Double score; // 热度
|
||||||
|
private Integer type = 0; // 类型: 0 普通 1 新 2 热
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class IMResponse {
|
||||||
|
private String type; // 消息类型 如:"reply","at","love","system","whisper","dynamic" / "error"
|
||||||
|
private LocalDateTime time;
|
||||||
|
private Object data; // 返回的相关数据
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回系统失败消息
|
||||||
|
* @param message 自定义系统消息提示
|
||||||
|
* @return 返回系统失败消息
|
||||||
|
*/
|
||||||
|
public static TextWebSocketFrame error(String message) {
|
||||||
|
return new TextWebSocketFrame(JSON.toJSONString(new IMResponse("error", LocalDateTime.now(), message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非系统类消息
|
||||||
|
* @param type 消息类型 如:"reply","at","love","system","whisper","dynamic" 对应 msg_unread 表的每一列
|
||||||
|
* @param data 返回的相关数据
|
||||||
|
* @return 返回非系统消息以及携带数据
|
||||||
|
*/
|
||||||
|
public static TextWebSocketFrame message(String type, Object data) {
|
||||||
|
return new TextWebSocketFrame(JSON.toJSONString(new IMResponse(type, LocalDateTime.now(), data)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MsgUnread {
|
||||||
|
@TableId
|
||||||
|
private Integer uid; // 用户uid 不自动增长 跟随注册时的uid
|
||||||
|
private Integer reply; // 回复我的
|
||||||
|
private Integer at; // @ 我的
|
||||||
|
private Integer love; // 收到的赞
|
||||||
|
private Integer system; // 系统通知
|
||||||
|
private Integer whisper; // 我的消息(私聊总数)
|
||||||
|
private Integer dynamic; // 动态
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class User {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer uid;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String nickname;
|
||||||
|
private String avatar;
|
||||||
|
private String background;
|
||||||
|
private Integer gender; // 性别,0女性 1男性 2无性别,默认2
|
||||||
|
private String description;
|
||||||
|
private Integer exp; // 经验值 50/200/1500/4500/10800/28800 分别是0~6级的区间
|
||||||
|
private Double coin; // 硬币数 保留一位小数
|
||||||
|
private Integer vip; // 0 普通用户,1 月度大会员,2 季度大会员,3 年度大会员
|
||||||
|
private Integer state; // 0 正常,1 封禁中,2 已注销
|
||||||
|
private Integer role; // 0 普通用户,1 普通管理员,2 超级管理员
|
||||||
|
private Integer auth; // 0 普通用户,1 个人认证,2 机构认证
|
||||||
|
private String authMsg; // 认证信息,如 teriteri官方账号
|
||||||
|
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
|
||||||
|
private Date createDate;
|
||||||
|
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
|
||||||
|
private Date deleteDate;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserVideo {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id; // 唯一标识
|
||||||
|
private Integer uid; // 观看视频的用户ID
|
||||||
|
private Integer vid; // 视频ID
|
||||||
|
private Integer play; // 观看次数
|
||||||
|
private Integer love; // 点赞 0没赞 1已点赞
|
||||||
|
private Integer unlove; // 不喜欢 0没点 1已不喜欢
|
||||||
|
private Integer coin; // 投币数 0-2 一个视频一个用户上限投2个币
|
||||||
|
private Integer collect; // 收藏 0未收藏 1已收藏
|
||||||
|
private Date playTime; // 最近观看时间
|
||||||
|
private Date loveTime; // 最近点赞时间
|
||||||
|
private Date coinTime; // 最近投币时间
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Video {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer vid;
|
||||||
|
private Integer uid;
|
||||||
|
private String title;
|
||||||
|
private Integer type;
|
||||||
|
private Integer auth;
|
||||||
|
private Double duration;
|
||||||
|
private String mcId;
|
||||||
|
private String scId;
|
||||||
|
private String tags;
|
||||||
|
private String descr;
|
||||||
|
private String coverUrl;
|
||||||
|
private String videoUrl;
|
||||||
|
private Integer status; // 0审核中 1通过审核 2打回整改(指投稿信息不符) 3视频违规删除(视频内容违规)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
|
||||||
|
private Date uploadDate;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Shanghai")
|
||||||
|
private Date deleteDate;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.teriteri.backend.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class VideoStats {
|
||||||
|
@TableId
|
||||||
|
private Integer vid;
|
||||||
|
private Integer play;
|
||||||
|
private Integer danmu;
|
||||||
|
private Integer good;
|
||||||
|
private Integer bad;
|
||||||
|
private Integer coin;
|
||||||
|
private Integer collect;
|
||||||
|
private Integer share;
|
||||||
|
private Integer comment;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.teriteri.backend.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CategoryDTO {
|
||||||
|
private String mcId;
|
||||||
|
private String mcName;
|
||||||
|
private List<Map<String, Object>> scList;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.teriteri.backend.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserDTO {
|
||||||
|
private Integer uid;
|
||||||
|
private String nickname;
|
||||||
|
private String avatar_url;
|
||||||
|
private String bg_url;
|
||||||
|
private Integer gender; // 性别,0女性 1男性 2无性别,默认2
|
||||||
|
private String description;
|
||||||
|
private Integer exp; // 经验值 50/200/1500/4500/10800/28800 分别是0~6级的区间
|
||||||
|
private Double coin; // 硬币数 保留一位小数
|
||||||
|
private Integer vip; // 0 普通用户,1 月度大会员,2 季度大会员,3 年度大会员
|
||||||
|
private Integer state; // 0 正常,1 封禁中
|
||||||
|
private Integer auth; // 0 普通用户,1 个人认证,2 机构认证
|
||||||
|
private String authMsg; // 认证信息,如 teriteri官方账号
|
||||||
|
private Integer videoCount; // 视频投稿数
|
||||||
|
private Integer followsCount; // 关注数
|
||||||
|
private Integer fansCount; // 粉丝数
|
||||||
|
private Integer loveCount; // 获赞数
|
||||||
|
private Integer playCount; // 播放数
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.teriteri.backend.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class VideoUploadInfoDTO {
|
||||||
|
private Integer uid;
|
||||||
|
private String hash;
|
||||||
|
private String title;
|
||||||
|
private Integer type;
|
||||||
|
private Integer auth;
|
||||||
|
private Double duration;
|
||||||
|
private String mcId;
|
||||||
|
private String scId;
|
||||||
|
private String tags;
|
||||||
|
private String descr;
|
||||||
|
private String coverUrl;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.teriteri.backend.service.category;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.Category;
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
|
||||||
|
public interface CategoryService {
|
||||||
|
/**
|
||||||
|
* 获取全部分区数据
|
||||||
|
* @return CustomResponse对象
|
||||||
|
*/
|
||||||
|
CustomResponse getAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询对应分区信息
|
||||||
|
* @param mcId 主分区ID
|
||||||
|
* @param scId 子分区ID
|
||||||
|
* @return Category类信息
|
||||||
|
*/
|
||||||
|
Category getCategoryById(String mcId, String scId);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.teriteri.backend.service.comment;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.Comment;
|
||||||
|
import com.teriteri.backend.pojo.CommentTree;
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface CommentService {
|
||||||
|
List<CommentTree> getCommentTreeByVid(Integer vid, Long offset, Integer type);
|
||||||
|
|
||||||
|
CommentTree sendComment(Integer vid, Integer uid, Integer rootId, Integer parentId, Integer toUserId, String content);
|
||||||
|
|
||||||
|
CustomResponse deleteComment(Integer id, Integer uid, boolean isAdmin);
|
||||||
|
|
||||||
|
List<Comment> getChildCommentsByRootId(Integer rootId, Integer vid, Long start, Long stop);
|
||||||
|
|
||||||
|
List<Comment> getRootCommentsByVid(Integer vid, Long offset, Integer type);
|
||||||
|
|
||||||
|
CommentTree getMoreCommentsById(Integer id);
|
||||||
|
|
||||||
|
/*
|
||||||
|
评论点赞点踩相关
|
||||||
|
*/
|
||||||
|
void updateLikeAndDisLike(Integer id, boolean addLike);
|
||||||
|
|
||||||
|
void updateComment(Integer id, String column, boolean incr, Integer count);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.teriteri.backend.service.comment;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface UserCommentService {
|
||||||
|
Map<String, Object> getUserLikeAndDislike(Integer uid);
|
||||||
|
|
||||||
|
void userSetLikeOrUnlike(Integer uid, Integer id, boolean isLike, boolean isSet);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.teriteri.backend.service.danmu;
|
||||||
|
|
||||||
|
import com.teriteri.backend.pojo.CustomResponse;
|
||||||
|
import com.teriteri.backend.pojo.Danmu;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface DanmuService {
|
||||||
|
/**
|
||||||
|
* 根据弹幕ID集合查询弹幕列表
|
||||||
|
* @param idset 弹幕ID集合
|
||||||
|
* @return 弹幕列表
|
||||||
|
*/
|
||||||
|
List<Danmu> getDanmuListByIdset(Set<Object> idset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除弹幕
|
||||||
|
* @param id 弹幕id
|
||||||
|
* @param uid 操作用户
|
||||||
|
* @param isAdmin 是否管理员
|
||||||
|
* @return 响应对象
|
||||||
|
*/
|
||||||
|
CustomResponse deleteDanmu(Integer id, Integer uid, boolean isAdmin);
|
||||||
|
}
|