Compare commits
36 Commits
Author | SHA1 | Date |
---|---|---|
|
627b12f099 | |
|
45b2eea3d2 | |
|
9afa1cb1c4 | |
|
38a72d5ea2 | |
|
3738110395 | |
|
629678241f | |
|
deed1bb13a | |
|
ce9823c369 | |
|
f6651db2b8 | |
|
17c571c61d | |
|
2423e27b34 | |
|
a9212be666 | |
|
d82bf584bb | |
|
a6a12a3b04 | |
|
89f8550dff | |
|
f51134ea29 | |
|
556802f406 | |
|
9693cfc524 | |
|
7f94911665 | |
|
91b482d0bc | |
|
fc7524589c | |
|
736b824d7f | |
|
4b5a912784 | |
|
f0e417f1d7 | |
|
4deba9f86c | |
|
102c9c4a38 | |
|
45512694b6 | |
|
1551cc1263 | |
|
9551f322f0 | |
|
1ffa7c4a75 | |
|
8154807f90 | |
|
256743d3b9 | |
|
bc378ee9ec | |
|
0350786d00 | |
|
378c4cbdf8 | |
|
85b2729a04 |
|
@ -1,28 +0,0 @@
|
|||
listen 1935;
|
||||
max_connections 1000;
|
||||
daemon off;
|
||||
http_api {
|
||||
enabled on;
|
||||
listen 1985;
|
||||
}
|
||||
http_server {
|
||||
enabled on;
|
||||
listen 8080;
|
||||
}
|
||||
vhost __defaultVhost__ {
|
||||
hls {
|
||||
enabled on;
|
||||
hls_path ./objs/nginx/html;
|
||||
hls_fragment 10;
|
||||
hls_window 60;
|
||||
}
|
||||
http_remux {
|
||||
enabled on;
|
||||
mount [vhost]/[app]/[stream].flv;
|
||||
}
|
||||
dvr {
|
||||
enabled on;
|
||||
dvr_path ./recordings/[app]/[stream].flv;
|
||||
dvr_plan session;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
version: '3'
|
||||
services:
|
||||
srs:
|
||||
image: ossrs/srs:4
|
||||
ports:
|
||||
- "1935:1935" # RTMP
|
||||
- "8080:8080" # HTTP-FLV
|
||||
- "8088:8088" # HLS
|
||||
volumes:
|
||||
- ./conf/srs.conf:/usr/local/srs/conf/srs.conf
|
||||
- ./recordings:/usr/local/srs/recordings
|
|
@ -1,378 +0,0 @@
|
|||
\# 视频模块接口文档
|
||||
|
||||
\## 基础信息
|
||||
|
||||
- 基础路径: `/api/videos`
|
||||
- 请求头: 需要携带 `Authorization: Bearer {token}` (除了获取视频列表和视频详情)
|
||||
|
||||
\## 接口列表
|
||||
|
||||
\### 1. 上传视频
|
||||
|
||||
POST /api/videos/upload
|
||||
|
||||
// 请求参数 (multipart/form-data)
|
||||
|
||||
{
|
||||
|
||||
file: File, // 视频文件
|
||||
|
||||
title: string, // 视频标题
|
||||
|
||||
description: string, // 视频描述
|
||||
|
||||
tags?: string // 视频标签,可选,多个标签用逗号分隔
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number, // 200表示成功
|
||||
|
||||
message: string, // 响应消息
|
||||
|
||||
data: {
|
||||
|
||||
id: number, // 视频ID
|
||||
|
||||
title: string, // 视频标题
|
||||
|
||||
description: string, // 视频描述
|
||||
|
||||
url: string, // 视频URL
|
||||
|
||||
coverUrl: string, // 封面URL
|
||||
|
||||
duration: number, // 视频时长(秒)
|
||||
|
||||
size: number, // 文件大小(字节)
|
||||
|
||||
status: string, // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除
|
||||
|
||||
userId: number, // 上传用户ID
|
||||
|
||||
username: string, // 上传用户名
|
||||
|
||||
createdTime: string, // 创建时间
|
||||
|
||||
updatedTime: string, // 更新时间
|
||||
|
||||
viewCount: number, // 观看次数
|
||||
|
||||
likeCount: number, // 点赞次数
|
||||
|
||||
tags: string, // 标签
|
||||
|
||||
hasLiked: boolean // 当前用户是否已点赞
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\### 2. 获取视频列表
|
||||
|
||||
GET /api/videos?pageNum=1&pageSize=10&keyword=xxx
|
||||
|
||||
// 请求参数 (query)
|
||||
|
||||
{
|
||||
|
||||
pageNum?: number, // 页码,默认1
|
||||
|
||||
pageSize?: number, // 每页条数,默认10
|
||||
|
||||
keyword?: string // 搜索关键词,可选
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: {
|
||||
|
||||
records: Array<{ // 视频列表
|
||||
|
||||
id: number,
|
||||
|
||||
title: string,
|
||||
|
||||
description: string,
|
||||
|
||||
url: string,
|
||||
|
||||
coverUrl: string,
|
||||
|
||||
duration: number,
|
||||
|
||||
size: number,
|
||||
|
||||
status: string,
|
||||
|
||||
userId: number,
|
||||
|
||||
username: string,
|
||||
|
||||
createdTime: string,
|
||||
|
||||
updatedTime: string,
|
||||
|
||||
viewCount: number,
|
||||
|
||||
likeCount: number,
|
||||
|
||||
tags: string,
|
||||
|
||||
hasLiked: boolean
|
||||
|
||||
}>,
|
||||
|
||||
total: number, // 总记录数
|
||||
|
||||
size: number, // 每页条数
|
||||
|
||||
current: number, // 当前页码
|
||||
|
||||
pages: number // 总页数
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\### 3. 获取视频详情
|
||||
|
||||
GET /api/videos/{id}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: {
|
||||
|
||||
id: number,
|
||||
|
||||
title: string,
|
||||
|
||||
description: string,
|
||||
|
||||
url: string,
|
||||
|
||||
coverUrl: string,
|
||||
|
||||
duration: number,
|
||||
|
||||
size: number,
|
||||
|
||||
status: string,
|
||||
|
||||
userId: number,
|
||||
|
||||
username: string,
|
||||
|
||||
createdTime: string,
|
||||
|
||||
updatedTime: string,
|
||||
|
||||
viewCount: number,
|
||||
|
||||
likeCount: number,
|
||||
|
||||
tags: string,
|
||||
|
||||
hasLiked: boolean
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\### 4. 更新视频信息
|
||||
|
||||
PUT /api/videos/{id}
|
||||
|
||||
// 请求体
|
||||
|
||||
{
|
||||
|
||||
title: string,
|
||||
|
||||
description: string,
|
||||
|
||||
tags?: string
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: VideoDTO // 同上面的视频详情
|
||||
|
||||
}
|
||||
|
||||
\### 5. 删除视频
|
||||
|
||||
DELETE /api/videos/{id}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: null
|
||||
|
||||
}
|
||||
|
||||
\### 6. 视频点赞/取消点赞
|
||||
|
||||
POST /api/videos/{id}/like
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: null
|
||||
|
||||
}
|
||||
|
||||
\### 7. 增加观看次数
|
||||
|
||||
POST /api/videos/{id}/view
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: null
|
||||
|
||||
}
|
||||
|
||||
\### 8. 获取推荐视频
|
||||
|
||||
GET /api/videos/recommend?limit=10
|
||||
|
||||
// 请求参数 (query)
|
||||
|
||||
{
|
||||
|
||||
limit?: number // 返回数量,默认10
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: Array<VideoDTO> // 视频列表
|
||||
|
||||
}
|
||||
|
||||
\### 9. 获取相似视频
|
||||
|
||||
GET /api/videos/{id}/similar?limit=10
|
||||
|
||||
// 请求参数 (query)
|
||||
|
||||
{
|
||||
|
||||
limit?: number // 返回数量,默认10
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
|
||||
{
|
||||
|
||||
code: number,
|
||||
|
||||
message: string,
|
||||
|
||||
data: Array<VideoDTO> // 视频列表
|
||||
|
||||
}
|
||||
|
||||
\## 错误码说明
|
||||
|
||||
{
|
||||
|
||||
200: "操作成功",
|
||||
|
||||
400: "请求参数错误",
|
||||
|
||||
401: "未登录或token已过期",
|
||||
|
||||
403: "无权限执行此操作",
|
||||
|
||||
404: "资源不存在",
|
||||
|
||||
500: "服务器内部错误"
|
||||
|
||||
}
|
||||
|
||||
\## 数据结构
|
||||
|
||||
\### VideoDTO
|
||||
|
||||
{
|
||||
|
||||
id: number; // 视频ID
|
||||
|
||||
title: string; // 视频标题
|
||||
|
||||
description: string; // 视频描述
|
||||
|
||||
url: string; // 视频URL
|
||||
|
||||
coverUrl: string; // 封面URL
|
||||
|
||||
duration: number; // 视频时长(秒)
|
||||
|
||||
size: number; // 文件大小(字节)
|
||||
|
||||
status: string; // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除
|
||||
|
||||
userId: number; // 上传用户ID
|
||||
|
||||
username: string; // 上传用户名
|
||||
|
||||
createdTime: string; // 创建时间
|
||||
|
||||
updatedTime: string; // 更新时间
|
||||
|
||||
viewCount: number; // 观看次数
|
||||
|
||||
likeCount: number; // 点赞次数
|
||||
|
||||
tags: string; // 标签,多个用逗号分隔
|
||||
|
||||
hasLiked: boolean; // 当前用户是否已点赞
|
||||
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
# 视频模块接口文档
|
||||
|
||||
## 基础信息
|
||||
- 基础路径: `/api/videos`
|
||||
- 请求头: 需要携带 `Authorization: Bearer {token}` (除了获取视频列表和视频详情)
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 上传视频
|
||||
POST /api/videos/upload
|
||||
|
||||
// 请求参数 (multipart/form-data)
|
||||
{
|
||||
file: File, // 视频文件
|
||||
title: string, // 视频标题
|
||||
description: string, // 视频描述
|
||||
tags?: string // 视频标签,可选,多个标签用逗号分隔
|
||||
}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number, // 200表示成功
|
||||
message: string, // 响应消息
|
||||
data: {
|
||||
id: number, // 视频ID
|
||||
title: string, // 视频标题
|
||||
description: string, // 视频描述
|
||||
url: string, // 视频URL
|
||||
coverUrl: string, // 封面URL
|
||||
duration: number, // 视频时长(秒)
|
||||
size: number, // 文件大小(字节)
|
||||
status: string, // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除
|
||||
userId: number, // 上传用户ID
|
||||
username: string, // 上传用户名
|
||||
createdTime: string, // 创建时间
|
||||
updatedTime: string, // 更新时间
|
||||
viewCount: number, // 观看次数
|
||||
likeCount: number, // 点赞次数
|
||||
tags: string, // 标签
|
||||
hasLiked: boolean // 当前用户是否已点赞
|
||||
}
|
||||
}
|
||||
|
||||
### 2. 获取视频列表
|
||||
GET /api/videos?pageNum=1&pageSize=10&keyword=xxx
|
||||
|
||||
// 请求参数 (query)
|
||||
{
|
||||
pageNum?: number, // 页码,默认1
|
||||
pageSize?: number, // 每页条数,默认10
|
||||
keyword?: string // 搜索关键词,可选
|
||||
}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: {
|
||||
records: Array<{ // 视频列表
|
||||
id: number,
|
||||
title: string,
|
||||
description: string,
|
||||
url: string,
|
||||
coverUrl: string,
|
||||
duration: number,
|
||||
size: number,
|
||||
status: string,
|
||||
userId: number,
|
||||
username: string,
|
||||
createdTime: string,
|
||||
updatedTime: string,
|
||||
viewCount: number,
|
||||
likeCount: number,
|
||||
tags: string,
|
||||
hasLiked: boolean
|
||||
}>,
|
||||
total: number, // 总记录数
|
||||
size: number, // 每页条数
|
||||
current: number, // 当前页码
|
||||
pages: number // 总页数
|
||||
}
|
||||
}
|
||||
|
||||
### 3. 获取视频详情
|
||||
GET /api/videos/{id}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: {
|
||||
id: number,
|
||||
title: string,
|
||||
description: string,
|
||||
url: string,
|
||||
coverUrl: string,
|
||||
duration: number,
|
||||
size: number,
|
||||
status: string,
|
||||
userId: number,
|
||||
username: string,
|
||||
createdTime: string,
|
||||
updatedTime: string,
|
||||
viewCount: number,
|
||||
likeCount: number,
|
||||
tags: string,
|
||||
hasLiked: boolean
|
||||
}
|
||||
}
|
||||
|
||||
### 4. 更新视频信息
|
||||
PUT /api/videos/{id}
|
||||
|
||||
// 请求体
|
||||
{
|
||||
title: string,
|
||||
description: string,
|
||||
tags?: string
|
||||
}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: VideoDTO // 同上面的视频详情
|
||||
}
|
||||
|
||||
### 5. 删除视频
|
||||
DELETE /api/videos/{id}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: null
|
||||
}
|
||||
|
||||
### 6. 视频点赞/取消点赞
|
||||
POST /api/videos/{id}/like
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: null
|
||||
}
|
||||
|
||||
### 7. 增加观看次数
|
||||
POST /api/videos/{id}/view
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: null
|
||||
}
|
||||
|
||||
### 8. 获取推荐视频
|
||||
GET /api/videos/recommend?limit=10
|
||||
|
||||
// 请求参数 (query)
|
||||
{
|
||||
limit?: number // 返回数量,默认10
|
||||
}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: Array<VideoDTO> // 视频列表
|
||||
}
|
||||
|
||||
### 9. 获取相似视频
|
||||
GET /api/videos/{id}/similar?limit=10
|
||||
|
||||
// 请求参数 (query)
|
||||
{
|
||||
limit?: number // 返回数量,默认10
|
||||
}
|
||||
|
||||
// 响应
|
||||
{
|
||||
code: number,
|
||||
message: string,
|
||||
data: Array<VideoDTO> // 视频列表
|
||||
}
|
||||
|
||||
## 错误码说明
|
||||
{
|
||||
200: "操作成功",
|
||||
400: "请求参数错误",
|
||||
401: "未登录或token已过期",
|
||||
403: "无权限执行此操作",
|
||||
404: "资源不存在",
|
||||
500: "服务器内部错误"
|
||||
}
|
||||
|
||||
## 数据结构
|
||||
|
||||
### VideoDTO
|
||||
{
|
||||
id: number; // 视频ID
|
||||
title: string; // 视频标题
|
||||
description: string; // 视频描述
|
||||
url: string; // 视频URL
|
||||
coverUrl: string; // 封面URL
|
||||
duration: number; // 视频时长(秒)
|
||||
size: number; // 文件大小(字节)
|
||||
status: string; // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除
|
||||
userId: number; // 上传用户ID
|
||||
username: string; // 上传用户名
|
||||
createdTime: string; // 创建时间
|
||||
updatedTime: string; // 更新时间
|
||||
viewCount: number; // 观看次数
|
||||
likeCount: number; // 点赞次数
|
||||
tags: string; // 标签,多个用逗号分隔
|
||||
hasLiked: boolean; // 当前用户是否已点赞
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
Field client in cn.easyes.starter.register.MapperFactoryBean required a bean of type 'org.elasticsearch.client.RestHighLevelClient' that could not be found.
|
83
docs/rtc.md
83
docs/rtc.md
|
@ -1,83 +0,0 @@
|
|||
很久以前,人类以为只有神仙可以听到、看到千里之外的声音和景象,称之为千里眼和顺风耳,短短几百年里,人类的技术革命实现了质的飞跃。
|
||||
|
||||
1876 年,贝尔电话的发明,使人类可以听到千里之外声音的梦想终于成真。
|
||||
|
||||
此后,音视频技术不断发展。一方面,视频压缩技术从 H.261 到 H.264,再到现在的 H.265 及AV1,视频压缩率越来越高;音频压缩技术也从电话使用的 G.711、G.722 等窄带音频压缩技术,发展到现代的 AAC、OPUS 等宽带音频压缩技术。
|
||||
|
||||
另一方面,从中国 3G 网络正式商用开始,移动网络也发生了翻天覆地的变化。从 3G 到 4G ,再到马上要落地的 5G,移动网络的带宽和质量越来越高,为音视频数据传输打下了坚实的基础。
|
||||
|
||||
尤其是 2011 年 Google 推出 WebRTC 技术后,大大降低了音视频技术的门槛,可以在浏览器上快速开发出各种音视频应用。
|
||||
|
||||
如今,在疫情的三年里,视频会议,远程会诊,线上教学等需求将RTC技术推向高潮,成为影响社会发展不可或缺的技术之一。
|
||||
|
||||
2023年,从用3W法学习解构RTC开始,笔者也开启了RTC分享之路。
|
||||
|
||||
1.什么是RTC?
|
||||
RTC是Real-Time Communication的缩写,译为实时通信,目的是在设备端实时的转发音视频多媒体数据,让用户能实时的进行音频和视频的会话,即基于 IP 技术实现的实时交互的音视频通信技术。
|
||||
|
||||
具体涵义如下:
|
||||
|
||||
▪ 实时:音视频数据传输的延迟要达到“实时”的标准,也就是说延时要小于400ms,能够实现低延时和无卡顿,在正常通信过程中基本感受不到延迟的存在。
|
||||
|
||||
▪ 音视频:音视频数据传输,实时音视频通信通过服务端为中转节点,即时采集、渲染、处理、传输终端用户的图像、视频、音频数据进行,实现音视频流数据在终端节点间完成通信的过程。
|
||||
|
||||
▪ 实时音视频服务商一般以SDK的形式提供一整套解决方案。
|
||||
|
||||
2.为什么选择RTC技术?
|
||||
痛点:
|
||||
|
||||
基础音视频流程复杂且广泛:涵盖音视频收集、音视频压缩/解码、数据传输、终端适配、视频分发等系列环节,每个环节展开,都是复杂技术点。企业若想打造自主实时音视频方案,不仅要养一定规模软硬研发团队,还要花费一定时间沉淀,对于该企业来说,成本太高。
|
||||
|
||||
RTC技术优势:
|
||||
|
||||
高音质
|
||||
基于专有回声消除&降噪技术,可在嘈杂的环境下实现高音质通话,让对话里语音听得比较清晰,没有回声、啸叫的状况出现。
|
||||
|
||||
高画质
|
||||
视频支持超高清晰度画面,一路视频提供多种分辨率,大屏幕可订阅更高分辨率提升视频通话体验,分分钟感受面对面交流感。
|
||||
|
||||
低延迟
|
||||
全球通信节点支持,支持实时性更好的UDP协议,端到端延时低
|
||||
|
||||
抗弱网
|
||||
自动增益控制&弱网丢包补偿技术 ,在丢包下保持音视频通话流畅。
|
||||
|
||||
当然,基于一些特定行业的应用场景,比如多人数直播时的高并发,医疗行业的网闸透传,成熟RTC服务商都有着良好的技术沉淀,让越来越多的企业使用这项技术。
|
||||
|
||||
3.RTC有哪些使用场景?
|
||||
随着移动互联网的普及和智能终端设备的广泛应用,实时音视频正逐渐成为主流互动方式,已在在线教育、社交娱乐、互动电商等热门领域得到广泛应用,也赋能于更多创新场景,如金融、政企服务、loT、医疗等,帮助人们享受更便捷和更人性化的生活服务。
|
||||
|
||||
协同办公-视频会议
|
||||
丰富的会议场景,轻松实现远程办公系统,打通团队沟通渠道,帮助企业充分挖掘和整合隐形资源。
|
||||
|
||||
典型应用:Zoom,腾讯会议
|
||||
|
||||
社交沟通-聊天室
|
||||
支持 1v1 通话或群聊功能,频道内用户可自由发言,适用于语音通话、语音群聊、语音聊天室等场景。
|
||||
|
||||
典型应用:微信语音通话,YY语音
|
||||
|
||||
游戏&娱乐
|
||||
玩家可通过语音/视频聊天推进游戏进程,团战作战、协同作战,及时分享游戏信息,一起连麦开黑,拉近玩家距离。
|
||||
|
||||
典型应用:网易游戏,虎牙直播
|
||||
|
||||
电商直播
|
||||
通过IM+音视频拓展多样化电商直播玩法,增强购物体验,提升获客率,让购物更有趣,促进电商平台交易转化,实现全球购物零距离。
|
||||
|
||||
典型应用:淘宝直播
|
||||
|
||||
在线教育
|
||||
视频面对面教学,真实还原线下教学场景,支持1V1教学、1对多教学、双师课堂等多种互动教学模式。
|
||||
|
||||
典型应用:小鹅通
|
||||
|
||||
远程医疗
|
||||
基于IM及实时音视频RTC,通过实施互动技术,实现优质医疗资源和知识共享,满足远程会诊、手术示教多种场景需求。
|
||||
|
||||
典型应用:微医
|
||||
|
||||
视频双录
|
||||
根据金融监管要求,为客户提供多场景的双录服务,提供柜面双录、远程双录、移动双录、AI自助双录,帮助金融机构实现业务回溯。
|
||||
|
||||
典型应用:招商银行app
|
|
@ -0,0 +1,84 @@
|
|||
mysql57
|
||||
|
||||
docker run -p 3308:3306 --name mysql57 -v /d/05_docker/DockerData/mysql57/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
|
||||
|
||||
mysql83
|
||||
|
||||
docker run ^
|
||||
-p 3306:3306 ^
|
||||
--name mysql8.3 ^
|
||||
--privileged=true ^
|
||||
-v /d/05_docker/DockerData/mysql83/log:/var/log/mysql ^
|
||||
-v /d/05_docker/DockerData/mysql83/data:/var/lib/mysql ^
|
||||
-v /d/05_docker/DockerData/mysql83/conf/my.cnf:/etc/mysql/my.cnf ^
|
||||
-v /d/05_docker/DockerData/mysql83/sql:/var/sql ^
|
||||
-e MYSQL_ROOT_PASSWORD=root ^
|
||||
-d mysql:8.3.0
|
||||
|
||||
redis
|
||||
|
||||
docker run -d --name redis -p 6379:6379 redis --requirepass "123456"
|
||||
|
||||
minio
|
||||
|
||||
docker run -p 9000:9000 -p 9090:9090 ^
|
||||
--name minio ^
|
||||
-d ^
|
||||
-e "MINIO_ROOT_USER=admin" ^
|
||||
-e "MINIO_ROOT_PASSWORD=admin123456" ^
|
||||
-v /d/05_docker/DockerData/minio/data:/data ^
|
||||
-v /d/05_docker/DockerData/minio/config:/root/.minio ^
|
||||
minio/minio:latest server /data ^
|
||||
--console-address ":9090" -address ":9000"
|
||||
|
||||
es
|
||||
|
||||
docker network create es-net
|
||||
|
||||
docker run -d ^
|
||||
--name es ^
|
||||
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" ^
|
||||
-e "discovery.type=single-node" ^
|
||||
-v es-data:/usr/share/elasticsearch/data ^
|
||||
-v es-plugins:/usr/share/elasticsearch/plugins ^
|
||||
--privileged ^
|
||||
--network es-net ^
|
||||
-p 9200:9200 ^
|
||||
-p 9300:9300 ^
|
||||
elasticsearch:7.12.1
|
||||
|
||||
docker cp es:/usr/share/elasticsearch/data D:\05_docker\DockerData\elasticsearch\
|
||||
docker cp es:/usr/share/elasticsearch/plugins D:\05_docker\DockerData\elasticsearch\
|
||||
|
||||
docker run -d ^
|
||||
--name es ^
|
||||
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" ^
|
||||
-e "discovery.type=single-node" ^
|
||||
-v /d/05_docker/DockerData/elasticsearch/data:/usr/share/elasticsearch/data ^
|
||||
-v /d/05_docker/DockerData/elasticsearch/plugins:/usr/share/elasticsearch/plugins ^
|
||||
--privileged ^
|
||||
--network es-net ^
|
||||
-p 9200:9200 ^
|
||||
-p 9300:9300 ^
|
||||
elasticsearch:7.12.1
|
||||
|
||||
|
||||
Kibana
|
||||
|
||||
docker run -d ^
|
||||
--name kibana ^
|
||||
-e ELASTICSEARCH_HOSTS=http://es:9200 ^
|
||||
--network es-net ^
|
||||
-p 5601:5601 ^
|
||||
kibana:7.12.1
|
||||
|
||||
|
||||
logstash
|
||||
|
||||
docker run -it -d --name logstash ^
|
||||
--network es-net ^
|
||||
-p 5044:5044 ^
|
||||
-v /d/05_docker/DockerData/logstash/pipeline/logstash.conf:/usr/share/logstash/pipeline/logstash.conf ^
|
||||
-v /d/05_docker/DockerData/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml ^
|
||||
logstash:7.12.1
|
||||
|
95
pom.xml
95
pom.xml
|
@ -271,6 +271,101 @@
|
|||
<version>7.14.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.agentsflex</groupId>-->
|
||||
<!-- <artifactId>agents-flex-bom</artifactId>-->
|
||||
<!-- <version>1.0.0-rc.3</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- springboot -->
|
||||
<dependency>
|
||||
<groupId>com.mongoplus</groupId>
|
||||
<artifactId>mongo-plus-boot-starter</artifactId>
|
||||
<version>2.1.6.1</version>
|
||||
</dependency>
|
||||
<!-- solon -->
|
||||
<dependency>
|
||||
<groupId>com.mongoplus</groupId>
|
||||
<artifactId>mongo-plus-solon-plugin</artifactId>
|
||||
<version>2.1.6.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
<version>3.0.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Janino 依赖,用于 logback 条件判断 -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.janino</groupId>
|
||||
<artifactId>janino</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.1.8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>6.6</version> <!-- 请使用最新版本 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--xxljob 许雪里 job-->
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
<version>2.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- docker java -->
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java</artifactId>
|
||||
<version>3.2.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- docker java httpclient -->
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<version>3.2.13</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<version>5.2</version> <!-- 或者适合你项目的版本 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<artifactId>httpcore5</artifactId>
|
||||
<version>5.2</version> <!-- 或者适合你项目的版本 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
package com.guwan.backend;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.guwan.backend.mapper")
|
||||
@EnableFeignClients
|
||||
public class BackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BackendApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("大爱仙尊: http://localhost:8084/daxz.html?id=1");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.guwan.backend.aspect;
|
|||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.guwan.backend.annotation.OperationLog;
|
||||
import com.guwan.backend.entity.SysLog;
|
||||
import com.guwan.backend.pojo.entity.SysLog;
|
||||
import com.guwan.backend.mapper.SysLogMapper;
|
||||
import com.guwan.backend.security.CustomUserDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
@ -49,23 +49,15 @@ public class OperationLogAspect {
|
|||
SysLog sysLog = new SysLog();
|
||||
|
||||
try {
|
||||
// 执行方法
|
||||
Object result = point.proceed();
|
||||
// 设置状态为成功
|
||||
sysLog.setStatus(1);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
// 记录异常信息
|
||||
sysLog.setStatus(0);
|
||||
sysLog.setErrorMsg(e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
// 记录日志
|
||||
saveLog(point, operationLog, beginTime, sysLog);
|
||||
} catch (Exception e) {
|
||||
log.error("记录操作日志失败", e);
|
||||
}
|
||||
saveLog(point, operationLog, beginTime, sysLog);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
package com.guwan.backend.client;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class Go2RTCClient {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Value("${go2rtc.api.url}")
|
||||
private String apiUrl;
|
||||
|
||||
/**
|
||||
* 创建流
|
||||
*/
|
||||
public void createStream(String streamId, String sourceUrl) {
|
||||
String url = apiUrl + "/api/streams/" + streamId;
|
||||
StreamConfig config = new StreamConfig(sourceUrl);
|
||||
|
||||
try {
|
||||
restTemplate.postForEntity(url, config, String.class);
|
||||
} catch (Exception e) {
|
||||
log.error("创建流失败: {}", e.getMessage());
|
||||
throw new RuntimeException("创建流失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除流
|
||||
*/
|
||||
public void deleteStream(String streamId) {
|
||||
String url = apiUrl + "/api/streams/" + streamId;
|
||||
|
||||
try {
|
||||
restTemplate.delete(url);
|
||||
} catch (Exception e) {
|
||||
log.error("删除流失败: {}", e.getMessage());
|
||||
throw new RuntimeException("删除流失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取WebRTC Offer
|
||||
*/
|
||||
public String getOffer(String streamId, String sdp) {
|
||||
String url = apiUrl + "/api/stream/" + streamId + "/webrtc";
|
||||
WebRTCRequest request = new WebRTCRequest(sdp);
|
||||
|
||||
try {
|
||||
ResponseEntity<WebRTCResponse> response =
|
||||
restTemplate.postForEntity(url, request, WebRTCResponse.class);
|
||||
return response.getBody().getSdp();
|
||||
} catch (Exception e) {
|
||||
log.error("获取WebRTC Offer失败: {}", e.getMessage());
|
||||
throw new RuntimeException("获取WebRTC Offer失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class StreamConfig {
|
||||
private String input;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class WebRTCRequest {
|
||||
private String sdp;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WebRTCResponse {
|
||||
private String sdp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package com.guwan.backend.client;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
@FeignClient(name = "ttsService", url = "http://127.0.0.1:8534")
|
||||
public interface SimpleTTSClient {
|
||||
|
||||
|
||||
|
||||
|
||||
@PostMapping(value = "/v1/audio/speech")
|
||||
byte[] saveAudio(String jsonInputString);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//public class SimpleAudioSaver {
|
||||
//
|
||||
// public static void main(String[] args) {
|
||||
// String urlString = "http://localhost:8534/v1/audio/speech"; // API URL
|
||||
// String jsonInputString = "{\"input\": \"想听个啥123\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"style\": \"\", \"rate\": 0, \"pitch\": 0}";
|
||||
//
|
||||
// saveAudio(urlString, jsonInputString);
|
||||
// }
|
||||
//
|
||||
// public static void saveAudio(String urlString, String jsonInputString) {
|
||||
// try {
|
||||
// // 创建 URL 对象
|
||||
// URL url = new URL(urlString);
|
||||
// // 打开连接
|
||||
// HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
// // 设置请求方式为 POST
|
||||
// connection.setRequestMethod("POST");
|
||||
// connection.setDoOutput(true);
|
||||
// connection.setRequestProperty("Content-Type", "application/json");
|
||||
// connection.setRequestProperty("Accept", "audio/mpeg");
|
||||
//
|
||||
// // 发送请求体
|
||||
// connection.getOutputStream().write(jsonInputString.getBytes("UTF-8"));
|
||||
//
|
||||
// // 处理响应
|
||||
// if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
// // 获取输入流
|
||||
// InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||
// // 保存文件
|
||||
// try (FileOutputStream outputStream = new FileOutputStream("output.mp3")) {
|
||||
// byte[] buffer = new byte[1024];
|
||||
// int bytesRead;
|
||||
// while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
// outputStream.write(buffer, 0, bytesRead);
|
||||
// }
|
||||
// }
|
||||
// System.out.println("Audio saved as output.mp3");
|
||||
// } else {
|
||||
// System.err.println("Failed to get audio. Response code: " + connection.getResponseCode());
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -1,83 +0,0 @@
|
|||
//package com.guwan.backend.client;
|
||||
//
|
||||
//import lombok.Data;
|
||||
//import lombok.RequiredArgsConstructor;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.springframework.beans.factory.annotation.Value;
|
||||
//import org.springframework.http.ResponseEntity;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//import org.springframework.web.client.RestTemplate;
|
||||
//
|
||||
//@Slf4j
|
||||
//@Component
|
||||
//@RequiredArgsConstructor
|
||||
//public class SrsClient {
|
||||
//
|
||||
// private final RestTemplate restTemplate;
|
||||
//
|
||||
// @Value("${srs.server.url}")
|
||||
// private String srsServerUrl;
|
||||
//
|
||||
// /**
|
||||
// * 开始录制
|
||||
// */
|
||||
// public void startRecord(String streamKey, RecordConfig config) {
|
||||
// String url = String.format("%s/api/v1/streams/%s/recording/start", srsServerUrl, streamKey);
|
||||
// try {
|
||||
// ResponseEntity<String> response = restTemplate.postForEntity(url, config, String.class);
|
||||
// if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
// throw new RuntimeException("开始录制失败: " + response.getBody());
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("开始录制失败", e);
|
||||
// throw new RuntimeException("开始录制失败", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 停止录制
|
||||
// */
|
||||
// public void stopRecord(String streamKey) {
|
||||
// String url = String.format("%s/api/v1/streams/%s/recording/stop", srsServerUrl, streamKey);
|
||||
// try {
|
||||
// ResponseEntity<String> response = restTemplate.postForEntity(url, null, String.class);
|
||||
// if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
// throw new RuntimeException("停止录制失败: " + response.getBody());
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("停止录制失败", e);
|
||||
// throw new RuntimeException("停止录制失败", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取流信息
|
||||
// */
|
||||
// public StreamInfo getStreamInfo(String streamKey) {
|
||||
// String url = String.format("%s/api/v1/streams/%s", srsServerUrl, streamKey);
|
||||
// try {
|
||||
// ResponseEntity<StreamInfo> response = restTemplate.getForEntity(url, StreamInfo.class);
|
||||
// return response.getBody();
|
||||
// } catch (Exception e) {
|
||||
// log.error("获取流信息失败", e);
|
||||
// throw new RuntimeException("获取流信息失败", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Data
|
||||
// public static class RecordConfig {
|
||||
// private String format = "flv"; // 录制格式
|
||||
// private String filePath; // 文件路径
|
||||
// }
|
||||
//
|
||||
// @Data
|
||||
// public static class StreamInfo {
|
||||
// private String streamId; // 流ID
|
||||
// private String clientId; // 客户端ID
|
||||
// private String ip; // 客户端IP
|
||||
// private Long startTime; // 开始时间
|
||||
// private String status; // 状态
|
||||
// private Long bytesIn; // 输入字节数
|
||||
// private Long bytesOut; // 输出字节数
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,19 @@
|
|||
package com.guwan.backend.client;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 语音转文本
|
||||
*/
|
||||
@FeignClient(name = "voiceService", url = "http://127.0.0.1:8532")
|
||||
public interface VoiceServiceClient {
|
||||
|
||||
@PostMapping(value = "/v1/audio/transcriptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
JSONObject voiceToText(@RequestPart("file") MultipartFile file,
|
||||
@RequestPart("model") String model);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.guwan.backend.common;
|
||||
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
@java.io.Serial private static final long serialVersionUID = -2119302295305964305L;
|
||||
|
||||
public BusinessException() {}
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BusinessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BusinessException(
|
||||
String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.guwan.backend.controller.monitor.CacheMonitor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static com.guwan.backend.constant.CacheConstants.CACHE_LIST;
|
||||
|
||||
@Slf4j
|
||||
@EnableCaching
|
||||
@Configuration
|
||||
public class CaffeineConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public CacheManager caffeineCacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||
.recordStats()
|
||||
.expireAfterAccess(60, TimeUnit.MINUTES)
|
||||
.initialCapacity(100)
|
||||
.maximumSize(1000));
|
||||
|
||||
cacheManager.setCacheNames(CACHE_LIST);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheMonitor cacheMonitor(CacheManager cacheManager) {
|
||||
return new CacheMonitor(cacheManager);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ public class DatabaseInitConfig implements ApplicationRunner {
|
|||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||
populator.addScript(new ClassPathResource("db/schema.sql"));
|
||||
populator.addScript(new ClassPathResource("db/data.sql"));
|
||||
//populator.addScript(new ClassPathResource("db/daaixianzun.sql"));
|
||||
populator.setContinueOnError(true);
|
||||
populator.execute(dataSource);
|
||||
log.info("数据库初始化完成");
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.core.DockerClientBuilder;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Configuration
|
||||
public class DockerConfig {
|
||||
|
||||
@Bean
|
||||
public DockerClient dockerClient() {
|
||||
// 配置 Docker 主机
|
||||
DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withDockerHost("tcp://localhost:2375") // 设置 Docker 主机地址
|
||||
.build();
|
||||
|
||||
// 创建 ApacheHttpClient5 实现
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient
|
||||
.Builder()
|
||||
.dockerHost(config.getDockerHost())
|
||||
.maxConnections(100)
|
||||
.connectionTimeout(Duration.ofSeconds(30))
|
||||
.responseTimeout(Duration.ofSeconds(45))
|
||||
.build();
|
||||
|
||||
// 创建 Docker 客户端并返回
|
||||
return DockerClientBuilder.getInstance(config)
|
||||
.withDockerHttpClient(httpClient)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -8,10 +8,9 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EsMapperScan("com.guwan.backend.es.mapper")
|
||||
@EsMapperScan("com.guwan.backend.elasticsearch.mapper")
|
||||
@EnableConfigurationProperties(EasyEsConfigProperties.class)
|
||||
public class EasyEsConfig {
|
||||
|
||||
@Bean
|
||||
public IndexStrategyFactory indexStrategyFactory() {
|
||||
return new IndexStrategyFactory();
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||
import org.springframework.kafka.core.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class KafkaConfig {
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
|
||||
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
|
||||
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
|
||||
return new DefaultKafkaProducerFactory<>(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, String> consumerFactory() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
|
||||
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
|
||||
config.put(ConsumerConfig.GROUP_ID_CONFIG, "book-recommendation-group");
|
||||
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
|
||||
return new DefaultKafkaConsumerFactory<>(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaTemplate<String, String> kafkaTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
|
||||
ConcurrentKafkaListenerContainerFactory<String, String> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
return factory;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @author roc
|
||||
*/
|
||||
@Configuration
|
||||
public class LocalDateTimeConfiguration {
|
||||
|
||||
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
|
||||
private String pattern;
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
|
||||
return builder -> {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
|
||||
//返回时间数据序列化
|
||||
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
//接收时间数据反序列化
|
||||
builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import com.guwan.backend.websocket.ChatWebSocketHandler;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new ChatWebSocketHandler(), "/ws/chat")
|
||||
.setAllowedOrigins("*"); // 允许所有来源
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.guwan.backend.config;
|
||||
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class XxlJobConfig {
|
||||
@Value("${xxl.job.admin.addresses}")
|
||||
private String adminAddresses;
|
||||
@Value("${xxl.job.accessToken}")
|
||||
private String accessToken;
|
||||
@Value("${xxl.job.executor.appname}")
|
||||
private String appname;
|
||||
@Value("${xxl.job.executor.address}")
|
||||
private String address;
|
||||
@Value("${xxl.job.executor.ip}")
|
||||
private String ip;
|
||||
@Value("${xxl.job.executor.port}")
|
||||
private int port;
|
||||
@Value("${xxl.job.executor.logpath}")
|
||||
private String logPath;
|
||||
@Value("${xxl.job.executor.logretentiondays}")
|
||||
private int logRetentionDays;
|
||||
|
||||
@Bean
|
||||
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
|
||||
xxlJobSpringExecutor.setAppname(appname);
|
||||
xxlJobSpringExecutor.setAddress(address);
|
||||
xxlJobSpringExecutor.setIp(ip);
|
||||
xxlJobSpringExecutor.setPort(port);
|
||||
xxlJobSpringExecutor.setAccessToken(accessToken);
|
||||
xxlJobSpringExecutor.setLogPath(logPath);
|
||||
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
|
||||
return xxlJobSpringExecutor;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.guwan.backend.constant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 安全相关常量配置
|
||||
*/
|
||||
public class CacheConstants {
|
||||
|
||||
/**
|
||||
* API接口白名单
|
||||
* 这些路径可以直接访问,不需要认证
|
||||
*/
|
||||
public static final List<String> CACHE_LIST = List.of(
|
||||
"userCache"
|
||||
);
|
||||
|
||||
/**
|
||||
* 静态资源白名单
|
||||
* 这些路径用于访问静态资源,不需要认证
|
||||
*/
|
||||
|
||||
}
|
|
@ -12,7 +12,7 @@ public class SecurityConstants {
|
|||
* 这些路径可以直接访问,不需要认证
|
||||
*/
|
||||
public static final List<String> WHITE_LIST = List.of(
|
||||
"/common/**", //公共接口
|
||||
"/api/common/**", //公共接口
|
||||
"/demo/**", // 测试接口
|
||||
"/api/products",
|
||||
"/api/user/register", // 用户注册
|
||||
|
@ -20,6 +20,15 @@ public class SecurityConstants {
|
|||
"/api/user/getEmailCode", // 获取邮箱验证码
|
||||
"/api/user/getPhoneCode", // 获取手机验证码
|
||||
"/chat.html",
|
||||
|
||||
"/daxz.html/**",
|
||||
|
||||
"/polling-chat.html",
|
||||
|
||||
"/ws/chat/**",
|
||||
|
||||
"/api/polling-chat/**",
|
||||
|
||||
"/v3/api-docs/**", // Swagger API文档
|
||||
"/swagger-ui/**", // Swagger UI
|
||||
"/swagger-ui.html", // Swagger UI HTML
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.guwan.backend.common.BusinessException;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.entity.BookCategory;
|
||||
import com.guwan.backend.service.BookCategoryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* (BookCategory)表控制层
|
||||
*
|
||||
* @author makejava
|
||||
* @since 2024-12-20 17:44:07
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("bookCategory")
|
||||
public class BookCategoryController {
|
||||
/**
|
||||
* 服务对象
|
||||
*/
|
||||
@Autowired
|
||||
private BookCategoryService bookCategoryService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param bookCategory 筛选条件
|
||||
* @param pageRequest 分页对象
|
||||
* @return 查询结果
|
||||
*/
|
||||
@GetMapping
|
||||
public Result<Page<BookCategory>> queryByPage(BookCategory bookCategory, PageRequest pageRequest) {
|
||||
// return Result.success(this.bookCategoryService.queryByPage(bookCategory, pageRequest));
|
||||
|
||||
return Result.success();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过主键查询单条数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 单条数据
|
||||
*/
|
||||
@GetMapping("{id}")
|
||||
public Result<BookCategory> queryById(@PathVariable("id") Integer id) {
|
||||
return Result.success(bookCategoryService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
*
|
||||
* @param bookCategory 实体
|
||||
* @return 新增结果
|
||||
*/
|
||||
@PostMapping
|
||||
public Result<BookCategory> add(BookCategory bookCategory) {
|
||||
|
||||
if (bookCategoryService.lambdaQuery()
|
||||
.eq(BookCategory::getCategoryName, bookCategory.getCategoryName()).one() != null){
|
||||
throw new BusinessException("该图书种类已经存在");
|
||||
}
|
||||
|
||||
if (bookCategoryService.save(bookCategory)) {
|
||||
return Result.success();
|
||||
}else {
|
||||
return Result.error("保存失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑数据
|
||||
*
|
||||
* @param bookCategory 实体
|
||||
* @return 编辑结果
|
||||
*/
|
||||
@PutMapping
|
||||
public Result<BookCategory> edit(BookCategory bookCategory) {
|
||||
//return Result.success(this.bookCategoryService.update(bookCategory));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 删除是否成功
|
||||
*/
|
||||
@DeleteMapping
|
||||
public Result<Boolean> deleteById(Integer id) {
|
||||
bookCategoryService.removeById(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.entity.Book;
|
||||
import com.guwan.backend.service.BookService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "图书管理", description = "图书相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/books")
|
||||
@RequiredArgsConstructor
|
||||
public class BookController {
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
@Operation(summary = "添加图书")
|
||||
@PostMapping("/add")
|
||||
public Result<Book> addBook(@RequestBody Book book) {
|
||||
try {
|
||||
return Result.success(bookService.addBook(book));
|
||||
} catch (Exception e) {
|
||||
log.error("添加图书失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询图书列表")
|
||||
@GetMapping
|
||||
public Result<IPage<Book>> getBookList(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
try {
|
||||
return Result.success(bookService.getBookList(pageNum, pageSize, keyword));
|
||||
} catch (Exception e) {
|
||||
log.error("查询图书列表失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "更新图书信息")
|
||||
@PutMapping("/{id}")
|
||||
public Result<Book> updateBook(@PathVariable Long id, @RequestBody Book book) {
|
||||
try {
|
||||
book.setId(id);
|
||||
return Result.success(bookService.updateBook(book));
|
||||
} catch (Exception e) {
|
||||
log.error("更新图书失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "删除图书")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteBook(@PathVariable Long id) {
|
||||
try {
|
||||
bookService.deleteBook(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("删除图书失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取图书详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<Book> getBook(@PathVariable Long id) {
|
||||
try {
|
||||
return Result.success(bookService.getBookById(id));
|
||||
} catch (Exception e) {
|
||||
log.error("获取图书详情失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ISBN获取图书")
|
||||
@GetMapping("/isbn/{isbn}")
|
||||
public Result<Book> getBookByIsbn(@PathVariable String isbn) {
|
||||
try {
|
||||
return Result.success(bookService.getBookByIsbn(isbn));
|
||||
} catch (Exception e) {
|
||||
log.error("根据ISBN获取图书失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "根据分类获取图书")
|
||||
@GetMapping("/category/{category}")
|
||||
public Result<IPage<Book>> getBooksByCategory(
|
||||
@PathVariable String category,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
return Result.success(bookService.getBooksByCategory(category, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("根据分类获取图书失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,368 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.guwan.backend.annotation.OperationLog;
|
||||
import com.guwan.backend.client.SimpleTTSClient;
|
||||
import com.guwan.backend.client.VoiceServiceClient;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.entity.BookContent;
|
||||
import com.guwan.backend.mongodb.EveryReadDetailOfMongodb;
|
||||
import com.guwan.backend.mongodb.EveryReadDetailOfMongodbService;
|
||||
import com.guwan.backend.mongodb.User;
|
||||
import com.guwan.backend.mongodb.MongodbUserService;
|
||||
import com.guwan.backend.service.BookContentService;
|
||||
import com.guwan.backend.util.MinioUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/common")
|
||||
@RequestMapping("/api/common")
|
||||
@RequiredArgsConstructor
|
||||
public class CommonController {
|
||||
|
||||
|
||||
private final MinioUtil minioUtil;
|
||||
|
||||
private final VoiceServiceClient voiceServiceClient;
|
||||
private final SimpleTTSClient simpleTTSClient;
|
||||
|
||||
|
||||
|
||||
|
||||
private final BookContentService bookContentService;
|
||||
|
||||
private final MongodbUserService mongodbUserService;
|
||||
|
||||
private final EveryReadDetailOfMongodbService everyReadDetailOfMongodbService;
|
||||
|
||||
@PostMapping("/uploadFile")
|
||||
public Result<String> uploadFile(String bucketName, MultipartFile file){
|
||||
return Result.success(minioUtil.getUrl(minioUtil.getFileUrl
|
||||
(bucketName, minioUtil.uploadFile(bucketName, file))));
|
||||
}
|
||||
|
||||
@PostMapping("/addBookComment")
|
||||
public Result<String> addBookComment(String url) {
|
||||
log.debug(url);
|
||||
// "http://localhost:9000/txt/8357cf6b-9637-4354-9ee6-2717141f665a.txt";
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
// 创建一个请求对象
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.build();
|
||||
|
||||
// 发起同步请求
|
||||
try {
|
||||
String content = getTextUsingOkHttp(client, request);
|
||||
ArrayList<BookContent> bookContents = processContent(content);
|
||||
bookContentService.saveBatch(bookContents);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return Result.success("ok");
|
||||
}
|
||||
|
||||
// 通过 OkHttpClient 发起同步请求获取文件内容
|
||||
public static String getTextUsingOkHttp(OkHttpClient client, Request request) throws IOException {
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
return response.body().string(); // 返回文件内容
|
||||
} else {
|
||||
throw new IOException("Unexpected code " + response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件内容,提取卷和节
|
||||
public static ArrayList<BookContent> processContent(String content) {
|
||||
// 正则表达式,提取卷和节
|
||||
String volumePattern = "第([一二三四五六七八九十]+)卷"; // 提取卷
|
||||
String sectionPattern = "第([一二三四五六七八九十零百]+)节:(.*)"; // 提取节及其节名
|
||||
|
||||
// 提取卷
|
||||
Pattern volumeRegex = Pattern.compile(volumePattern);
|
||||
Matcher volumeMatcher = volumeRegex.matcher(content);
|
||||
|
||||
// 提取节
|
||||
Pattern sectionRegex = Pattern.compile(sectionPattern);
|
||||
Matcher sectionMatcher = sectionRegex.matcher(content);
|
||||
|
||||
// 列表来存储所有卷和节的内容
|
||||
List<String> volumes = new ArrayList<>(); // 存储所有卷的标题
|
||||
List<String> sections = new ArrayList<>(); // 存储所有节的标题
|
||||
List<String> sectionContents = new ArrayList<>(); // 存储每节的正文内容
|
||||
|
||||
// 收集卷的信息
|
||||
while (volumeMatcher.find()) {
|
||||
String volume = "第" + volumeMatcher.group(1) + "卷";
|
||||
volumes.add(volume);
|
||||
}
|
||||
|
||||
// 收集节的信息
|
||||
while (sectionMatcher.find()) {
|
||||
String sectionTitle = "第" + sectionMatcher.group(1) + "节:" + sectionMatcher.group(2).trim(); // 这里去掉节名前后空格
|
||||
sections.add(sectionTitle);
|
||||
|
||||
// 获取节的正文内容
|
||||
int start = sectionMatcher.end(); // 获取节标题之后的位置
|
||||
int end = content.length(); // 默认到文件末尾
|
||||
|
||||
// 查找下一个节的位置(即本节内容的结束位置)
|
||||
Matcher nextSectionMatcher = sectionRegex.matcher(content);
|
||||
if (nextSectionMatcher.find(start)) {
|
||||
end = nextSectionMatcher.start();
|
||||
}
|
||||
|
||||
// 获取当前节的正文内容
|
||||
String sectionContent = content.substring(start, end).trim();
|
||||
sectionContents.add(sectionContent);
|
||||
}
|
||||
|
||||
// 标记是否是第一次匹配到“第一节”
|
||||
boolean isFirstSection = true;
|
||||
|
||||
ArrayList<BookContent> bookContents = new ArrayList<>();
|
||||
|
||||
int sectionId = 1;
|
||||
|
||||
// 输出卷和节信息
|
||||
for (int i = 0; i < volumes.size(); i++) {
|
||||
// 输出卷的标题
|
||||
|
||||
// 输出该卷的每一节标题和正文内容
|
||||
for (int j = 0; j < sections.size(); j++) {
|
||||
|
||||
// System.out.print(volumes.get(i));
|
||||
String section = sections.get(j);
|
||||
String sectionContent = sectionContents.get(j);
|
||||
|
||||
// 输出节标题
|
||||
// System.out.println(" " + section);
|
||||
|
||||
// 输出节的正文内容
|
||||
// System.out.println(" 正文: " + sectionContent);
|
||||
|
||||
// 如果是“第一节”,并且不是第一次出现,递增卷的索引
|
||||
if (section.contains("第一节") && !isFirstSection) {
|
||||
i++; // 不是第一次才递增
|
||||
}
|
||||
|
||||
// 第一次匹配到“第一节”后,标记为false
|
||||
isFirstSection = false;
|
||||
|
||||
|
||||
BookContent bookContent = new BookContent();
|
||||
bookContent.setBookName("大爱仙尊");
|
||||
bookContent.setVolume(volumes.get(i));
|
||||
bookContent.setSection(section);
|
||||
bookContent.setSectionContent(sectionContent);
|
||||
bookContent.setSectionId(sectionId++);
|
||||
|
||||
System.out.println("bookContent = " + bookContent);
|
||||
|
||||
bookContents.add(bookContent);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return bookContents;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/getBookComment")
|
||||
public Result<String> getBookComment(Long id) {
|
||||
BookContent bookContent = bookContentService.getById(id);
|
||||
return Result.success(bookContent.getSectionContent());
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/getBookContent")
|
||||
public Result<String> getBookContent(String bookName, Long id) {
|
||||
BookContent bookContent = bookContentService.getBookContent(bookName, id);
|
||||
return Result.success(bookContent.getSectionContent());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/getBookCommentByPath")
|
||||
public ResponseEntity<Map<String, Object>> getBookCommentByPath(@RequestParam("id") Long id) {
|
||||
// 从数据库中获取评论内容
|
||||
//String comments = bookContentService.getById(id).getSectionContent();
|
||||
|
||||
BookContent byId = bookContentService.lambdaQuery()
|
||||
.eq(BookContent::getBookName, "大爱仙尊")
|
||||
.eq(BookContent::getSectionId, id).one();
|
||||
|
||||
// 构造返回数据
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("data", byId);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/testMongodb")
|
||||
public Result testMongodb(){
|
||||
|
||||
|
||||
User user = new User();
|
||||
user.setId("1");
|
||||
user.setName("1");
|
||||
user.setAge(1L);
|
||||
user.setEmail("1");
|
||||
|
||||
mongodbUserService.save(user);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/everyRead")
|
||||
public Result everyRead(@RequestBody EveryReadDetailOfMongodb everyReadDetailOfMongodb){
|
||||
|
||||
|
||||
everyReadDetailOfMongodbService.save(everyReadDetailOfMongodb);
|
||||
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@GetMapping("/getUserOneTotalTime")
|
||||
@OperationLog(description = "统计用户某本书的读书时间")
|
||||
public Result getUserOneTotalTime(@RequestParam Long userId){
|
||||
|
||||
|
||||
//return Result.success();
|
||||
|
||||
// everyReadDetailOfMongodbService.getUserOneTotalTime(userId);
|
||||
|
||||
return Result.success(everyReadDetailOfMongodbService.getUserOneTotalTime(userId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* model 默认值 /model/faster-whisper-small/
|
||||
*/
|
||||
@PostMapping("/convertVoiceToText")
|
||||
public Result convertVoiceToText(MultipartFile file, String model) {
|
||||
|
||||
if (model == null){
|
||||
model = "/model/faster-whisper-small/";
|
||||
}
|
||||
|
||||
return Result.success(voiceServiceClient.voiceToText(file, model));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/simpleTTS")
|
||||
public Result simpleTTS(String input, String voiceModel) {
|
||||
|
||||
|
||||
if (voiceModel == null) {
|
||||
voiceModel = "zh-CN-XiaoxiaoNeural";
|
||||
}
|
||||
|
||||
// 使用 JSONObject 构造JSON
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.putOpt("input", input);
|
||||
jsonObject.putOpt("voice", voiceModel);
|
||||
|
||||
String jsonInputString = jsonObject.toString();
|
||||
System.out.println(jsonInputString);
|
||||
|
||||
|
||||
// jsonInputString = "{\"input\": \"想听个啥123\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"style\": \"\", \"rate\": 0, \"pitch\": 0}";
|
||||
|
||||
|
||||
String url = minioUtil.getUrl(minioUtil.getFileUrl("temp",
|
||||
minioUtil.uploadByteArray(
|
||||
simpleTTSClient.saveAudio(jsonInputString), "mp3")));
|
||||
|
||||
|
||||
return Result.success(url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@PostMapping("/testPart")
|
||||
public void testPart(@RequestPart(value = "file", required = false) MultipartFile file,
|
||||
@RequestPart(value = "str1", required = false) String str1,
|
||||
@RequestPart(value = "f1", required = false) String f1) {
|
||||
|
||||
String s = null;
|
||||
if (file != null) {
|
||||
s = minioUtil.uploadFile("temp", file);
|
||||
}
|
||||
|
||||
System.out.println("s = " + s);
|
||||
|
||||
System.out.println("str1 = " + str1);
|
||||
|
||||
System.out.println("f1 = " + f1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/testPartOfFloat")
|
||||
public void testPartOfFloat(@RequestPart(value = "file", required = false) MultipartFile file,
|
||||
@RequestPart(value = "str1", required = false) String str1,
|
||||
@RequestPart(value = "f1", required = false) Float f1) {
|
||||
|
||||
String s = null;
|
||||
if (file != null) {
|
||||
s = minioUtil.uploadFile("temp", file);
|
||||
}
|
||||
|
||||
System.out.println("s = " + s);
|
||||
|
||||
System.out.println("str1 = " + str1);
|
||||
|
||||
System.out.println("f1 = " + f1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@PostMapping("/testParam")
|
||||
public void testParm(@RequestPart(value = "file", required = false) MultipartFile file,
|
||||
@RequestParam(value = "str1", required = true) String str1,
|
||||
@RequestParam(value = "f1", required = false) Float f1) {
|
||||
|
||||
String s = null;
|
||||
if (file != null) {
|
||||
s = minioUtil.uploadFile("temp", file);
|
||||
}
|
||||
|
||||
|
||||
System.out.println("s = " + s);
|
||||
|
||||
System.out.println("str1 = " + str1);
|
||||
|
||||
System.out.println("f1 = " + f1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +1,10 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import cn.easyes.core.conditions.LambdaEsQueryWrapper;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.arcsoft.face.ActiveDeviceInfo;
|
||||
import com.arcsoft.face.ActiveFileInfo;
|
||||
import com.arcsoft.face.FaceEngine;
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
import com.arcsoft.face.enums.ExtractType;
|
||||
import com.arcsoft.face.toolkit.ImageFactory;
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.es.document.ProductDocument;
|
||||
import com.guwan.backend.es.mapper.ProductEsMapper;
|
||||
import com.guwan.backend.face.dto.FaceRecognitionResDTO;
|
||||
import com.guwan.backend.face.service.FaceEngineService;
|
||||
import com.guwan.backend.elasticsearch.document.ProductDocument;
|
||||
import com.guwan.backend.elasticsearch.mapper.ProductEsMapper;
|
||||
import com.guwan.backend.util.MinioUtil;
|
||||
import io.minio.GetObjectArgs;
|
||||
import io.minio.MinioClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -28,11 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
@ -41,8 +26,6 @@ import java.util.regex.Pattern;
|
|||
@Validated
|
||||
public class DemoController {
|
||||
|
||||
@Autowired
|
||||
private FaceEngineService faceEngineService;
|
||||
|
||||
@Autowired
|
||||
private MinioClient minioClient;
|
||||
|
@ -60,146 +43,6 @@ public class DemoController {
|
|||
(bucketName, minioUtil.uploadFile(bucketName, file))));
|
||||
}
|
||||
|
||||
@GetMapping("demo111")
|
||||
public int saveTenPerson() {
|
||||
|
||||
try {
|
||||
|
||||
try {
|
||||
GetObjectArgs args = GetObjectArgs.builder()
|
||||
.bucket("videos")
|
||||
.object("James Gosling.jpg")
|
||||
.build();
|
||||
|
||||
//判断人脸照片是否合格
|
||||
//1.保存到本地
|
||||
InputStream tempInputStream = minioClient.getObject(args);
|
||||
//----------------算法检测----------------------------------------------
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = tempInputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
byte[] bytes = outputStream.toByteArray();
|
||||
outputStream.close();
|
||||
tempInputStream.close();
|
||||
|
||||
ImageInfo rgbData = ImageFactory.getRGBData(bytes);
|
||||
List<FaceInfo> faceInfoList = faceEngineService.detectFaces(rgbData);
|
||||
if (CollectionUtil.isNotEmpty(faceInfoList)) {
|
||||
FaceInfo faceInfo = faceInfoList.get(0);
|
||||
FaceRecognitionResDTO faceRecognitionResDTO = new FaceRecognitionResDTO();
|
||||
faceRecognitionResDTO.setRect(faceInfo.getRect());
|
||||
byte[] featureBytes = faceEngineService.extractFaceFeature(rgbData, faceInfo, ExtractType.REGISTER);
|
||||
if (featureBytes != null) {
|
||||
System.out.println("featureBytes = " + Arrays.toString(featureBytes));
|
||||
}else{
|
||||
log.error("图片不合格,未检测到人脸");
|
||||
return 2;
|
||||
}
|
||||
}else{
|
||||
log.error("图片不合格,未检测到人脸");
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
e.printStackTrace();
|
||||
return 10;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/offline")
|
||||
public Result activeOffline() {
|
||||
FaceEngine faceEngine;
|
||||
String USER_HOME = System.getProperty("user.home");
|
||||
System.out.println(USER_HOME);
|
||||
String jvmName = System.getProperty("java.vm.name", "").toLowerCase();
|
||||
String osName = System.getProperty("os.name", "").toLowerCase();
|
||||
String osArch = System.getProperty("os.arch", "").toLowerCase();
|
||||
String abiType = System.getProperty("sun.arch.abi", "").toLowerCase();
|
||||
String libPath = System.getProperty("sun.boot.library.path", "").toLowerCase();
|
||||
USER_HOME = System.getProperty("user.home");
|
||||
if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) {
|
||||
osName = "android";
|
||||
} else if (jvmName.startsWith("robovm") && osName.startsWith("darwin")) {
|
||||
osName = "ios";
|
||||
osArch = "arm";
|
||||
} else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) {
|
||||
osName = "macosx";
|
||||
} else {
|
||||
int spaceIndex = osName.indexOf(' ');
|
||||
if (spaceIndex > 0) {
|
||||
osName = osName.substring(0, spaceIndex);
|
||||
}
|
||||
}
|
||||
if (osArch.equals("i386") || osArch.equals("i486") || osArch.equals("i586") || osArch.equals("i686")) {
|
||||
osArch = "x86";
|
||||
} else if (osArch.equals("amd64") || osArch.equals("x86-64") || osArch.equals("x64")) {
|
||||
osArch = "x86_64";
|
||||
} else if (osArch.startsWith("aarch64") || osArch.startsWith("armv8") || osArch.startsWith("arm64")) {
|
||||
osArch = "arm64";
|
||||
} else if ((osArch.startsWith("arm")) && ((abiType.equals("gnueabihf")) || (libPath.contains("openjdk-armhf")))) {
|
||||
osArch = "armhf";
|
||||
} else if (osArch.startsWith("arm")) {
|
||||
osArch = "arm";
|
||||
}
|
||||
String PLATFORM = osName + "-" + osArch;
|
||||
|
||||
System.out.println("PLATFORM = " + PLATFORM);
|
||||
|
||||
String CACHE_LIB_FOLDER = USER_HOME + "/.arcface/cache/" + "4.1" + "/" + PLATFORM + "/";
|
||||
|
||||
System.out.println("CACHE_LIB_FOLDER = " + CACHE_LIB_FOLDER);
|
||||
|
||||
if (Pattern.matches("windows.*", osName)) {
|
||||
|
||||
System.out.println("osName = " + osName);
|
||||
faceEngine = new FaceEngine(CACHE_LIB_FOLDER);
|
||||
}else{
|
||||
// faceEngine = new FaceEngine("D:\\00_桌面\\ArcSoft_ArcFacePro_windows_x64_java_V4.1\\libs\\WIN64");
|
||||
faceEngine = new FaceEngine(CACHE_LIB_FOLDER);
|
||||
}
|
||||
|
||||
|
||||
ActiveDeviceInfo activeDeviceInfo = new ActiveDeviceInfo();
|
||||
int code = faceEngine.getActiveDeviceInfo(activeDeviceInfo);
|
||||
|
||||
// System.out.println("\n设备信息:" + activeDeviceInfo.toString() + "\n"); // 这个(com.arcsoft.face.model.ActiveDeviceInfo@4ad9b7b)并不是设备信息
|
||||
System.out.println("设备信息:" + activeDeviceInfo.getDeviceInfo() + "\n"); // 这个的结果才是设备信息
|
||||
|
||||
|
||||
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
|
||||
faceEngine.getActiveFileInfo(activeFileInfo);
|
||||
System.out.println("激活文件信息:" + activeFileInfo.toString());
|
||||
|
||||
System.out.println("activeFileInfo = " + activeFileInfo);
|
||||
|
||||
return Result.success(activeDeviceInfo.getDeviceInfo());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.github.dockerjava.api.command.CreateContainerCmd;
|
||||
import com.github.dockerjava.api.model.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/common")
|
||||
public class DockerController {
|
||||
|
||||
private final DockerClient dockerClient;
|
||||
|
||||
@Autowired
|
||||
public DockerController(DockerClient dockerClient) {
|
||||
this.dockerClient = dockerClient;
|
||||
}
|
||||
|
||||
@GetMapping("/docker-info")
|
||||
public String getDockerInfo() {
|
||||
Info info = dockerClient.infoCmd().exec(); // 获取 Docker 守护进程的信息
|
||||
|
||||
|
||||
System.out.println("info = " + info);
|
||||
|
||||
return info.toString(); // 将 Docker 信息返回到 HTTP 响应
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取镜像列表
|
||||
*
|
||||
* @param
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/imageList")
|
||||
public List<Image> imageList() {
|
||||
List<Image> imageList = dockerClient.listImagesCmd().withShowAll(true).exec();
|
||||
|
||||
System.out.println("imageList = " + imageList);
|
||||
return imageList;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/listContainers")
|
||||
public List<Container> listContainers() {
|
||||
List<Container> exec = dockerClient.listContainersCmd().exec();
|
||||
|
||||
exec.forEach(container ->
|
||||
System.out.println("name = " + Arrays.toString(container.getNames())));
|
||||
return exec;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/create")
|
||||
public void create() throws IOException {
|
||||
|
||||
|
||||
|
||||
String javaFilePath = "D:\\00_桌面\\demo.java"; // 这里填入 Java 文件路径
|
||||
File javaFile = new File(javaFilePath);
|
||||
|
||||
Path localDir = Files.createTempDirectory("docker-java");
|
||||
File localJavaFile = new File(localDir.toFile(), "Main.java");
|
||||
Files.copy(javaFile.toPath(), localJavaFile.toPath());
|
||||
|
||||
|
||||
|
||||
String imageName = "openjdk:8"; // 使用官方 OpenJDK 11 镜像
|
||||
|
||||
|
||||
HostConfig hostConfig = HostConfig.newHostConfig()
|
||||
.withBinds(new Bind(localDir.toString(), new com.github.dockerjava.api.model.Volume("/java"))); // 绑定挂载
|
||||
|
||||
|
||||
// 启动容器
|
||||
String containerId = dockerClient.createContainerCmd(imageName)
|
||||
.withCmd("bash", "-c", "javac /java/Main.java && java -cp /java Main && tail -f /dev/null")
|
||||
.withHostConfig(hostConfig)
|
||||
.withName("java-app-container")
|
||||
.exec().getId();
|
||||
|
||||
dockerClient.startContainerCmd(containerId).exec();
|
||||
|
||||
System.out.println("Container started. Check the output in Docker.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
//package com.guwan.backend.controller;
|
||||
//
|
||||
//import com.guwan.backend.entity.LiveRoom;
|
||||
//import com.guwan.backend.entity.LiveRoomDTO;
|
||||
//import com.guwan.backend.service.LiveService;
|
||||
//import com.guwan.backend.util.Result;
|
||||
//import com.guwan.backend.util.SecurityUtil;
|
||||
//import lombok.RequiredArgsConstructor;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.springframework.web.bind.annotation.*;
|
||||
//import io.swagger.v3.oas.annotations.Operation;
|
||||
//import io.swagger.v3.oas.annotations.media.Schema;
|
||||
//import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
//import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
//
|
||||
//@Slf4j
|
||||
//@Tag(name = "直播管理", description = "直播相关接口")
|
||||
//@RestController
|
||||
//@RequestMapping("/api/live")
|
||||
//@RequiredArgsConstructor
|
||||
//public class LiveController {
|
||||
//
|
||||
// private final LiveService liveService;
|
||||
// private final SecurityUtil securityUtil;
|
||||
//
|
||||
// @Operation(summary = "创建直播间")
|
||||
// @SecurityRequirement(name = "bearer-jwt")
|
||||
// @PostMapping("/room")
|
||||
// public Result<LiveRoom> createLiveRoom(@RequestBody LiveRoomDTO dto) {
|
||||
// try {
|
||||
// return Result.success(liveService.createLiveRoom(dto));
|
||||
// } catch (Exception e) {
|
||||
// log.error("创建直播间失败", e);
|
||||
// return Result.error(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Operation(summary = "开始直播")
|
||||
// @SecurityRequirement(name = "bearer-jwt")
|
||||
// @PostMapping("/room/{id}/start")
|
||||
// public Result<Void> startLive(@PathVariable Long id) {
|
||||
// try {
|
||||
// // 检查权限
|
||||
// checkPermission(id);
|
||||
// liveService.startLive(id);
|
||||
// return Result.success();
|
||||
// } catch (Exception e) {
|
||||
// log.error("开始直播失败", e);
|
||||
// return Result.error(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Operation(summary = "结束直播")
|
||||
// @SecurityRequirement(name = "bearer-jwt")
|
||||
// @PostMapping("/room/{id}/end")
|
||||
// public Result<Void> endLive(@PathVariable Long id) {
|
||||
// try {
|
||||
// // 检查权限
|
||||
// checkPermission(id);
|
||||
// liveService.endLive(id);
|
||||
// return Result.success();
|
||||
// } catch (Exception e) {
|
||||
// log.error("结束直播失败", e);
|
||||
// return Result.error(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 检查权限
|
||||
// */
|
||||
// private void checkPermission(Long roomId) {
|
||||
// LiveRoom room = liveService.getLiveRoom(roomId);
|
||||
// if (room == null) {
|
||||
// throw new IllegalArgumentException("直播间不存在");
|
||||
// }
|
||||
//
|
||||
// Long currentUserId = securityUtil.getCurrentUserId();
|
||||
// if (!room.getUserId().equals(currentUserId)) {
|
||||
// throw new IllegalStateException("无权操作此直播间");
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -1,80 +0,0 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.dto.live.CreateRoomRequest;
|
||||
import com.guwan.backend.dto.live.StartLiveRequest;
|
||||
import com.guwan.backend.dto.live.WebRTCRequest;
|
||||
import com.guwan.backend.dto.live.WebRTCResponse;
|
||||
import com.guwan.backend.entity.LiveRoom;
|
||||
import com.guwan.backend.service.LiveStreamService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "直播管理", description = "直播相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/live")
|
||||
@RequiredArgsConstructor
|
||||
public class LiveStreamController {
|
||||
|
||||
private final LiveStreamService liveStreamService;
|
||||
|
||||
@Operation(summary = "创建直播间")
|
||||
@PostMapping("/room")
|
||||
public Result<LiveRoom> createLiveRoom(@RequestBody CreateRoomRequest request) {
|
||||
try {
|
||||
LiveRoom room = liveStreamService.createLiveRoom(
|
||||
request.getTitle(),
|
||||
request.getDescription(),
|
||||
request.getUserId()
|
||||
);
|
||||
return Result.success(room);
|
||||
} catch (Exception e) {
|
||||
log.error("创建直播间失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "开始直播")
|
||||
@PostMapping("/room/{id}/start")
|
||||
public Result<Void> startLive(
|
||||
@PathVariable Long id,
|
||||
@RequestBody StartLiveRequest request) {
|
||||
try {
|
||||
liveStreamService.startLive(id, request.getSourceUrl());
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("开始直播失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "结束直播")
|
||||
@PostMapping("/room/{id}/end")
|
||||
public Result<Void> endLive(@PathVariable Long id) {
|
||||
try {
|
||||
liveStreamService.endLive(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("结束直播失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取WebRTC Offer")
|
||||
@PostMapping("/room/{id}/webrtc")
|
||||
public Result<WebRTCResponse> getWebRTCOffer(
|
||||
@PathVariable Long id,
|
||||
@RequestBody WebRTCRequest request) {
|
||||
try {
|
||||
String sdp = liveStreamService.getWebRTCOffer(id, request.getSdp());
|
||||
return Result.success(new WebRTCResponse(sdp));
|
||||
} catch (Exception e) {
|
||||
log.error("获取WebRTC Offer失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import cn.easyes.core.biz.EsPageInfo;
|
||||
import com.guwan.backend.elasticsearch.document.LogstashLog;
|
||||
import com.guwan.backend.service.LogstashLogService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/logs")
|
||||
@RequiredArgsConstructor
|
||||
public class LogController {
|
||||
|
||||
private final LogstashLogService logstashLogService;
|
||||
|
||||
@GetMapping("/search")
|
||||
public List<LogstashLog> searchLogs(
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
|
||||
@RequestParam(required = false) String level,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
|
||||
if (keyword != null) {
|
||||
return logstashLogService.searchLogsByKeyword(keyword, startTime, endTime);
|
||||
} else {
|
||||
return logstashLogService.queryLogsByTimeRangeAndLevel(startTime, endTime, level);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/errors")
|
||||
public EsPageInfo<LogstashLog> getRecentErrors(
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return logstashLogService.getRecentErrorLogs(pageNum, pageSize);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.dto.product.ProductDTO;
|
||||
import com.guwan.backend.service.ProductSearchService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "商品搜索", description = "商品搜索相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/products")
|
||||
@RequiredArgsConstructor
|
||||
public class ProductController {
|
||||
|
||||
@Autowired(required = false)
|
||||
private ProductSearchService productSearchService;
|
||||
|
||||
@Operation(summary = "保存商品")
|
||||
@PostMapping
|
||||
public Result<ProductDTO> save(@RequestBody ProductDTO product) {
|
||||
try {
|
||||
if (productSearchService == null) {
|
||||
return Result.error("搜索服务未启用");
|
||||
}
|
||||
productSearchService.saveOrUpdate(product);
|
||||
return Result.success(product);
|
||||
} catch (Exception e) {
|
||||
log.error("保存商品失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "搜索商品")
|
||||
@GetMapping("/search")
|
||||
public Result<List<ProductDTO>> search(
|
||||
@Parameter(description = "搜索关键词") @RequestParam String keyword) {
|
||||
try {
|
||||
return Result.success(productSearchService.search(keyword));
|
||||
} catch (Exception e) {
|
||||
log.error("搜索商品失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "按分类查询商品")
|
||||
@GetMapping("/category/{category}")
|
||||
public Result<List<ProductDTO>> getByCategory(
|
||||
@Parameter(description = "商品分类") @PathVariable String category) {
|
||||
try {
|
||||
return Result.success(productSearchService.getByCategory(category));
|
||||
} catch (Exception e) {
|
||||
log.error("查询商品分类失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.entity.ReadingNote;
|
||||
import com.guwan.backend.service.ReadingNoteService;
|
||||
import com.guwan.backend.util.SecurityUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "阅读笔记", description = "阅读笔记相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/reading-notes")
|
||||
@RequiredArgsConstructor
|
||||
public class ReadingNoteController {
|
||||
|
||||
private final ReadingNoteService noteService;
|
||||
private final SecurityUtil securityUtil;
|
||||
|
||||
@Operation(summary = "添加笔记")
|
||||
@PostMapping
|
||||
public Result<ReadingNote> addNote(@RequestBody ReadingNote note) {
|
||||
try {
|
||||
note.setUserId(securityUtil.getCurrentUserId());
|
||||
return Result.success(noteService.addNote(note));
|
||||
} catch (Exception e) {
|
||||
log.error("添加笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "更新笔记")
|
||||
@PutMapping("/{id}")
|
||||
public Result<ReadingNote> updateNote(@PathVariable Long id, @RequestBody ReadingNote note) {
|
||||
try {
|
||||
note.setId(id);
|
||||
note.setUserId(securityUtil.getCurrentUserId());
|
||||
return Result.success(noteService.updateNote(note));
|
||||
} catch (Exception e) {
|
||||
log.error("更新笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "删除笔记")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteNote(@PathVariable Long id) {
|
||||
try {
|
||||
noteService.deleteNote(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("删除笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取笔记详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<ReadingNote> getNote(@PathVariable Long id) {
|
||||
try {
|
||||
return Result.success(noteService.getNoteById(id));
|
||||
} catch (Exception e) {
|
||||
log.error("获取笔记详情失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户的所有笔记")
|
||||
@GetMapping
|
||||
public Result<IPage<ReadingNote>> getUserNotes(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(noteService.getUserNotes(userId, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取书籍的所有公开笔记")
|
||||
@GetMapping("/book/{bookId}")
|
||||
public Result<IPage<ReadingNote>> getBookNotes(
|
||||
@PathVariable Long bookId,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
return Result.success(noteService.getBookNotes(bookId, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取书籍笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户在特定书籍上的笔记")
|
||||
@GetMapping("/book/{bookId}/my")
|
||||
public Result<IPage<ReadingNote>> getUserBookNotes(
|
||||
@PathVariable Long bookId,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(noteService.getUserBookNotes(userId, bookId, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户书籍笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有公开笔记")
|
||||
@GetMapping("/public")
|
||||
public Result<IPage<ReadingNote>> getPublicNotes(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
return Result.success(noteService.getPublicNotes(pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取公开笔记失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.pojo.dto.ReadingStatistics;
|
||||
import com.guwan.backend.pojo.entity.ReadingProgress;
|
||||
import com.guwan.backend.service.ReadingProgressService;
|
||||
import com.guwan.backend.util.SecurityUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "阅读进度", description = "阅读进度相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/reading-progress")
|
||||
@RequiredArgsConstructor
|
||||
public class ReadingProgressController {
|
||||
|
||||
private final ReadingProgressService progressService;
|
||||
private final SecurityUtil securityUtil;
|
||||
|
||||
@Operation(summary = "更新阅读进度")
|
||||
@PostMapping
|
||||
public Result<ReadingProgress> updateProgress(@RequestBody ReadingProgress progress) {
|
||||
try {
|
||||
progress.setUserId(securityUtil.getCurrentUserId());
|
||||
return Result.success(progressService.updateProgress(progress));
|
||||
} catch (Exception e) {
|
||||
log.error("更新阅读进度失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取书籍阅读进度")
|
||||
@GetMapping("/book/{bookId}")
|
||||
public Result<ReadingProgress> getProgress(@PathVariable Long bookId) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(progressService.getProgress(userId, bookId));
|
||||
} catch (Exception e) {
|
||||
log.error("获取阅读进度失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户的所有阅读进度")
|
||||
@GetMapping
|
||||
public Result<IPage<ReadingProgress>> getUserProgress(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(progressService.getUserProgress(userId, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户阅读进度失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取特定状态的书籍")
|
||||
@GetMapping("/status/{status}")
|
||||
public Result<IPage<ReadingProgress>> getProgressByStatus(
|
||||
@PathVariable String status,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(progressService.getProgressByStatus(userId, status, pageNum, pageSize));
|
||||
} catch (Exception e) {
|
||||
log.error("获取阅读状态失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "更新阅读时长")
|
||||
@PostMapping("/{bookId}/reading-time")
|
||||
public Result<Void> updateReadingTime(
|
||||
@PathVariable Long bookId,
|
||||
@RequestParam Integer minutes) {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
progressService.updateReadingTime(userId, bookId, minutes);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("更新阅读时长失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取阅读统计")
|
||||
@GetMapping("/statistics")
|
||||
public Result<ReadingStatistics> getReadingStatistics() {
|
||||
try {
|
||||
Long userId = securityUtil.getCurrentUserId();
|
||||
return Result.success(progressService.getReadingStatistics(userId));
|
||||
} catch (Exception e) {
|
||||
log.error("获取阅读统计失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,12 @@ package com.guwan.backend.controller;
|
|||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.dto.user.*;
|
||||
import com.guwan.backend.pojo.dto.user.*;
|
||||
import com.guwan.backend.service.EmailService;
|
||||
import com.guwan.backend.service.UserService;
|
||||
import com.guwan.backend.util.RedisUtils;
|
||||
import com.guwan.backend.util.SmsUtils;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
@ -98,9 +97,13 @@ public class UserController {
|
|||
log.info("邮箱注册: {}", email);
|
||||
|
||||
Context context = new Context();
|
||||
|
||||
context.setVariable("nowDate", DateUtil.now());
|
||||
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
|
||||
redisUtils.set(email, code, 10);
|
||||
|
||||
context.setVariable("code", code.toCharArray());
|
||||
|
||||
emailService.sendHtmlMessage(email,
|
||||
|
@ -112,7 +115,9 @@ public class UserController {
|
|||
@PostMapping("/getPhoneCode")
|
||||
public Result registerByPhone(@RequestBody @Valid PhoneDto phoneDto) throws Exception {
|
||||
String phone = phoneDto.getPhone();
|
||||
|
||||
log.info("手机号注册: {}", phone);
|
||||
|
||||
String random = RandomUtil.randomNumbers(6);
|
||||
|
||||
SmsUtils.sendMessage(phone, random);
|
||||
|
@ -127,9 +132,14 @@ public class UserController {
|
|||
|
||||
|
||||
@PostMapping("/password/reset")
|
||||
public Result<Void> resetPassword(@RequestParam @Email String email) {
|
||||
public Result<Void> resetPassword(@RequestBody ChangePasswordDTO changePasswordDTO) {
|
||||
|
||||
log.debug("更改方式: {}, 内容: {}",
|
||||
changePasswordDTO.getChangeWay(),
|
||||
changePasswordDTO.getInfo());
|
||||
|
||||
try {
|
||||
userService.resetPassword(email);
|
||||
userService.resetPassword(changePasswordDTO);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("重置密码失败", e);
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
package com.guwan.backend.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.guwan.backend.common.Result;
|
||||
import com.guwan.backend.dto.video.VideoDTO;
|
||||
import com.guwan.backend.dto.video.VideoUploadDTO;
|
||||
import com.guwan.backend.service.VideoService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "视频管理", description = "视频相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/videos")
|
||||
@RequiredArgsConstructor
|
||||
public class VideoController {
|
||||
|
||||
private final VideoService videoService;
|
||||
|
||||
|
||||
@Operation(summary = "上传视频", description = "上传视频文件并返回视频信息")
|
||||
// @SecurityRequirement(name = "bearer-jwt")
|
||||
@PostMapping("/upload")
|
||||
public Result<VideoDTO> uploadVideo(@RequestBody VideoUploadDTO videoUploadDTO) {
|
||||
try {
|
||||
VideoDTO video = videoService.uploadVideo(videoUploadDTO.getFileUrl(),
|
||||
videoUploadDTO.getTitle(), videoUploadDTO.getDescription(), videoUploadDTO.getTags());
|
||||
return Result.success(video);
|
||||
} catch (Exception e) {
|
||||
log.error("上传视频失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "更新视频信息")
|
||||
@SecurityRequirement(name = "bearer-jwt")
|
||||
@PutMapping("/{id}")
|
||||
public Result<VideoDTO> updateVideo(
|
||||
@Parameter(description = "视频ID") @PathVariable Long id,
|
||||
@RequestBody VideoDTO videoDTO) {
|
||||
try {
|
||||
videoDTO.setId(id);
|
||||
return Result.success(videoService.updateVideo(videoDTO));
|
||||
} catch (Exception e) {
|
||||
log.error("更新视频失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "删除视频")
|
||||
@SecurityRequirement(name = "bearer-jwt")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteVideo(
|
||||
@Parameter(description = "视频ID") @PathVariable Long id) {
|
||||
try {
|
||||
videoService.deleteVideo(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("删除视频失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取视频详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<VideoDTO> getVideo(
|
||||
@Parameter(description = "视频ID") @PathVariable Long id) {
|
||||
try {
|
||||
VideoDTO video = videoService.getVideoById(id);
|
||||
if (video == null) {
|
||||
return Result.notFound("视频不存在");
|
||||
}
|
||||
return Result.success(video);
|
||||
} catch (Exception e) {
|
||||
log.error("获取视频失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "获取视频列表", description = "支持分页和关键词搜索")
|
||||
@GetMapping
|
||||
public Result<IPage<VideoDTO>> getVideoList(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword) {
|
||||
try {
|
||||
return Result.success(videoService.getVideoList(pageNum, pageSize, keyword));
|
||||
} catch (Exception e) {
|
||||
log.error("获取视频列表失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "增加视频观看次数")
|
||||
@PostMapping("/{id}/view")
|
||||
public Result<Void> incrementViewCount(
|
||||
@Parameter(description = "视频ID") @PathVariable Long id) {
|
||||
try {
|
||||
videoService.incrementViewCount(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("增加观看次数失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "视频点赞/取消点赞")
|
||||
@SecurityRequirement(name = "bearer-jwt")
|
||||
@PostMapping("/{id}/like")
|
||||
public Result<Void> toggleLike(
|
||||
@Parameter(description = "视频ID") @PathVariable Long id) {
|
||||
try {
|
||||
videoService.toggleLike(id);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("点赞失败", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.guwan.backend.controller.monitor;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.stats.CacheStats;
|
||||
import com.guwan.backend.annotation.OperationLog;
|
||||
import com.guwan.backend.common.BusinessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/cache")
|
||||
public class CacheMonitor {
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
public CacheMonitor(CacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
@OperationLog(description = "根据名称获取本地缓存使用情况")
|
||||
public Map<String, Object> getStats(String cacheName) {
|
||||
CaffeineCacheManager caffeineCacheManager = (CaffeineCacheManager) cacheManager;
|
||||
Cache<Object, Object> nativeCache = null;
|
||||
try {
|
||||
nativeCache = (Cache<Object, Object>)
|
||||
caffeineCacheManager.getCache(cacheName).getNativeCache();
|
||||
} catch (Exception e) {
|
||||
// throw new RuntimeException(e);
|
||||
throw new BusinessException("没有此本地缓存");
|
||||
}
|
||||
|
||||
CacheStats stats = nativeCache.stats();
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("hitCount 命中次数(成功从缓存中获取数据的次数)", stats.hitCount());
|
||||
result.put("missCount 未命中次数(缓存中没有找到数据的次数)", stats.missCount());
|
||||
result.put("loadCount 加载次数(需要从数据源重新加载数据的次数)", stats.loadCount());
|
||||
result.put("evictionCount 驱逐次数(由于空间限制而被清除的缓存项数量)", stats.evictionCount());
|
||||
result.put("hitRate 命中率", String.format("%.2f%%", stats.hitRate() * 100));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
package com.guwan.backend.converter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@Component
|
||||
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
|
||||
|
||||
*/
|
||||
/**
|
||||
* converter for support http request with header content-type: multipart/form-data
|
||||
*//*
|
||||
|
||||
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
|
||||
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canWrite(MediaType mediaType) {
|
||||
return false;
|
||||
}
|
||||
}*/
|
|
@ -1,18 +0,0 @@
|
|||
package com.guwan.backend.dto.live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "创建直播间请求")
|
||||
public class CreateRoomRequest {
|
||||
|
||||
@Schema(description = "直播间标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "直播间描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "主播用户ID")
|
||||
private Long userId;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.guwan.backend.dto.live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "直播间DTO")
|
||||
public class LiveRoomDTO {
|
||||
@Schema(description = "直播间标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "直播间描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "封面图URL")
|
||||
private String coverUrl;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.dto.live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "开始直播请求")
|
||||
public class StartLiveRequest {
|
||||
|
||||
@Schema(description = "推流地址(RTMP/RTSP)")
|
||||
private String sourceUrl;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.dto.live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "WebRTC请求")
|
||||
public class WebRTCRequest {
|
||||
|
||||
@Schema(description = "SDP offer")
|
||||
private String sdp;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.guwan.backend.dto.live;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "WebRTC响应")
|
||||
public class WebRTCResponse {
|
||||
|
||||
@Schema(description = "SDP answer")
|
||||
private String sdp;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package com.guwan.backend.dto.video;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@Schema(description = "视频上传DTO")
|
||||
@Data
|
||||
public class VideoUploadDTO {
|
||||
@Schema(description = "视频文件地址")
|
||||
String fileUrl;
|
||||
@Schema(description = "视频标题")
|
||||
String title;
|
||||
@Schema(description = "视频描述")
|
||||
String description;
|
||||
@Schema(description = "视频标签,多个用逗号分隔", nullable = true)
|
||||
String tags;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package com.guwan.backend.elasticsearch.document;
|
||||
|
||||
import cn.easyes.annotation.IndexField;
|
||||
import cn.easyes.annotation.IndexId;
|
||||
import cn.easyes.annotation.IndexName;
|
||||
import cn.easyes.annotation.rely.FieldType;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
@IndexName("logstash-logs-*")
|
||||
public class LogstashLog {
|
||||
|
||||
@IndexId
|
||||
private String id;
|
||||
|
||||
@JsonProperty("@timestamp")
|
||||
@IndexField(value = "@timestamp", fieldType = FieldType.DATE)
|
||||
private Instant timestamp;
|
||||
|
||||
@JsonProperty("@version")
|
||||
@IndexField(fieldType = FieldType.KEYWORD)
|
||||
private String version;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String message;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String level;
|
||||
|
||||
@IndexField(fieldType = FieldType.LONG)
|
||||
private Long level_value;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String logger_name;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String thread_name;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String stack_trace;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String app_name;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String host;
|
||||
|
||||
@IndexField(fieldType = FieldType.LONG)
|
||||
private Long port;
|
||||
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private List<String> tags;
|
||||
|
||||
@Data
|
||||
public static class GeoIP {
|
||||
@IndexField(fieldType = FieldType.IP)
|
||||
private String ip;
|
||||
|
||||
@IndexField(fieldType = FieldType.FLOAT)
|
||||
private Float latitude;
|
||||
|
||||
@IndexField(fieldType = FieldType.FLOAT)
|
||||
private Float longitude;
|
||||
|
||||
@IndexField(fieldType = FieldType.GEO_POINT)
|
||||
private String location;
|
||||
}
|
||||
|
||||
@IndexField(fieldType = FieldType.OBJECT)
|
||||
private GeoIP geoip;
|
||||
|
||||
@JsonProperty("APP_NAME")
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String APP_NAME;
|
||||
|
||||
@JsonProperty("LOGSTASH_HOST")
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String LOGSTASH_HOST;
|
||||
|
||||
@JsonProperty("LOGSTASH_PORT")
|
||||
@IndexField(fieldType = FieldType.TEXT)
|
||||
private String LOGSTASH_PORT;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.guwan.backend.es.document;
|
||||
package com.guwan.backend.elasticsearch.document;
|
||||
|
||||
import cn.easyes.annotation.IndexField;
|
||||
import cn.easyes.annotation.IndexId;
|
|
@ -1,4 +1,4 @@
|
|||
package com.guwan.backend.es.document;
|
||||
package com.guwan.backend.elasticsearch.document;
|
||||
|
||||
import cn.easyes.annotation.IndexField;
|
||||
import cn.easyes.annotation.IndexId;
|
|
@ -0,0 +1,9 @@
|
|||
package com.guwan.backend.elasticsearch.mapper;
|
||||
|
||||
import cn.easyes.core.conditions.interfaces.BaseEsMapper;
|
||||
import com.guwan.backend.elasticsearch.document.LogstashLog;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface LogstashLogEsMapper extends BaseEsMapper<LogstashLog> {
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.guwan.backend.es.mapper;
|
||||
package com.guwan.backend.elasticsearch.mapper;
|
||||
|
||||
import cn.easyes.core.conditions.interfaces.BaseEsMapper;
|
||||
import com.guwan.backend.es.document.ProductDocument;
|
||||
import com.guwan.backend.elasticsearch.document.ProductDocument;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
|
@ -1,7 +1,7 @@
|
|||
package com.guwan.backend.es.mapper;
|
||||
package com.guwan.backend.elasticsearch.mapper;
|
||||
|
||||
import cn.easyes.core.conditions.interfaces.BaseEsMapper;
|
||||
import com.guwan.backend.es.document.VideoDocument;
|
||||
import com.guwan.backend.elasticsearch.document.VideoDocument;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
|
@ -1,16 +0,0 @@
|
|||
package com.guwan.backend.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class LiveMessage {
|
||||
private String type; // 消息类型:CHAT-聊天,GIFT-礼物,LIKE-点赞,ENTER-进入,LEAVE-离开
|
||||
private Long userId; // 用户ID
|
||||
private String username; // 用户名
|
||||
private String content; // 消息内容
|
||||
private String giftType; // 礼物类型
|
||||
private Integer giftCount; // 礼物数量
|
||||
private LocalDateTime time; // 时间
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package com.guwan.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("live_room")
|
||||
public class LiveRoom {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String title; // 直播间标题
|
||||
private String description; // 直播间描述
|
||||
private String coverUrl; // 封面图
|
||||
private Long userId; // 主播ID
|
||||
private String username; // 主播名称
|
||||
private String streamId; // Go2RTC流ID
|
||||
private String status; // 状态:PREPARING-准备中,LIVING-直播中,ENDED-已结束
|
||||
private Integer onlineCount; // 在线人数
|
||||
private Integer likeCount; // 点赞数
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
//package com.guwan.backend.face;
|
||||
//
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.shapelight.common.config.GlobalValue;
|
||||
//import net.shapelight.modules.face.entity.UserCompareInfo;
|
||||
//import net.shapelight.modules.face.service.FaceEngineService;
|
||||
//import net.shapelight.modules.face.util.Base64Util;
|
||||
//import net.shapelight.modules.face.util.UserInfo;
|
||||
//import net.shapelight.modules.face.util.UserRamGroup;
|
||||
//import net.shapelight.modules.ten.entity.TenCellEntity;
|
||||
//import net.shapelight.modules.ten.entity.TenPersonEntity;
|
||||
//import net.shapelight.modules.ten.service.TenCellService;
|
||||
//import net.shapelight.modules.ten.service.TenPersonService;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.ApplicationArguments;
|
||||
//import org.springframework.boot.ApplicationRunner;
|
||||
//import org.springframework.core.annotation.Order;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//
|
||||
//import java.util.List;
|
||||
//
|
||||
//@Component
|
||||
//@Order(1)
|
||||
//@Slf4j
|
||||
//public class FaceEngineAutoRun implements ApplicationRunner {
|
||||
// @Autowired
|
||||
// private FaceEngineService faceEngineService;
|
||||
// @Autowired
|
||||
// private TenPersonService tenPersonService;
|
||||
// @Autowired
|
||||
// private TenCellService tenCellService;
|
||||
// @Autowired
|
||||
// private GlobalValue globalValue;
|
||||
//
|
||||
// @Override
|
||||
// public void run(ApplicationArguments args) throws Exception {
|
||||
// // 任务初始化
|
||||
// log.debug("服务启动。。。。。初始化人脸库");
|
||||
//// Map<String, String> fileMap = Maps.newHashMap();
|
||||
//// fileMap.put("zhao1", "赵丽颖");
|
||||
//// fileMap.put("yang1", "杨紫");
|
||||
//// fileMap.put("baixue", "白雪");
|
||||
//// fileMap.put("chenchuang", "陈创");
|
||||
//// for (String f : fileMap.keySet()) {
|
||||
//// ClassPathResource resource = new ClassPathResource("static/images/" + f + ".jpg");
|
||||
//// InputStream inputStream = resource.getInputStream();
|
||||
//// ImageInfo rgbData = ImageFactory.getRGBData(inputStream);
|
||||
//// List<FaceInfo> faceInfoList = faceEngineService.detectFaces(rgbData);
|
||||
//// if (CollectionUtil.isNotEmpty(faceInfoList)) {
|
||||
//// byte[] feature = faceEngineService.extractFaceFeature(rgbData, faceInfoList.get(0), ExtractType.REGISTER);
|
||||
//// UserRamCache.UserInfo userInfo = new UserCompareInfo();
|
||||
//// userInfo.setFaceId(f);
|
||||
//// userInfo.setName(fileMap.get(f));
|
||||
//// userInfo.setFaceFeature(feature);
|
||||
//// //这边注册到内存缓存中,也可以根据业务,注册到数据库中
|
||||
//// UserRamCache.addUser(userInfo);
|
||||
//// }
|
||||
//// }
|
||||
//
|
||||
//// int count = tenPersonService.findCount();
|
||||
//// int pageSize = 1000;
|
||||
//// int page = count/pageSize;
|
||||
//// if(count%1000!=0){
|
||||
//// page = page+1;
|
||||
//// }
|
||||
//// int faceCount = 0;
|
||||
//// for (int i = 0; i < page; i++) {
|
||||
//// int start = i*1000;
|
||||
//// List<TenPersonEntity> listPage = tenPersonService.listPage(start,1000);
|
||||
//// for(TenPersonEntity personEntity: listPage){
|
||||
//// if(personEntity.getFeature()!=null && personEntity.getFeature().length()>0){
|
||||
//// UserRamCache.UserInfo userInfo = new UserCompareInfo();
|
||||
//// userInfo.setFaceId(personEntity.getPersonId()+"");
|
||||
//// userInfo.setName(personEntity.getName());
|
||||
//// userInfo.setFaceFeature(Base64Util.base64ToBytes(personEntity.getFeature()));
|
||||
//// //这边注册到内存缓存中
|
||||
//// UserRamCache.addUser(userInfo);
|
||||
//// faceCount++;
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//
|
||||
// System.out.println(globalValue.getTenantId());
|
||||
//
|
||||
// List<TenCellEntity> cellList = tenCellService.list(new QueryWrapper<TenCellEntity>()
|
||||
// .eq("tenant_id",globalValue.getTenantId())
|
||||
// .eq("delete_flag",0));
|
||||
// for(TenCellEntity cellEntity: cellList){
|
||||
// String cellId = cellEntity.getCellId()+"";
|
||||
// System.out.println("cellId = " + cellId);
|
||||
// System.out.println("UserRamGroup = " + UserRamGroup.class);
|
||||
// UserRamGroup.addCell(cellId);
|
||||
// UserRamGroup.addOrgId(cellEntity.getOrgId(),cellId);
|
||||
// int count = tenPersonService.findCount(cellId);
|
||||
// int pageSize = 1000;
|
||||
// int page = count/pageSize;
|
||||
// if(count%1000!=0){
|
||||
// page = page+1;
|
||||
// }
|
||||
// int faceCount = 0;
|
||||
// for (int i = 0; i < page; i++) {
|
||||
// int start = i*1000;
|
||||
// List<TenPersonEntity> listPage = tenPersonService.listPage(start,1000, cellId);
|
||||
// for(TenPersonEntity personEntity: listPage){
|
||||
// if(personEntity.getFeature()!=null && personEntity.getFeature().length()>0){
|
||||
// UserInfo userInfo = new UserCompareInfo();
|
||||
// userInfo.setFaceId(personEntity.getPersonId()+"");
|
||||
// userInfo.setName(personEntity.getName());
|
||||
// userInfo.setFaceFeature(Base64Util.base64ToBytes(personEntity.getFeature()));
|
||||
// //这边注册到内存缓存中
|
||||
// UserRamGroup.addUser(userInfo,cellId);
|
||||
// faceCount++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// log.debug(cellEntity.getName()+":初始化人脸库完成,共 "+faceCount+" 人");
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -1,133 +0,0 @@
|
|||
package com.guwan.backend.face.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ArcFaceAutoConfiguration implements InitializingBean, DisposableBean {
|
||||
|
||||
|
||||
private static final String PLATFORM;
|
||||
|
||||
private static final String USER_HOME;
|
||||
|
||||
public static String CACHE_LIB_FOLDER;
|
||||
|
||||
@Value("${config.arcface-sdk.version}")
|
||||
public String ARC_FACE_VERSION;
|
||||
|
||||
static {
|
||||
String jvmName = System.getProperty("java.vm.name", "").toLowerCase();
|
||||
String osName = System.getProperty("os.name", "").toLowerCase();
|
||||
String osArch = System.getProperty("os.arch", "").toLowerCase();
|
||||
String abiType = System.getProperty("sun.arch.abi", "").toLowerCase();
|
||||
String libPath = System.getProperty("sun.boot.library.path", "").toLowerCase();
|
||||
USER_HOME = System.getProperty("user.home");
|
||||
if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) {
|
||||
osName = "android";
|
||||
} else if (jvmName.startsWith("robovm") && osName.startsWith("darwin")) {
|
||||
osName = "ios";
|
||||
osArch = "arm";
|
||||
} else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) {
|
||||
osName = "macosx";
|
||||
} else {
|
||||
int spaceIndex = osName.indexOf(' ');
|
||||
if (spaceIndex > 0) {
|
||||
osName = osName.substring(0, spaceIndex);
|
||||
}
|
||||
}
|
||||
if (osArch.equals("i386") || osArch.equals("i486") || osArch.equals("i586") || osArch.equals("i686")) {
|
||||
osArch = "x86";
|
||||
} else if (osArch.equals("amd64") || osArch.equals("x86-64") || osArch.equals("x64")) {
|
||||
osArch = "x86_64";
|
||||
} else if (osArch.startsWith("aarch64") || osArch.startsWith("armv8") || osArch.startsWith("arm64")) {
|
||||
osArch = "arm64";
|
||||
} else if ((osArch.startsWith("arm")) && ((abiType.equals("gnueabihf")) || (libPath.contains("openjdk-armhf")))) {
|
||||
osArch = "armhf";
|
||||
} else if (osArch.startsWith("arm")) {
|
||||
osArch = "arm";
|
||||
}
|
||||
PLATFORM = osName + "-" + osArch;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws IOException {
|
||||
CACHE_LIB_FOLDER = USER_HOME + "/.arcface/cache/" + ARC_FACE_VERSION + "/" + PLATFORM + "/";
|
||||
loadLibrary();
|
||||
}
|
||||
|
||||
public void loadLibrary() throws IOException {
|
||||
String baseFolder = "";
|
||||
String suffix = ".dll";
|
||||
if ("windows-x86_64".equals(PLATFORM)) {
|
||||
baseFolder = "WIN64";
|
||||
} else if ("windows-x86".equals(PLATFORM)) {
|
||||
baseFolder = "WIN32";
|
||||
} else if ("linux-x86_64".equals(PLATFORM)) {
|
||||
baseFolder = "LINUX64";
|
||||
suffix = ".so";
|
||||
}
|
||||
|
||||
if ("".equals(baseFolder)) {
|
||||
throw new RuntimeException("ArcFace不支持该操作系统");
|
||||
}
|
||||
|
||||
|
||||
File file = new File(CACHE_LIB_FOLDER);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
|
||||
List<String> libList = new LinkedList<>();
|
||||
libList.add("libarcsoft_face");
|
||||
libList.add("libarcsoft_face_engine");
|
||||
libList.add("libarcsoft_face_engine_jni");
|
||||
|
||||
for (String lib : libList) {
|
||||
ClassPathResource resource = new ClassPathResource("libs/" + ARC_FACE_VERSION + "/" + baseFolder + "/" + lib + suffix);
|
||||
InputStream inputStream = resource.getInputStream();
|
||||
int faceLength = inputStream.available();
|
||||
File facePath = new File(CACHE_LIB_FOLDER + lib + suffix);
|
||||
if (facePath.exists()) {
|
||||
if (facePath.length() == faceLength) {
|
||||
continue;
|
||||
}
|
||||
facePath.delete();
|
||||
}
|
||||
writeToLocal(CACHE_LIB_FOLDER + lib + suffix, inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeToLocal(String destination, InputStream input)
|
||||
throws IOException {
|
||||
int index;
|
||||
byte[] bytes = new byte[1024 * 100];
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(destination);
|
||||
while ((index = input.read(bytes)) != -1) {
|
||||
fileOutputStream.write(bytes, 0, index);
|
||||
}
|
||||
fileOutputStream.flush();
|
||||
fileOutputStream.close();
|
||||
input.close();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author shentao
|
||||
* @desc
|
||||
* @date 2022/3/30
|
||||
*/
|
||||
@Data
|
||||
public class CompareFacesReqDTO {
|
||||
private String image1;
|
||||
private String image2;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FaceAddReqDTO {
|
||||
|
||||
private String name;
|
||||
|
||||
private String image;
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FaceDetectReqDTO {
|
||||
|
||||
private String image;
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import com.arcsoft.face.Rect;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FaceDetectResDTO {
|
||||
private Rect rect;
|
||||
private int orient;
|
||||
private int faceId = -1;
|
||||
private int age = -1;
|
||||
private int gender = -1;
|
||||
private int liveness = -1;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FaceRecognitionReqDTO {
|
||||
|
||||
private String image;
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
|
||||
import com.arcsoft.face.Rect;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FaceRecognitionResDTO {
|
||||
|
||||
private Rect rect;
|
||||
private String personId;
|
||||
private String name;
|
||||
private float similar;
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.guwan.backend.face.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetFaceListResDTO {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String url;
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.face.entity;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ProcessInfo {
|
||||
private int age;
|
||||
private int gender;
|
||||
private int liveness;
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.face.entity;
|
||||
|
||||
|
||||
import com.guwan.backend.face.util.UserInfo;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public class UserCompareInfo extends UserInfo {
|
||||
private Float similar;
|
||||
private Integer isHeadOnView; //0否1是
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package com.guwan.backend.face.enums;
|
||||
|
||||
|
||||
import com.guwan.backend.face.rpc.ErrorCode;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
@Getter
|
||||
public enum ErrorCodeEnum implements ErrorCode {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS(0, "success", "成功"),
|
||||
FAIL(1, "fail", "失败"),
|
||||
PARAM_ERROR(2, "param error", "参数错误"),
|
||||
SYSTEM_ERROR(999, "system error", "系统错误"),
|
||||
|
||||
;
|
||||
private Integer code;
|
||||
private String desc;
|
||||
private String descCN;
|
||||
|
||||
ErrorCodeEnum(Integer code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
ErrorCodeEnum(Integer code, String desc, String descCN) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
this.descCN = descCN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescCN() {
|
||||
return descCN;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package com.guwan.backend.face.face;
|
||||
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
|
||||
public class FacePreviewInfo {
|
||||
private FaceInfo faceInfo;
|
||||
private int trackId;
|
||||
private int gender = -1;
|
||||
|
||||
public FacePreviewInfo(FaceInfo faceInfo, int trackId) {
|
||||
this.faceInfo = faceInfo;
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
public FacePreviewInfo(FaceInfo faceInfo, int trackId, int gender) {
|
||||
this.faceInfo = faceInfo;
|
||||
this.trackId = trackId;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public int getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(int gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public FaceInfo getFaceInfo() {
|
||||
return faceInfo;
|
||||
}
|
||||
|
||||
public void setFaceInfo(FaceInfo faceInfo) {
|
||||
this.faceInfo = faceInfo;
|
||||
}
|
||||
|
||||
|
||||
public int getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
public void setTrackId(int trackId) {
|
||||
this.trackId = trackId;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,292 +0,0 @@
|
|||
package com.guwan.backend.face.face;
|
||||
|
||||
import com.arcsoft.face.*;
|
||||
import com.arcsoft.face.enums.DetectMode;
|
||||
import com.arcsoft.face.enums.ErrorInfo;
|
||||
import com.arcsoft.face.enums.ExtractType;
|
||||
import com.arcsoft.face.toolkit.ImageFactory;
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
import com.guwan.backend.face.config.ArcFaceAutoConfiguration;
|
||||
import com.guwan.backend.face.factory.FaceEngineFactory;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Slf4j
|
||||
public final class FaceRecognize {
|
||||
|
||||
/**
|
||||
* VIDEO模式人脸检测引擎,用于预览帧人脸追踪
|
||||
*/
|
||||
private static FaceEngine ftEngine;
|
||||
|
||||
/**
|
||||
* 人脸注册引擎
|
||||
*/
|
||||
private static FaceEngine regEngine;
|
||||
|
||||
/**
|
||||
* 用于人脸识别的引擎池
|
||||
*/
|
||||
private static GenericObjectPool<FaceEngine> frEnginePool;
|
||||
|
||||
|
||||
private static volatile ConcurrentHashMap<Integer, FaceResult> faceResultRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
private static ExecutorService frService = Executors.newFixedThreadPool(20);
|
||||
|
||||
public static ConcurrentHashMap<String, byte[]> faceFeatureRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化引擎
|
||||
*/
|
||||
public void initEngine(String appId, String sdkKey, String activeKey, String activeFile) {
|
||||
|
||||
//引擎配置
|
||||
ftEngine = new FaceEngine(ArcFaceAutoConfiguration.CACHE_LIB_FOLDER);
|
||||
int activeCode;
|
||||
if (StringUtils.isNotEmpty(activeFile)) {
|
||||
activeCode = ftEngine.activeOffline(activeFile);
|
||||
} else {
|
||||
activeCode = ftEngine.activeOnline(appId, sdkKey, activeKey);
|
||||
}
|
||||
|
||||
EngineConfiguration ftEngineCfg = new EngineConfiguration();
|
||||
ftEngineCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_VIDEO);
|
||||
ftEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceDetect(true).build());
|
||||
int ftInitCode = ftEngine.init(ftEngineCfg);
|
||||
|
||||
//引擎配置
|
||||
regEngine = new FaceEngine(ArcFaceAutoConfiguration.CACHE_LIB_FOLDER);
|
||||
|
||||
EngineConfiguration regEngineCfg = new EngineConfiguration();
|
||||
regEngineCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
|
||||
regEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceDetect(true).supportFaceRecognition(true).build());
|
||||
int regInitCode = regEngine.init(regEngineCfg);
|
||||
|
||||
|
||||
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
|
||||
poolConfig.setMaxIdle(5);
|
||||
poolConfig.setMaxTotal(5);
|
||||
poolConfig.setMinIdle(5);
|
||||
poolConfig.setLifo(false);
|
||||
EngineConfiguration frEngineCfg = new EngineConfiguration();
|
||||
frEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceRecognition(true).build());
|
||||
frEnginePool = new GenericObjectPool(new FaceEngineFactory(appId, sdkKey, activeKey,activeFile, frEngineCfg), poolConfig);//底层库算法对象池
|
||||
|
||||
|
||||
if (!(activeCode == ErrorInfo.MOK.getValue() || activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue())) {
|
||||
log.error("activeCode: " + activeCode);
|
||||
throw new RuntimeException("activeCode: " + activeCode);
|
||||
}
|
||||
if (ftInitCode != ErrorInfo.MOK.getValue()) {
|
||||
log.error("ftInitEngine: " + ftInitCode);
|
||||
throw new RuntimeException("ftInitEngine: " + ftInitCode);
|
||||
}
|
||||
|
||||
if (regInitCode != ErrorInfo.MOK.getValue()) {
|
||||
log.error("regInitEngine: " + regInitCode);
|
||||
throw new RuntimeException("regInitEngine: " + regInitCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void registerFace(String imagePath) {
|
||||
|
||||
log.info("正在注册人脸");
|
||||
|
||||
int count = 0;
|
||||
if (regEngine != null) {
|
||||
File file = new File(imagePath);
|
||||
File[] files = file.listFiles();
|
||||
|
||||
for (File file1 : files) {
|
||||
ImageInfo imageInfo = ImageFactory.getRGBData(file1);
|
||||
if (imageInfo != null) {
|
||||
List<FaceInfo> faceInfoList = new ArrayList<>();
|
||||
int code = regEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),
|
||||
imageInfo.getImageFormat(), faceInfoList);
|
||||
|
||||
if (code == 0 && faceInfoList.size() > 0) {
|
||||
FaceFeature faceFeature = new FaceFeature();
|
||||
int resCode = regEngine.extractFaceFeature(imageInfo, faceInfoList.get(0), ExtractType.REGISTER, 0, faceFeature);
|
||||
if (resCode == 0) {
|
||||
int lastIndexOf = file1.getName().lastIndexOf(".");
|
||||
String name = file1.getName().substring(0, file1.getName().length() - lastIndexOf - 1);
|
||||
faceFeatureRegistry.put(name, faceFeature.getFeatureData());
|
||||
log.info("成功注册人脸:" + name);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("人脸注册完成,共注册:" + count + "张人脸");
|
||||
} else {
|
||||
throw new RuntimeException("注册失败,引擎未初始化或初始化失败");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void registerFace(Map<String, byte[]> face) {
|
||||
face.forEach((k, v) -> {
|
||||
faceFeatureRegistry.put(k, v.clone());
|
||||
});
|
||||
}
|
||||
|
||||
public static void removeFace(String name) {
|
||||
faceFeatureRegistry.remove(name);
|
||||
}
|
||||
|
||||
public static void clearFace() {
|
||||
faceFeatureRegistry.clear();
|
||||
}
|
||||
|
||||
public static FaceResult getFaceResult(FaceInfo faceInfo, ImageInfo imageInfo) {
|
||||
FaceResult faceResult = faceResultRegistry.get(faceInfo.getFaceId());
|
||||
if (faceResult == null) {
|
||||
faceResult = new FaceResult();
|
||||
faceResultRegistry.put(faceInfo.getFaceId(), faceResult);
|
||||
frService.submit(new FaceInfoRunnable(faceInfo, imageInfo, faceResult));
|
||||
} else if (faceResult.isFlag()) {
|
||||
return faceResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<FacePreviewInfo> detectFaces(ImageInfo imageInfo) {
|
||||
if (ftEngine != null) {
|
||||
List<FaceInfo> faceInfoList = new ArrayList<>();
|
||||
int code = ftEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),
|
||||
imageInfo.getImageFormat(), faceInfoList);
|
||||
|
||||
List<FacePreviewInfo> previewInfoList = new LinkedList<>();
|
||||
for (FaceInfo faceInfo : faceInfoList) {
|
||||
FacePreviewInfo facePreviewInfo = new FacePreviewInfo();
|
||||
facePreviewInfo.setFaceInfo(faceInfo);
|
||||
previewInfoList.add(facePreviewInfo);
|
||||
}
|
||||
|
||||
clearFaceResultRegistry(faceInfoList);
|
||||
return previewInfoList;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private long lastClearTime = System.currentTimeMillis();
|
||||
|
||||
//清理过时的人脸
|
||||
private void clearFaceResultRegistry(List<FaceInfo> faceInfoList) {
|
||||
if (System.currentTimeMillis() - lastClearTime > 5000) {
|
||||
Iterator<Integer> iterator = faceResultRegistry.keySet().iterator();
|
||||
for (; iterator.hasNext(); ) {
|
||||
Integer next = iterator.next();
|
||||
boolean flag = false;
|
||||
for (FaceInfo faceInfo : faceInfoList) {
|
||||
if (next.equals(faceInfo.getFaceId())) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
}
|
||||
lastClearTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class FaceResult {
|
||||
private boolean flag = false;
|
||||
private String name;
|
||||
private float score;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public class FacePreviewInfo {
|
||||
|
||||
private FaceInfo faceInfo;
|
||||
private int age;
|
||||
private boolean liveness;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class FaceInfoRunnable implements Runnable {
|
||||
private FaceInfo faceInfo;
|
||||
private ImageInfo imageInfo;
|
||||
private FaceResult faceResult;
|
||||
|
||||
public FaceInfoRunnable(FaceInfo faceInfo, ImageInfo imageInfo, FaceResult faceResult) {
|
||||
this.faceInfo = faceInfo;
|
||||
this.imageInfo = imageInfo;
|
||||
this.faceResult = faceResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FaceEngine frEngine = null;
|
||||
try {
|
||||
frEngine = frEnginePool.borrowObject();
|
||||
if (frEngine != null) {
|
||||
FaceFeature faceFeature = new FaceFeature();
|
||||
int resCode = frEngine.extractFaceFeature(imageInfo, faceInfo, ExtractType.RECOGNIZE, 0, faceFeature);
|
||||
if (resCode == 0) {
|
||||
|
||||
float score = 0.0F;
|
||||
Iterator<Map.Entry<String, byte[]>> iterator = faceFeatureRegistry.entrySet().iterator();
|
||||
for (; iterator.hasNext(); ) {
|
||||
Map.Entry<String, byte[]> next = iterator.next();
|
||||
FaceFeature faceFeatureTarget = new FaceFeature();
|
||||
faceFeatureTarget.setFeatureData(next.getValue());
|
||||
|
||||
FaceSimilar faceSimilar = new FaceSimilar();
|
||||
frEngine.compareFaceFeature(faceFeatureTarget, faceFeature, faceSimilar);
|
||||
if (faceSimilar.getScore() > score) {
|
||||
score = faceSimilar.getScore();
|
||||
faceResult.setName(next.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("相似度:" + score);
|
||||
if (score >= 0.8f) {
|
||||
faceResult.setScore(score);
|
||||
faceResult.setFlag(true);
|
||||
faceResultRegistry.put(faceInfo.getFaceId(), faceResult);
|
||||
} else {
|
||||
faceResultRegistry.remove(faceInfo.getFaceId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
} finally {
|
||||
if (frEngine != null) {
|
||||
frEnginePool.returnObject(frEngine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package com.guwan.backend.face.factory;
|
||||
|
||||
import com.arcsoft.face.EngineConfiguration;
|
||||
import com.arcsoft.face.FaceEngine;
|
||||
import com.arcsoft.face.enums.ErrorInfo;
|
||||
import com.guwan.backend.face.config.ArcFaceAutoConfiguration;
|
||||
import com.guwan.backend.face.enums.ErrorCodeEnum;
|
||||
import com.guwan.backend.face.rpc.BusinessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
|
||||
@Slf4j
|
||||
public class FaceEngineFactory extends BasePooledObjectFactory<FaceEngine> {
|
||||
|
||||
private String appId;
|
||||
private String sdkKey;
|
||||
private String activeKey;
|
||||
private String activeFile;
|
||||
private EngineConfiguration engineConfiguration;
|
||||
|
||||
|
||||
public FaceEngineFactory(String appId, String sdkKey, String activeKey, String activeFile, EngineConfiguration engineConfiguration) {
|
||||
this.appId = appId;
|
||||
this.sdkKey = sdkKey;
|
||||
this.activeKey = activeKey;
|
||||
this.activeFile=activeFile;
|
||||
this.engineConfiguration = engineConfiguration;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FaceEngine create() {
|
||||
FaceEngine faceEngine = new FaceEngine(ArcFaceAutoConfiguration.CACHE_LIB_FOLDER);
|
||||
/* FaceEngine faceEngine = new FaceEngine("/softTemp/jar/82K111TPM121XK4H.dat");*/
|
||||
// FaceEngine faceEngine = new FaceEngine("/home/huangyifang/gb/咸阳师范/ArcSoft_ArcFacePro_linux_java_V4.1/libs/LINUX64/");
|
||||
int activeCode;
|
||||
if (StringUtils.isNotEmpty(activeFile)) {
|
||||
activeCode = faceEngine.activeOffline(activeFile);
|
||||
} else {
|
||||
activeCode = faceEngine.activeOnline(appId, sdkKey, activeKey);
|
||||
}
|
||||
log.debug("引擎激活errorCode:" + activeCode);
|
||||
if (activeCode != ErrorInfo.MOK.getValue() && activeCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
|
||||
log.error("引擎激活失败" + activeCode);
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "引擎激活失败" + activeCode);
|
||||
}
|
||||
int initCode = faceEngine.init(engineConfiguration);
|
||||
if (initCode != ErrorInfo.MOK.getValue()) {
|
||||
log.error("引擎初始化失败" + initCode);
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "引擎初始化失败" + initCode);
|
||||
}
|
||||
log.debug("初始化引擎errorCode:" + initCode);
|
||||
return faceEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<FaceEngine> wrap(FaceEngine faceEngine) {
|
||||
return new DefaultPooledObject<>(faceEngine);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroyObject(PooledObject<FaceEngine> p) throws Exception {
|
||||
FaceEngine faceEngine = p.getObject();
|
||||
int result = faceEngine.unInit();
|
||||
super.destroyObject(p);
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package com.guwan.backend.face.rpc;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: st7251
|
||||
* @Date: 2018/11/23 14:18
|
||||
*/
|
||||
@Data
|
||||
public class BusinessException extends RuntimeException {
|
||||
private ErrorCode errorCode;
|
||||
private String msg;
|
||||
private String msgCN;
|
||||
|
||||
public BusinessException(Response response) {
|
||||
this.errorCode = new ErrorCode() {
|
||||
@Override
|
||||
public Integer getCode() {
|
||||
return response.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDesc() {
|
||||
return response.getMsg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescCN() {
|
||||
return response.getMsg();
|
||||
}
|
||||
};
|
||||
this.msg=response.getMsg();
|
||||
this.msgCN=response.getMsg();
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode) {
|
||||
super(errorCode.getDesc());
|
||||
this.errorCode = errorCode;
|
||||
this.msg= errorCode.getDesc();
|
||||
this.msgCN=errorCode.getDescCN();
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode, String msg) {
|
||||
super(errorCode.getDesc());
|
||||
this.errorCode = errorCode;
|
||||
this.msg = msg;
|
||||
this.msgCN=msg;
|
||||
}
|
||||
|
||||
public BusinessException(Throwable cause, ErrorCode errorCode) {
|
||||
super(cause);
|
||||
this.errorCode = errorCode;
|
||||
this.msg= errorCode.getDesc();
|
||||
this.msgCN=errorCode.getDescCN();
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(Throwable cause, ErrorCode errorCode, String msg) {
|
||||
super(cause);
|
||||
this.errorCode = errorCode;
|
||||
this.msg = msg;
|
||||
this.msgCN=msg;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.guwan.backend.face.rpc;
|
||||
|
||||
public interface ErrorCode {
|
||||
|
||||
|
||||
Integer getCode();
|
||||
|
||||
String getDesc();
|
||||
|
||||
String getDescCN();
|
||||
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
//package com.guwan.backend.face.rpc;
|
||||
//
|
||||
//
|
||||
//import com.guwan.backend.face.enums.ErrorCodeEnum;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
//import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
//
|
||||
//@RestControllerAdvice
|
||||
//@Slf4j
|
||||
//public class GlobalExceptionHandler{
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 自定义异常
|
||||
// */
|
||||
// @ExceptionHandler(BusinessException.class)
|
||||
// public Response businessException(BusinessException e) {
|
||||
// log.error(e.getMessage(), e);
|
||||
// Response response = new Response();
|
||||
// response.setCode(e.getErrorCode().getCode());
|
||||
// response.setMsg(e.getMsgCN());
|
||||
// return response;
|
||||
// }
|
||||
//
|
||||
// @ExceptionHandler(IllegalArgumentException.class)
|
||||
// public Response handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
// log.error(e.getMessage(), e);
|
||||
// Response response = new Response();
|
||||
// response.setCode(ErrorCodeEnum.PARAM_ERROR.getCode());
|
||||
// response.setMsg(e.getMessage());
|
||||
// return response;
|
||||
// }
|
||||
//
|
||||
// @ExceptionHandler(Exception.class)
|
||||
// public Response handleException(Exception e) {
|
||||
// log.error(e.getMessage(), e);
|
||||
// Response response = new Response();
|
||||
// response.setCode(ErrorCodeEnum.SYSTEM_ERROR.getCode());
|
||||
// response.setMsg(ErrorCodeEnum.SYSTEM_ERROR.getDescCN());
|
||||
// return response;
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//// @ExceptionHandler(InternalServerErrorException.class)
|
||||
//// @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
//// public ResponseEntity<String> handleInternalServerErrorException(InternalServerErrorException ex) {
|
||||
//// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
|
||||
//// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//}
|
|
@ -1,39 +0,0 @@
|
|||
package com.guwan.backend.face.rpc;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Response<T> {
|
||||
|
||||
private int code = -1;
|
||||
private String msg = "success";
|
||||
private T data;
|
||||
|
||||
public static <T> Response<T> newSuccessResponse(T data) {
|
||||
return newResponse(data, 0, "success");
|
||||
}
|
||||
|
||||
public static <T> Response<T> newFailedResponse(Integer code, String message) {
|
||||
return newResponse(null, code, message);
|
||||
}
|
||||
|
||||
public static <T> Response<T> newFailedResponse(ErrorCode ErrorCode) {
|
||||
return newResponse(null, ErrorCode.getCode(), ErrorCode.getDesc());
|
||||
}
|
||||
|
||||
public static <T> Response<T> newFailedResponse(ErrorCode ErrorCode, String message) {
|
||||
return newResponse(null, ErrorCode.getCode(), message);
|
||||
}
|
||||
|
||||
public static <T> Response<T> newResponse(T data, Integer code, String message) {
|
||||
Response<T> response = new Response<T>();
|
||||
response.setCode(code);
|
||||
response.setMsg(message);
|
||||
if (data != null && data instanceof String && "".equals(data)) {
|
||||
response.setData(null);
|
||||
} else {
|
||||
response.setData(data);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -1,483 +0,0 @@
|
|||
//package com.guwan.backend.face.rtsp;
|
||||
//
|
||||
//import cn.hutool.core.collection.CollectionUtil;
|
||||
//import cn.hutool.json.JSONObject;
|
||||
//import com.arcsoft.face.FaceInfo;
|
||||
//import com.arcsoft.face.enums.ExtractType;
|
||||
//import com.arcsoft.face.toolkit.ImageFactory;
|
||||
//import com.arcsoft.face.toolkit.ImageInfo;
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
//import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
//import io.minio.MinioClient;
|
||||
//import io.minio.PutObjectOptions;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import net.shapelight.common.config.GlobalValue;
|
||||
//import net.shapelight.common.config.MinioConfig;
|
||||
//import net.shapelight.common.utils.Constant;
|
||||
//import net.shapelight.common.utils.UUIDUtil;
|
||||
//import net.shapelight.modules.face.entity.UserCompareInfo;
|
||||
//import net.shapelight.modules.face.service.FaceEngineService;
|
||||
//import net.shapelight.modules.face.util.UserInfo;
|
||||
//import net.shapelight.modules.face.util.UserRamGroup;
|
||||
//import net.shapelight.modules.feignClient.CxFeignClient;
|
||||
//import net.shapelight.modules.iCq.controller.video.vo.FaceVideoVo;
|
||||
//import net.shapelight.modules.ten.dao.TenCellDao;
|
||||
//import net.shapelight.modules.ten.entity.TenCellEntity;
|
||||
//import net.shapelight.modules.ten.entity.TenPersonEntity;
|
||||
//import net.shapelight.modules.ten.service.TenPersonService;
|
||||
//import net.shapelight.modules.ten.service.impl.TenPersonServiceImpl;
|
||||
//import org.bytedeco.javacpp.avutil;
|
||||
//import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
//import org.bytedeco.javacv.Frame;
|
||||
//import org.bytedeco.javacv.FrameGrabber;
|
||||
//import org.bytedeco.javacv.Java2DFrameConverter;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.beans.factory.annotation.Value;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//import org.springframework.web.multipart.MultipartFile;
|
||||
//
|
||||
//import javax.imageio.ImageIO;
|
||||
//import java.awt.image.BufferedImage;
|
||||
//import java.io.ByteArrayInputStream;
|
||||
//import java.io.ByteArrayOutputStream;
|
||||
//import java.io.IOException;
|
||||
//import java.io.InputStream;
|
||||
//import java.text.SimpleDateFormat;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//import java.util.concurrent.ConcurrentHashMap;
|
||||
//
|
||||
//import static org.bytedeco.javacpp.avutil.AV_LOG_ERROR;
|
||||
//
|
||||
//@Component
|
||||
//@Slf4j
|
||||
//public class RtspFrameGrabber {
|
||||
//
|
||||
// @Autowired
|
||||
// private FaceEngineService faceEngineService;
|
||||
//
|
||||
// @Autowired
|
||||
// private TenPersonService personService;
|
||||
//
|
||||
// @Autowired
|
||||
// private GlobalValue globalValue;
|
||||
//
|
||||
// @Autowired
|
||||
// private CxFeignClient feignClient;
|
||||
// /**
|
||||
// * 用于记录人脸识别相关状态
|
||||
// */
|
||||
// public ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>();
|
||||
// /**
|
||||
// * rtsp视频流url
|
||||
// */
|
||||
// @Value("${global.Url.rtspUrl}")
|
||||
// private String rtspUrl;
|
||||
// /**
|
||||
// * 帧抓取器
|
||||
// */
|
||||
// private FFmpegFrameGrabber grabber;
|
||||
// /**
|
||||
// * 视频帧率
|
||||
// */
|
||||
// private int frameRate = 25;
|
||||
// /**
|
||||
// * 视频码率
|
||||
// */
|
||||
//// private int bitRate = 2000000;
|
||||
// private int bitRate = 128000;
|
||||
// /**
|
||||
// * 视频宽度
|
||||
// */
|
||||
// private int frameWidth = 480;
|
||||
// /**
|
||||
// * 视频高度
|
||||
// */
|
||||
// private int frameHeight = 270;
|
||||
// @Autowired
|
||||
// private TenPersonServiceImpl tenPersonService;
|
||||
//
|
||||
// @Autowired
|
||||
// private TenCellDao tenCellDao;
|
||||
//
|
||||
// @Autowired
|
||||
// private MinioConfig minioConfig;
|
||||
// @Autowired
|
||||
// private MinioClient minioClient;
|
||||
//
|
||||
//
|
||||
// private void createGrabber() {
|
||||
// try {
|
||||
//
|
||||
// grabber = FFmpegFrameGrabber.createDefault(rtspUrl);
|
||||
// grabber.setOption("rtsp_transport", "tcp");
|
||||
//
|
||||
//// grabber.setOption("reconnect", "1");
|
||||
//// grabber.setOption("reconnect_at_eof", "1");
|
||||
//// grabber.setOption("reconnect_streamed", "1");
|
||||
//// grabber.setOption("reconnect_delay_max", "2");
|
||||
//// grabber.setOption("preset", "veryfast");
|
||||
//// grabber.setOption("probesize", "192");
|
||||
//// grabber.setOption("tune", "zerolatency");
|
||||
//// grabber.setFrameRate(30.0);
|
||||
//// grabber.setOption("buffer_size", "" + this.bufferSize);
|
||||
//// grabber.setOption("max_delay", "500000");
|
||||
//// grabber.setOption("stimeout", String.valueOf(20000));
|
||||
//// grabber.setOption("loglevel", "quiet");
|
||||
//
|
||||
//
|
||||
//// grabber.setOption("appkey", "****");//海康视频 appkey
|
||||
//// grabber.setOption("secret", byte2Base64);//海康视频 secret
|
||||
//// grabber.setOption("port", "446");//默认443
|
||||
//// grabber.setOption("enableHTTPS", "1"); //是否启用HTTPS协议,这里总是填1
|
||||
//// grabber.setOption("rtsp_flags", "prefer_tcp");
|
||||
// grabber.setOption("stimeout", "5000000");//5秒
|
||||
//
|
||||
//
|
||||
// //设置帧率
|
||||
// grabber.setFrameRate(frameRate);
|
||||
// //设置获取的视频宽度
|
||||
// grabber.setImageWidth(frameWidth);
|
||||
// //设置获取的视频高度
|
||||
// grabber.setImageHeight(frameHeight);
|
||||
// //设置视频bit率
|
||||
// grabber.setVideoBitrate(bitRate);
|
||||
// grabber.start();
|
||||
// } catch (Exception e) {
|
||||
// log.error(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// public List<FaceVideoVo> startGrabber(String cellId) {
|
||||
//
|
||||
// List<FaceVideoVo> faceVideoVos = new ArrayList<>();
|
||||
//
|
||||
// avutil.av_log_set_level(AV_LOG_ERROR);
|
||||
// Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();
|
||||
// if (grabber == null) {
|
||||
// //log.info("连接rtsp:" + rstp + ",开始创建grabber");
|
||||
// createGrabber();
|
||||
// }
|
||||
// try {
|
||||
// Frame frame = grabber.grabImage();
|
||||
// if (frame != null) {
|
||||
//// logger.info("处理帧.............................................");
|
||||
// BufferedImage bi = java2DFrameConverter.getBufferedImage(frame);
|
||||
//
|
||||
// byte[] bytes = imageToBytes(bi, "jpg");
|
||||
//
|
||||
// /* InputStream frameInputStream = new ByteArrayInputStream(bytes);
|
||||
// String frameFileName = "temp/" + "frame_" + UUIDUtil.uuid()
|
||||
// + ".jpg";
|
||||
//
|
||||
// PutObjectOptions framePutObjectOptions = new PutObjectOptions(bytes.length, -1);
|
||||
// framePutObjectOptions.setContentType("image/jpeg");
|
||||
//
|
||||
// minioClient.putObject(
|
||||
// minioConfig.getBucketName(), frameFileName, frameInputStream, framePutObjectOptions);
|
||||
//
|
||||
// System.out.println("文件名 = " + globalValue.getMinioEndpoint() + "/" +
|
||||
// globalValue.getMinioBucketName() + "/" + frameFileName);*/
|
||||
// /* // 读取图片
|
||||
// BufferedImage image = ImageIO.read(new File("C:\\Users\\zhangbo\\OneDrive\\图片\\Camera Roll\\1寸相片.jpg"));
|
||||
//
|
||||
// // 创建字节输出流
|
||||
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
//
|
||||
// // 使用ImageIO将BufferedImage编码为byte[]
|
||||
// ImageIO.write(image, "jpg", baos);
|
||||
//
|
||||
// // 转换为byte数组
|
||||
// byte[] imageBytes = baos.toByteArray();*/
|
||||
// faceVideoVos = imageRecognition(bytes, cellId);
|
||||
// System.out.println("faceVideoVos = " + faceVideoVos);
|
||||
//
|
||||
// } else {
|
||||
// log.error("解码失败");
|
||||
// if (grabber != null) {
|
||||
// try {
|
||||
// grabber.stop();
|
||||
// } catch (FrameGrabber.Exception ex) {
|
||||
// log.error("grabber stop exception: " + ex.getMessage());
|
||||
// } finally {
|
||||
// grabber = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error(e.getMessage());
|
||||
//
|
||||
// if (grabber != null) {
|
||||
// try {
|
||||
// grabber.stop();
|
||||
// } catch (FrameGrabber.Exception ex) {
|
||||
// log.error("grabber stop exception: " + ex.getMessage());
|
||||
// } finally {
|
||||
// grabber = null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// } finally {
|
||||
// grabber = null;
|
||||
// }
|
||||
//// try {
|
||||
//// Thread.sleep(100);
|
||||
//// } catch (InterruptedException e) {
|
||||
//// logger.error(e.getMessage());
|
||||
//// }
|
||||
//
|
||||
// return faceVideoVos;
|
||||
// }
|
||||
//
|
||||
// public List<FaceVideoVo> recognition(MultipartFile file,String cellId) {
|
||||
// try {
|
||||
// byte[] bytes = file.getBytes();
|
||||
// return imageRecognitionNew(bytes, cellId);
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private List<FaceVideoVo> imageRecognition(byte[] bytes,String cellId) {
|
||||
// List<FaceVideoVo> temp = new ArrayList<>();
|
||||
// if (bytes != null && bytes.length > 0) {
|
||||
// ImageInfo imageInfo = ImageFactory.getRGBData(bytes);
|
||||
// List<FaceInfo> faceInfoList = faceEngineService.detectFaces(imageInfo);
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// if (CollectionUtil.isNotEmpty(faceInfoList)) {
|
||||
// faceInfoList.forEach(faceInfo -> {
|
||||
// /* FaceRecognitionResDTO faceRecognitionResDTO = new FaceRecognitionResDTO();
|
||||
// faceRecognitionResDTO.setRect(faceInfo.getRect());*/
|
||||
//
|
||||
// byte[] featureBytes = faceEngineService.extractFaceFeature(imageInfo,
|
||||
// faceInfo, ExtractType.REGISTER);
|
||||
// if (featureBytes != null) {
|
||||
// //底库
|
||||
// List<UserInfo> userInfoList = UserRamGroup.getUserList(cellId);
|
||||
// //人脸对比 这里长度永远为1
|
||||
// List<UserCompareInfo> userCompareInfoList = faceEngineService
|
||||
// .faceRecognition(featureBytes, userInfoList, Float.parseFloat(globalValue.getRecFaceThd()));
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// FaceVideoVo faceVideoVo = new FaceVideoVo();
|
||||
//
|
||||
// //System.out.println("faceInfo = " + faceInfo.getRect());
|
||||
//
|
||||
// faceVideoVo.setRect(faceInfo.getRect());
|
||||
//
|
||||
// if (faceInfo.getFace3DAngle().getYaw() > 20 || faceInfo.getFace3DAngle().getYaw() < -20) {
|
||||
// faceVideoVo.setIsHeadOnView(0);
|
||||
// }else if (faceInfo.getFace3DAngle().getPitch() > 20 || faceInfo.getFace3DAngle().getPitch() < -20) {
|
||||
// faceVideoVo.setIsHeadOnView(0);
|
||||
// }else {
|
||||
// faceVideoVo.setIsHeadOnView(1);
|
||||
// }
|
||||
//
|
||||
// faceVideoVo.setPersonId(userCompareInfoList.get(0).getFaceId()).setName(userCompareInfoList.get(0).getName());
|
||||
//
|
||||
//
|
||||
//
|
||||
// InputStream frameInputStream = new ByteArrayInputStream(bytes);
|
||||
// String frameFileName = "temp/" + "frame_" + UUIDUtil.uuid()
|
||||
// + ".jpg";
|
||||
//
|
||||
// PutObjectOptions framePutObjectOptions = new PutObjectOptions(bytes.length, -1);
|
||||
// framePutObjectOptions.setContentType("image/jpeg");
|
||||
//
|
||||
// try {
|
||||
// frameInputStream.close();
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// minioClient.putObject(
|
||||
// minioConfig.getBucketName(), frameFileName, frameInputStream, framePutObjectOptions);
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//
|
||||
// System.out.println("文件名 = " + globalValue.getMinioEndpoint() + "/" +
|
||||
// globalValue.getMinioBucketName() + "/" + frameFileName);
|
||||
//
|
||||
//
|
||||
//
|
||||
// faceVideoVo.setImageUrl(frameFileName);
|
||||
//
|
||||
// temp.add(faceVideoVo);
|
||||
//
|
||||
//
|
||||
//
|
||||
// TenPersonEntity tenPerson = personService.getOne(new LambdaQueryWrapper<TenPersonEntity>()
|
||||
// .eq(TenPersonEntity::getPersonId, userCompareInfoList.get(0).getFaceId()));
|
||||
// Map<Integer, String> personnelTypeMap = new HashMap<>();
|
||||
// personnelTypeMap.put(Constant.PERSON_TYPE_OWNER, "2");//内部人员
|
||||
// personnelTypeMap.put(Constant.PERSON_TYPE_MEMBER, "1");//承包商
|
||||
// personnelTypeMap.put(Constant.PERSON_TYPE_TENANT, "3");//长期供应商
|
||||
// personnelTypeMap.put(Constant.PERSON_TYPE_GUEST, "4");//访客
|
||||
//
|
||||
// //
|
||||
// Map<String, Object> params = new HashMap<>();
|
||||
// params.put("pmWatchVideoRecordId", "");
|
||||
// params.put("orgId", tenPerson.getOrgId());
|
||||
// params.put("orgName", tenCellDao.selectOne(new QueryWrapper<TenCellEntity>().eq("cell_id", tenPerson.getCellId())).getName());
|
||||
// params.put("personnelName", tenPerson.getName());
|
||||
// params.put("personnelId", tenPerson.getOpenId());
|
||||
// params.put("personnelCardId", tenPerson.getIdCard());
|
||||
// params.put("personnelType", personnelTypeMap.get(tenPerson.getPersonType()));
|
||||
// params.put("dictSex", tenPerson.getGender() == 0 ? "男" : "女");
|
||||
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
|
||||
// params.put("watchVideoTime", sdf.format(System.currentTimeMillis()));
|
||||
//
|
||||
// if(tenPerson.getIsWatchSafeVideo() != 1){
|
||||
// tenPerson.setIsWatchSafeVideo(faceVideoVo.getIsHeadOnView() == 0 ? 2 : 1);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// //tenPerson.setIsWatchSafeVideo();
|
||||
//
|
||||
// tenPersonService.updateById(tenPerson);
|
||||
//
|
||||
// System.out.println("params = " + params);
|
||||
//
|
||||
// if(faceVideoVo.getIsHeadOnView() == 1){
|
||||
// JSONObject jsonObject = feignClient.savePmWatchVideoRecord(params);
|
||||
// if (jsonObject.getBool("success") != null && jsonObject.getBool("success")) {
|
||||
// personService.update(new LambdaUpdateWrapper<TenPersonEntity>()
|
||||
// .set(TenPersonEntity::getIsWatchSafeVideo, 1)
|
||||
// .eq(TenPersonEntity::getPersonId, userCompareInfoList.get(0).getFaceId()));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// log.error("图片不合格,未检测到人脸");
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// } else {
|
||||
// log.error("图片不合格,未检测到人脸");
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
// }
|
||||
// return temp;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private List<FaceVideoVo> imageRecognitionNew(byte[] bytes,String cellId) {
|
||||
// List<FaceVideoVo> temp = new ArrayList<>();
|
||||
// if (bytes != null && bytes.length > 0) {
|
||||
// ImageInfo imageInfo = ImageFactory.getRGBData(bytes);
|
||||
// List<FaceInfo> faceInfoList = faceEngineService.detectFaces(imageInfo);
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// if (CollectionUtil.isNotEmpty(faceInfoList)) {
|
||||
// faceInfoList.forEach(faceInfo -> {
|
||||
// /* FaceRecognitionResDTO faceRecognitionResDTO = new FaceRecognitionResDTO();
|
||||
// faceRecognitionResDTO.setRect(faceInfo.getRect());*/
|
||||
//
|
||||
// byte[] featureBytes = faceEngineService.extractFaceFeature(imageInfo,
|
||||
// faceInfo, ExtractType.REGISTER);
|
||||
// if (featureBytes != null) {
|
||||
// //底库
|
||||
// List<UserInfo> userInfoList = UserRamGroup.getUserList(cellId);
|
||||
// //人脸对比 这里长度永远为1
|
||||
// List<UserCompareInfo> userCompareInfoList = faceEngineService
|
||||
// .faceRecognition(featureBytes, userInfoList, Float.parseFloat(globalValue.getRecFaceThd()));
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// FaceVideoVo faceVideoVo = new FaceVideoVo();
|
||||
//
|
||||
// //System.out.println("faceInfo = " + faceInfo.getRect());
|
||||
//
|
||||
// faceVideoVo.setRect(faceInfo.getRect());
|
||||
//
|
||||
// if (faceInfo.getFace3DAngle().getYaw() > 40 || faceInfo.getFace3DAngle().getYaw() < -40) {
|
||||
// faceVideoVo.setIsHeadOnView(0);
|
||||
// }else if (faceInfo.getFace3DAngle().getPitch() > 40 || faceInfo.getFace3DAngle().getPitch() < -40) {
|
||||
// faceVideoVo.setIsHeadOnView(0);
|
||||
// }else {
|
||||
// faceVideoVo.setIsHeadOnView(1);
|
||||
// }
|
||||
//
|
||||
// faceVideoVo.setPersonId(userCompareInfoList.get(0).getFaceId()).setName(userCompareInfoList.get(0).getName());
|
||||
//
|
||||
//
|
||||
//
|
||||
// InputStream frameInputStream = new ByteArrayInputStream(bytes);
|
||||
// String frameFileName = "temp/" + "frame_" + UUIDUtil.uuid()
|
||||
// + ".jpg";
|
||||
//
|
||||
// PutObjectOptions framePutObjectOptions = new PutObjectOptions(bytes.length, -1);
|
||||
// framePutObjectOptions.setContentType("image/jpeg");
|
||||
//
|
||||
// try {
|
||||
// frameInputStream.close();
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// minioClient.putObject(
|
||||
// minioConfig.getBucketName(), frameFileName, frameInputStream, framePutObjectOptions);
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//
|
||||
// System.out.println("文件名 = " + globalValue.getMinioEndpoint() + "/" +
|
||||
// globalValue.getMinioBucketName() + "/" + frameFileName);
|
||||
//
|
||||
//
|
||||
//
|
||||
// faceVideoVo.setImageUrl(frameFileName);
|
||||
//
|
||||
// temp.add(faceVideoVo);
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// log.error("图片不合格,未检测到人脸");
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// } else {
|
||||
// log.error("图片不合格,未检测到人脸");
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// return temp;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 图片转字节数组
|
||||
// *
|
||||
// * @param _bi 图片数据
|
||||
// * @return 图片字节码
|
||||
// */
|
||||
// private byte[] imageToBytes(BufferedImage _bi, String _format) {
|
||||
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
// try {
|
||||
// ImageIO.write(_bi, _format, baos);
|
||||
// } catch (IOException e) {
|
||||
// log.error(e.getMessage());
|
||||
// return null;
|
||||
// }
|
||||
// return baos.toByteArray();
|
||||
// }
|
||||
//
|
||||
//}
|
|
@ -1,28 +0,0 @@
|
|||
package com.guwan.backend.face.service;
|
||||
|
||||
|
||||
import com.arcsoft.face.FaceInfo;
|
||||
import com.arcsoft.face.enums.ExtractType;
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
import com.guwan.backend.face.entity.ProcessInfo;
|
||||
import com.guwan.backend.face.entity.UserCompareInfo;
|
||||
import com.guwan.backend.face.util.UserInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface FaceEngineService {
|
||||
|
||||
List<FaceInfo> detectFaces(ImageInfo imageInfo);
|
||||
|
||||
Float compareFace(ImageInfo imageInfo1, ImageInfo imageInfo2) ;
|
||||
|
||||
byte[] extractFaceFeature(ImageInfo imageInfo, FaceInfo faceInfo, ExtractType extractType);
|
||||
|
||||
List<UserCompareInfo> faceRecognition(byte[] faceFeature, List<UserInfo> userInfoList, float passRate) ;
|
||||
|
||||
List<ProcessInfo> process(ImageInfo imageInfo, List<FaceInfo> faceInfoList);
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,354 +0,0 @@
|
|||
package com.guwan.backend.face.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.arcsoft.face.*;
|
||||
import com.arcsoft.face.enums.DetectMode;
|
||||
import com.arcsoft.face.enums.DetectOrient;
|
||||
import com.arcsoft.face.enums.ExtractType;
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.guwan.backend.face.entity.ProcessInfo;
|
||||
import com.guwan.backend.face.entity.UserCompareInfo;
|
||||
import com.guwan.backend.face.enums.ErrorCodeEnum;
|
||||
import com.guwan.backend.face.factory.FaceEngineFactory;
|
||||
import com.guwan.backend.face.rpc.BusinessException;
|
||||
import com.guwan.backend.face.service.FaceEngineService;
|
||||
import com.guwan.backend.face.util.UserInfo;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
|
||||
@Service("faceEngineService")
|
||||
@Slf4j
|
||||
public class FaceEngineServiceImpl implements FaceEngineService {
|
||||
|
||||
public final static Logger logger = LoggerFactory.getLogger(FaceEngineServiceImpl.class);
|
||||
|
||||
@Value("${config.arcface-sdk.app-id}")
|
||||
public String appId;
|
||||
|
||||
@Value("${config.arcface-sdk.sdk-key}")
|
||||
public String sdkKey;
|
||||
|
||||
@Value("${config.arcface-sdk.active-key}")
|
||||
public String activeKey;
|
||||
|
||||
@Value("${config.arcface-sdk.active-file}")
|
||||
public String activeFile;
|
||||
|
||||
@Value("${config.arcface-sdk.detect-pool-size}")
|
||||
public Integer detectPooSize;
|
||||
|
||||
@Value("${config.arcface-sdk.compare-pool-size}")
|
||||
public Integer comparePooSize;
|
||||
|
||||
private ExecutorService compareExecutorService;
|
||||
|
||||
//通用人脸识别引擎池
|
||||
private GenericObjectPool<FaceEngine> faceEngineGeneralPool;
|
||||
|
||||
//人脸比对引擎池
|
||||
private GenericObjectPool<FaceEngine> faceEngineComparePool;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
||||
|
||||
GenericObjectPoolConfig detectPoolConfig = new GenericObjectPoolConfig();
|
||||
detectPoolConfig.setMaxIdle(detectPooSize);
|
||||
detectPoolConfig.setMaxTotal(detectPooSize);
|
||||
detectPoolConfig.setMinIdle(detectPooSize);
|
||||
detectPoolConfig.setLifo(false);
|
||||
EngineConfiguration detectCfg = new EngineConfiguration();
|
||||
FunctionConfiguration detectFunctionCfg = new FunctionConfiguration();
|
||||
detectFunctionCfg.setSupportFaceDetect(true);//开启人脸检测功能
|
||||
detectFunctionCfg.setSupportFaceRecognition(true);//开启人脸识别功能
|
||||
detectFunctionCfg.setSupportAge(true);//开启年龄检测功能
|
||||
detectFunctionCfg.setSupportGender(true);//开启性别检测功能
|
||||
detectFunctionCfg.setSupportLiveness(true);//开启活体检测功能
|
||||
detectCfg.setFunctionConfiguration(detectFunctionCfg);
|
||||
detectCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);//图片检测模式,如果是连续帧的视频流图片,那么改成VIDEO模式
|
||||
detectCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
|
||||
faceEngineGeneralPool = new GenericObjectPool(new FaceEngineFactory(appId, sdkKey, activeKey, activeFile, detectCfg), detectPoolConfig);//底层库算法对象池
|
||||
|
||||
|
||||
//初始化特征比较线程池
|
||||
GenericObjectPoolConfig comparePoolConfig = new GenericObjectPoolConfig();
|
||||
comparePoolConfig.setMaxIdle(comparePooSize);
|
||||
comparePoolConfig.setMaxTotal(comparePooSize);
|
||||
comparePoolConfig.setMinIdle(comparePooSize);
|
||||
comparePoolConfig.setLifo(false);
|
||||
EngineConfiguration compareCfg = new EngineConfiguration();
|
||||
FunctionConfiguration compareFunctionCfg = new FunctionConfiguration();
|
||||
compareFunctionCfg.setSupportFaceRecognition(true);//开启人脸识别功能
|
||||
compareCfg.setFunctionConfiguration(compareFunctionCfg);
|
||||
compareCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);//图片检测模式,如果是连续帧的视频流图片,那么改成VIDEO模式
|
||||
compareCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
|
||||
faceEngineComparePool = new GenericObjectPool(new FaceEngineFactory(appId, sdkKey, activeKey, activeFile, compareCfg), comparePoolConfig);//底层库算法对象池
|
||||
compareExecutorService = Executors.newFixedThreadPool(comparePooSize);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<FaceInfo> detectFaces(ImageInfo imageInfo) {
|
||||
|
||||
FaceEngine faceEngine = null;
|
||||
try {
|
||||
faceEngine = faceEngineGeneralPool.borrowObject();
|
||||
if (faceEngine == null) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
|
||||
}
|
||||
|
||||
//人脸检测得到人脸列表
|
||||
List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
|
||||
//人脸检测
|
||||
int errorCode = faceEngine.detectFaces(imageInfo.getImageData(),
|
||||
imageInfo.getWidth(), imageInfo.getHeight(),
|
||||
imageInfo.getImageFormat(), faceInfoList);
|
||||
if (errorCode == 0) {
|
||||
return faceInfoList;
|
||||
} else {
|
||||
log.error("人脸检测失败,errorCode:" + errorCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
} finally {
|
||||
if (faceEngine != null) {
|
||||
//释放引擎对象
|
||||
faceEngineGeneralPool.returnObject(faceEngine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float compareFace(ImageInfo imageInfo1, ImageInfo imageInfo2) {
|
||||
|
||||
List<FaceInfo> faceInfoList1 = detectFaces(imageInfo1);
|
||||
List<FaceInfo> faceInfoList2 = detectFaces(imageInfo2);
|
||||
|
||||
if (CollectionUtil.isEmpty(faceInfoList1)) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "照片1未检测到人脸");
|
||||
}
|
||||
if (CollectionUtil.isEmpty(faceInfoList2)) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "照片2未检测到人脸");
|
||||
}
|
||||
|
||||
byte[] feature1 = extractFaceFeature(imageInfo1, faceInfoList1.get(0), ExtractType.REGISTER);
|
||||
byte[] feature2 = extractFaceFeature(imageInfo2, faceInfoList2.get(0), ExtractType.RECOGNIZE);
|
||||
|
||||
FaceEngine faceEngine = null;
|
||||
try {
|
||||
faceEngine = faceEngineGeneralPool.borrowObject();
|
||||
if (faceEngine == null) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
|
||||
}
|
||||
|
||||
FaceFeature faceFeature1 = new FaceFeature();
|
||||
faceFeature1.setFeatureData(feature1);
|
||||
FaceFeature faceFeature2 = new FaceFeature();
|
||||
faceFeature2.setFeatureData(feature2);
|
||||
//提取人脸特征
|
||||
FaceSimilar faceSimilar = new FaceSimilar();
|
||||
int errorCode = faceEngine.compareFaceFeature(faceFeature1, faceFeature2, faceSimilar);
|
||||
if (errorCode == 0) {
|
||||
return faceSimilar.getScore();
|
||||
} else {
|
||||
log.error("特征提取失败,errorCode:" + errorCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
} finally {
|
||||
if (faceEngine != null) {
|
||||
//释放引擎对象
|
||||
faceEngineGeneralPool.returnObject(faceEngine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸特征
|
||||
*
|
||||
* @param imageInfo
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public byte[] extractFaceFeature(ImageInfo imageInfo, FaceInfo faceInfo, ExtractType extractType) {
|
||||
|
||||
FaceEngine faceEngine = null;
|
||||
try {
|
||||
faceEngine = faceEngineGeneralPool.borrowObject();
|
||||
if (faceEngine == null) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
|
||||
}
|
||||
|
||||
FaceFeature faceFeature = new FaceFeature();
|
||||
//提取人脸特征
|
||||
int errorCode = faceEngine.extractFaceFeature(imageInfo, faceInfo, extractType, 0, faceFeature);
|
||||
if (errorCode == 0) {
|
||||
return faceFeature.getFeatureData();
|
||||
} else {
|
||||
log.error("特征提取失败,errorCode:" + errorCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
} finally {
|
||||
if (faceEngine != null) {
|
||||
//释放引擎对象
|
||||
faceEngineGeneralPool.returnObject(faceEngine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserCompareInfo> faceRecognition(byte[] faceFeature, List<UserInfo> userInfoList, float passRate) {
|
||||
List<UserCompareInfo> resultUserInfoList = Lists.newLinkedList();//识别到的人脸列表
|
||||
|
||||
FaceFeature targetFaceFeature = new FaceFeature();
|
||||
targetFaceFeature.setFeatureData(faceFeature);
|
||||
|
||||
List<List<UserInfo>> faceUserInfoPartList = Lists.partition(userInfoList, 1000);//分成1000一组,多线程处理
|
||||
CompletionService<List<UserCompareInfo>> completionService = new ExecutorCompletionService(compareExecutorService);
|
||||
for (List<UserInfo> part : faceUserInfoPartList) {
|
||||
completionService.submit(new CompareFaceTask(part, targetFaceFeature, passRate));
|
||||
}
|
||||
for (int i = 0; i < faceUserInfoPartList.size(); i++) {
|
||||
List<UserCompareInfo> faceUserInfoList = null;
|
||||
try {
|
||||
faceUserInfoList = completionService.take().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
}
|
||||
if (CollectionUtil.isNotEmpty(userInfoList)) {
|
||||
resultUserInfoList.addAll(faceUserInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
resultUserInfoList.sort((h1, h2) -> h2.getSimilar().compareTo(h1.getSimilar()));//从大到小排序
|
||||
|
||||
return resultUserInfoList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProcessInfo> process(ImageInfo imageInfo, List<FaceInfo> faceInfoList) {
|
||||
FaceEngine faceEngine = null;
|
||||
try {
|
||||
//获取引擎对象
|
||||
faceEngine = faceEngineGeneralPool.borrowObject();
|
||||
if (faceEngine == null) {
|
||||
throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
|
||||
}
|
||||
|
||||
|
||||
int errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, FunctionConfiguration.builder().supportAge(true).supportGender(true).supportLiveness(true).build());
|
||||
if (errorCode == 0) {
|
||||
List<ProcessInfo> processInfoList = Lists.newLinkedList();
|
||||
|
||||
//性别列表
|
||||
List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
|
||||
faceEngine.getGender(genderInfoList);
|
||||
|
||||
//年龄列表
|
||||
List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
|
||||
faceEngine.getAge(ageInfoList);
|
||||
//活体结果列表
|
||||
List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
|
||||
faceEngine.getLiveness(livenessInfoList);
|
||||
|
||||
|
||||
for (int i = 0; i < genderInfoList.size(); i++) {
|
||||
ProcessInfo processInfo = new ProcessInfo();
|
||||
processInfo.setGender(genderInfoList.get(i).getGender());
|
||||
processInfo.setAge(ageInfoList.get(i).getAge());
|
||||
processInfo.setLiveness(livenessInfoList.get(i).getLiveness());
|
||||
processInfoList.add(processInfo);
|
||||
}
|
||||
return processInfoList;
|
||||
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
} finally {
|
||||
if (faceEngine != null) {
|
||||
//释放引擎对象
|
||||
faceEngineGeneralPool.returnObject(faceEngine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class CompareFaceTask implements Callable<List<UserCompareInfo>> {
|
||||
|
||||
private List<UserInfo> userInfoList;
|
||||
private FaceFeature targetFaceFeature;
|
||||
private float passRate;
|
||||
|
||||
|
||||
public CompareFaceTask(List<UserInfo> userInfoList, FaceFeature targetFaceFeature, float passRate) {
|
||||
this.userInfoList = userInfoList;
|
||||
this.targetFaceFeature = targetFaceFeature;
|
||||
this.passRate = passRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserCompareInfo> call() throws Exception {
|
||||
FaceEngine faceEngine = null;
|
||||
List<UserCompareInfo> resultUserInfoList = Lists.newLinkedList();//识别到的人脸列表
|
||||
try {
|
||||
faceEngine = faceEngineComparePool.borrowObject();
|
||||
for (UserInfo userInfo : userInfoList) {
|
||||
FaceFeature sourceFaceFeature = new FaceFeature();
|
||||
sourceFaceFeature.setFeatureData(userInfo.getFaceFeature());
|
||||
FaceSimilar faceSimilar = new FaceSimilar();
|
||||
faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
|
||||
|
||||
System.out.println("faceSimilar.getScore() = " + faceSimilar.getScore());
|
||||
|
||||
if (faceSimilar.getScore() > passRate) {//相似值大于配置预期,加入到识别到人脸的列表
|
||||
UserCompareInfo info = new UserCompareInfo();
|
||||
info.setName(userInfo.getName());
|
||||
info.setFaceId(userInfo.getFaceId());
|
||||
info.setSimilar(faceSimilar.getScore());
|
||||
resultUserInfoList.add(info);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
} finally {
|
||||
if (faceEngine != null) {
|
||||
faceEngineComparePool.returnObject(faceEngine);
|
||||
}
|
||||
}
|
||||
|
||||
return resultUserInfoList;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package com.guwan.backend.face.util;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class Base64Util {
|
||||
public static String base64Process(String base64Str) {
|
||||
if (!ObjectUtils.isEmpty(base64Str)) {
|
||||
String photoBase64 = base64Str.substring(0, 30).toLowerCase();
|
||||
int indexOf = photoBase64.indexOf("base64,");
|
||||
if (indexOf > 0) {
|
||||
base64Str = base64Str.substring(indexOf + 7);
|
||||
}
|
||||
base64Str = base64Str.replaceAll(" ", "+");
|
||||
base64Str = base64Str.replaceAll("\r|\n", "");
|
||||
return base64Str;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static byte[] base64ToBytes(String base64) {
|
||||
if (ObjectUtils.isEmpty(base64)) {
|
||||
return null;
|
||||
}
|
||||
String base64Process = base64Process(base64);
|
||||
|
||||
byte[] decode = Base64.getDecoder().decode(base64Process);
|
||||
return decode;
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
package com.guwan.backend.face.util;
|
||||
|
||||
import com.arcsoft.face.*;
|
||||
import com.arcsoft.face.enums.*;
|
||||
import com.arcsoft.face.toolkit.ImageFactory;
|
||||
import com.arcsoft.face.toolkit.ImageInfo;
|
||||
import com.arcsoft.face.toolkit.ImageInfoEx;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FaceEngineTest {
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
//激活码,从官网获取
|
||||
String appId = "DEnAZa1bWXcaAxyWUg33QZaKCmMkNmrQxuKZJQGmZsHJ";
|
||||
String sdkKey = "vWbvUyStZeartSaM6QoTzPYWFpSaj4uhfDmRifSzCd6";
|
||||
String activeKey = "82G1-11QA-713Y-8NB4";
|
||||
|
||||
System.err.println("注意,如果返回的errorCode不为0,可查看com.arcsoft.face.enums.ErrorInfo类获取相应的错误信息");
|
||||
|
||||
//人脸识别引擎库存放路径
|
||||
FaceEngine faceEngine = new FaceEngine("/home/huangyifang/gb/咸阳师范/ArcSoft_ArcFacePro_linux_java_V4.1/libs/LINUX64");
|
||||
//激活引擎
|
||||
int errorCode = faceEngine.activeOnline(appId, sdkKey, activeKey);
|
||||
System.out.println("引擎激活errorCode:" + errorCode);
|
||||
|
||||
ActiveDeviceInfo activeDeviceInfo = new ActiveDeviceInfo();
|
||||
//采集设备信息(可离线)
|
||||
errorCode = faceEngine.getActiveDeviceInfo(activeDeviceInfo);
|
||||
System.out.println("采集设备信息errorCode:" + errorCode);
|
||||
System.out.println("设备信息:" + activeDeviceInfo.getDeviceInfo());
|
||||
|
||||
// faceEngine.activeOffline("d:\\ArcFacePro64.dat.offline");
|
||||
|
||||
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
|
||||
errorCode = faceEngine.getActiveFileInfo(activeFileInfo);
|
||||
System.out.println("获取激活文件errorCode:" + errorCode);
|
||||
System.out.println("激活文件信息:" + activeFileInfo.toString());
|
||||
|
||||
//引擎配置
|
||||
EngineConfiguration engineConfiguration = new EngineConfiguration();
|
||||
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
|
||||
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
|
||||
engineConfiguration.setDetectFaceMaxNum(10);
|
||||
//功能配置
|
||||
FunctionConfiguration functionConfiguration = new FunctionConfiguration();
|
||||
functionConfiguration.setSupportAge(true);
|
||||
functionConfiguration.setSupportFaceDetect(true);
|
||||
functionConfiguration.setSupportFaceRecognition(true);
|
||||
functionConfiguration.setSupportGender(true);
|
||||
functionConfiguration.setSupportLiveness(true);
|
||||
functionConfiguration.setSupportIRLiveness(true);
|
||||
functionConfiguration.setSupportImageQuality(true);
|
||||
functionConfiguration.setSupportMaskDetect(true);
|
||||
functionConfiguration.setSupportUpdateFaceData(true);
|
||||
engineConfiguration.setFunctionConfiguration(functionConfiguration);
|
||||
|
||||
//初始化引擎
|
||||
errorCode = faceEngine.init(engineConfiguration);
|
||||
System.out.println("初始化引擎errorCode:" + errorCode);
|
||||
VersionInfo version = faceEngine.getVersion();
|
||||
System.out.println(version);
|
||||
|
||||
//人脸检测
|
||||
ImageInfo imageInfo = ImageFactory.getRGBData(new File("/home/huangyifang/gb/咸阳师范/10.jpg"));
|
||||
List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
|
||||
errorCode = faceEngine.detectFaces(imageInfo, faceInfoList);
|
||||
System.out.println("人脸检测errorCode:" + errorCode);
|
||||
System.out.println("检测到人脸数:" + faceInfoList.size());
|
||||
|
||||
ImageQuality imageQuality = new ImageQuality();
|
||||
errorCode = faceEngine.imageQualityDetect(imageInfo, faceInfoList.get(0), 0, imageQuality);
|
||||
System.out.println("图像质量检测errorCode:" + errorCode);
|
||||
System.out.println("图像质量分数:" + imageQuality.getFaceQuality());
|
||||
|
||||
//特征提取
|
||||
FaceFeature faceFeature = new FaceFeature();
|
||||
errorCode = faceEngine.extractFaceFeature(imageInfo, faceInfoList.get(0), ExtractType.REGISTER, 0, faceFeature);
|
||||
System.out.println("特征提取errorCode:" + errorCode);
|
||||
|
||||
//人脸检测2
|
||||
ImageInfo imageInfo2 = ImageFactory.getRGBData(new File("/home/huangyifang/gb/咸阳师范/10.jpg"));
|
||||
List<FaceInfo> faceInfoList2 = new ArrayList<FaceInfo>();
|
||||
errorCode = faceEngine.detectFaces(imageInfo2, faceInfoList2);
|
||||
System.out.println("人脸检测errorCode:" + errorCode);
|
||||
System.out.println("检测到人脸数:" + faceInfoList.size());
|
||||
|
||||
//特征提取2
|
||||
FaceFeature faceFeature2 = new FaceFeature();
|
||||
errorCode = faceEngine.extractFaceFeature(imageInfo2, faceInfoList2.get(0), ExtractType.RECOGNIZE, 0, faceFeature2);
|
||||
System.out.println("特征提取errorCode:" + errorCode);
|
||||
|
||||
//特征比对
|
||||
FaceFeature targetFaceFeature = new FaceFeature();
|
||||
targetFaceFeature.setFeatureData(faceFeature.getFeatureData());
|
||||
FaceFeature sourceFaceFeature = new FaceFeature();
|
||||
sourceFaceFeature.setFeatureData(faceFeature2.getFeatureData());
|
||||
FaceSimilar faceSimilar = new FaceSimilar();
|
||||
|
||||
errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
|
||||
System.out.println("特征比对errorCode:" + errorCode);
|
||||
System.out.println("人脸相似度:" + faceSimilar.getScore());
|
||||
|
||||
|
||||
//人脸属性检测
|
||||
FunctionConfiguration configuration = new FunctionConfiguration();
|
||||
configuration.setSupportAge(true);
|
||||
configuration.setSupportGender(true);
|
||||
configuration.setSupportLiveness(true);
|
||||
configuration.setSupportMaskDetect(true);
|
||||
errorCode = faceEngine.process(imageInfo, faceInfoList, configuration);
|
||||
System.out.println("图像属性处理errorCode:" + errorCode);
|
||||
|
||||
//性别检测
|
||||
List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
|
||||
errorCode = faceEngine.getGender(genderInfoList);
|
||||
System.out.println("性别:" + genderInfoList.get(0).getGender());
|
||||
|
||||
//年龄检测
|
||||
List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
|
||||
errorCode = faceEngine.getAge(ageInfoList);
|
||||
System.out.println("年龄:" + ageInfoList.get(0).getAge());
|
||||
|
||||
//活体检测
|
||||
List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
|
||||
errorCode = faceEngine.getLiveness(livenessInfoList);
|
||||
System.out.println("活体:" + livenessInfoList.get(0).getLiveness());
|
||||
|
||||
//口罩检测
|
||||
List<MaskInfo> maskInfoList = new ArrayList<MaskInfo>();
|
||||
errorCode = faceEngine.getMask(maskInfoList);
|
||||
System.out.println("口罩:" + maskInfoList.get(0).getMask());
|
||||
|
||||
|
||||
//IR属性处理
|
||||
ImageInfo imageInfoGray = ImageFactory.getGrayData(new File("/home/huangyifang/gb/咸阳师范/10.jpg"));
|
||||
List<FaceInfo> faceInfoListGray = new ArrayList<FaceInfo>();
|
||||
errorCode = faceEngine.detectFaces(imageInfoGray, faceInfoListGray);
|
||||
|
||||
FunctionConfiguration configuration2 = new FunctionConfiguration();
|
||||
configuration2.setSupportIRLiveness(true);
|
||||
errorCode = faceEngine.processIr(imageInfoGray, faceInfoListGray, configuration2);
|
||||
//IR活体检测
|
||||
List<IrLivenessInfo> irLivenessInfo = new ArrayList<>();
|
||||
errorCode = faceEngine.getLivenessIr(irLivenessInfo);
|
||||
System.out.println("IR活体:" + irLivenessInfo.get(0).getLiveness());
|
||||
|
||||
//获取激活文件信息
|
||||
ActiveFileInfo activeFileInfo2 = new ActiveFileInfo();
|
||||
errorCode = faceEngine.getActiveFileInfo(activeFileInfo2);
|
||||
|
||||
//更新人脸数据
|
||||
errorCode = faceEngine.updateFaceData(imageInfo, faceInfoList);
|
||||
|
||||
//高级人脸图像处理接口
|
||||
ImageInfoEx imageInfoEx = new ImageInfoEx();
|
||||
imageInfoEx.setHeight(imageInfo.getHeight());
|
||||
imageInfoEx.setWidth(imageInfo.getWidth());
|
||||
imageInfoEx.setImageFormat(imageInfo.getImageFormat());
|
||||
imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
|
||||
imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});
|
||||
List<FaceInfo> faceInfoList1 = new ArrayList<>();
|
||||
errorCode = faceEngine.detectFaces(imageInfoEx, DetectModel.ASF_DETECT_MODEL_RGB, faceInfoList1);
|
||||
ImageQuality imageQuality1 = new ImageQuality();
|
||||
errorCode = faceEngine.imageQualityDetect(imageInfoEx, faceInfoList1.get(0), 0, imageQuality1);
|
||||
FunctionConfiguration fun = new FunctionConfiguration();
|
||||
fun.setSupportAge(true);
|
||||
errorCode = faceEngine.process(imageInfoEx, faceInfoList1, fun);
|
||||
List<AgeInfo> ageInfoList1 = new ArrayList<>();
|
||||
int age = faceEngine.getAge(ageInfoList1);
|
||||
FaceFeature feature = new FaceFeature();
|
||||
errorCode = faceEngine.extractFaceFeature(imageInfoEx, faceInfoList1.get(0), ExtractType.REGISTER, 0, feature);
|
||||
errorCode = faceEngine.updateFaceData(imageInfoEx, faceInfoList1);
|
||||
|
||||
//设置活体测试
|
||||
errorCode = faceEngine.setLivenessParam(0.5f, 0.7f, 0.3f);
|
||||
System.out.println("设置活体活体阈值errorCode:" + errorCode);
|
||||
|
||||
LivenessParam livenessParam=new LivenessParam();
|
||||
errorCode = faceEngine.getLivenessParam(livenessParam);
|
||||
|
||||
//注册人脸信息1
|
||||
FaceFeatureInfo faceFeatureInfo = new FaceFeatureInfo();
|
||||
faceFeatureInfo.setSearchId(5);
|
||||
faceFeatureInfo.setFaceTag("FeatureData1");
|
||||
faceFeatureInfo.setFeatureData(faceFeature.getFeatureData());
|
||||
errorCode = faceEngine.registerFaceFeature(faceFeatureInfo);
|
||||
|
||||
//注册人脸信息2
|
||||
FaceFeatureInfo faceFeatureInfo2 = new FaceFeatureInfo();
|
||||
faceFeatureInfo2.setSearchId(6);
|
||||
faceFeatureInfo2.setFaceTag("FeatureData2");
|
||||
faceFeatureInfo2.setFeatureData(faceFeature2.getFeatureData());
|
||||
errorCode = faceEngine.registerFaceFeature(faceFeatureInfo2);
|
||||
|
||||
//获取注册人脸个数
|
||||
FaceSearchCount faceSearchCount = new FaceSearchCount();
|
||||
errorCode = faceEngine.getFaceCount(faceSearchCount);
|
||||
System.out.println("注册人脸个数:" + faceSearchCount.getCount());
|
||||
|
||||
//搜索最相似人脸
|
||||
SearchResult searchResult = new SearchResult();
|
||||
errorCode = faceEngine.searchFaceFeature(faceFeature, CompareModel.LIFE_PHOTO, searchResult);
|
||||
System.out.println("最相似人脸Id:" + searchResult.getFaceFeatureInfo().getSearchId());
|
||||
|
||||
//更新人脸信息
|
||||
FaceFeatureInfo faceFeatureInfo3 = new FaceFeatureInfo();
|
||||
faceFeatureInfo3.setSearchId(6);
|
||||
faceFeatureInfo3.setFaceTag("FeatureData2Update");
|
||||
faceFeatureInfo3.setFeatureData(faceFeature2.getFeatureData());
|
||||
errorCode = faceEngine.updateFaceFeature(faceFeatureInfo3);
|
||||
|
||||
//移除人脸信息
|
||||
errorCode = faceEngine.removeFaceFeature(6);
|
||||
|
||||
//引擎卸载
|
||||
errorCode = faceEngine.unInit();
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.guwan.backend.face.util;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserInfo {
|
||||
private String faceId;
|
||||
private String name;
|
||||
private byte[] faceFeature;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package com.guwan.backend.face.util;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class UserRamCache {
|
||||
|
||||
private final ConcurrentHashMap<String, UserInfo> USER_INFO_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private final Set<Listener> REGISTER = new CopyOnWriteArraySet<>();
|
||||
|
||||
public void addUser(UserInfo userInfo) {
|
||||
USER_INFO_MAP.put(userInfo.getFaceId(), userInfo);
|
||||
for (Listener listener : REGISTER) {
|
||||
listener.onAdd(userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeUser(String faceId) {
|
||||
UserInfo userInfo = USER_INFO_MAP.remove(faceId);
|
||||
for (Listener listener : REGISTER) {
|
||||
listener.onRemove(userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public List<UserInfo> getUserList() {
|
||||
List<UserInfo> userInfoList = Lists.newLinkedList();
|
||||
userInfoList.addAll(USER_INFO_MAP.values());
|
||||
return userInfoList;
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
USER_INFO_MAP.clear();
|
||||
REGISTER.clear();
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
REGISTER.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
REGISTER.remove(listener);
|
||||
}
|
||||
|
||||
// @Data
|
||||
// public class UserInfo {
|
||||
//
|
||||
// private String faceId;
|
||||
// private String name;
|
||||
// private byte[] faceFeature;
|
||||
//
|
||||
// }
|
||||
|
||||
public interface Listener {
|
||||
default void onAdd(UserInfo userInfo) {
|
||||
}
|
||||
|
||||
default void onRemove(UserInfo userInfo) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.guwan.backend.face.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class UserRamGroup {
|
||||
|
||||
private static final ConcurrentHashMap<String, UserRamCache> USER_RAM_GROUP_MAP = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<String, String> ORG_CELL_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addCell(String cellId){
|
||||
UserRamCache cell = new UserRamCache();
|
||||
USER_RAM_GROUP_MAP.put(cellId,cell);
|
||||
}
|
||||
|
||||
public static void removeCell(String cellId){
|
||||
USER_RAM_GROUP_MAP.remove(cellId);
|
||||
}
|
||||
|
||||
public static void addUser(UserInfo userInfo, String cellId) {
|
||||
USER_RAM_GROUP_MAP.get(cellId).addUser(userInfo);
|
||||
}
|
||||
|
||||
public static void removeUser(String faceId, String cellId) {
|
||||
USER_RAM_GROUP_MAP.get(cellId).removeUser(faceId);
|
||||
}
|
||||
|
||||
public static List<UserInfo> getUserList(String cellId) {
|
||||
return USER_RAM_GROUP_MAP.get(cellId).getUserList();
|
||||
}
|
||||
|
||||
public static void addOrgId(String orgId,String cellId){
|
||||
ORG_CELL_MAP.put(orgId,cellId);
|
||||
}
|
||||
public static String getOrgCellMap(String orgId) {
|
||||
return ORG_CELL_MAP.get(orgId);
|
||||
}
|
||||
|
||||
public static void clear(){
|
||||
USER_RAM_GROUP_MAP.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.guwan.backend.kafka;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class BookEvent {
|
||||
private String eventId;
|
||||
private String eventType; // READ, RATE, BOOKMARK 等
|
||||
private String userId;
|
||||
private String bookId;
|
||||
private String bookName;
|
||||
private LocalDateTime timestamp;
|
||||
private Map<String, Object> eventData;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//package com.guwan.backend.kafka;
|
||||
//
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.kafka.annotation.KafkaListener;
|
||||
//import org.springframework.stereotype.Service;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//
|
||||
//@Service
|
||||
//@Slf4j
|
||||
//public class BookEventConsumer {
|
||||
//
|
||||
// @Autowired
|
||||
// private ObjectMapper objectMapper;
|
||||
//
|
||||
// @KafkaListener(topics = "book-events", groupId = "book-recommendation-group")
|
||||
// public void consume(String message) {
|
||||
// try {
|
||||
// BookEvent event = objectMapper.readValue(message, BookEvent.class);
|
||||
// processBookEvent(event);
|
||||
// } catch (Exception e) {
|
||||
// log.error("Error processing book event", e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void processBookEvent(BookEvent event) {
|
||||
// // 处理接收到的事件
|
||||
// log.info("Processing book event: {}", event);
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,28 @@
|
|||
package com.guwan.backend.kafka;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
@Service
|
||||
public class BookEventProducer {
|
||||
|
||||
private static final String TOPIC = "book-events";
|
||||
|
||||
@Autowired
|
||||
private KafkaTemplate<String, String> kafkaTemplate;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public void sendBookEvent(BookEvent event) {
|
||||
try {
|
||||
String message = objectMapper.writeValueAsString(event);
|
||||
kafkaTemplate.send(TOPIC, message);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error sending book event", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.guwan.backend.kafka;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/common/v1/books")
|
||||
public class BookOfKafkaController {
|
||||
|
||||
@Autowired
|
||||
private BookService bookService;
|
||||
|
||||
@PostMapping("/{bookId}/reading")
|
||||
public void recordReading(
|
||||
@PathVariable String bookId,
|
||||
@RequestParam String userId) {
|
||||
bookService.recordReadingEvent(userId, bookId);
|
||||
}
|
||||
|
||||
@PostMapping("/{bookId}/rating")
|
||||
public void rateBook(
|
||||
@PathVariable String bookId,
|
||||
@RequestParam String userId,
|
||||
@RequestParam int rating) {
|
||||
bookService.recordBookRating(userId, bookId, rating);
|
||||
}
|
||||
|
||||
@PostMapping("/{bookId}/bookmark")
|
||||
public void bookmarkBook(
|
||||
@PathVariable String bookId,
|
||||
@RequestParam String userId) {
|
||||
bookService.recordBookmarkEvent(userId, bookId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.guwan.backend.kafka;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
public class BookService {
|
||||
|
||||
@Autowired
|
||||
private BookEventProducer producer;
|
||||
|
||||
public void recordReadingEvent(String userId, String bookId) {
|
||||
BookEvent event = new BookEvent();
|
||||
event.setEventId(UUID.randomUUID().toString());
|
||||
event.setEventType("READ");
|
||||
event.setUserId(userId);
|
||||
event.setBookId(bookId);
|
||||
event.setBookName("大爱仙尊");
|
||||
event.setTimestamp(LocalDateTime.now());
|
||||
event.setEventData(Map.of("readingTime", 30));
|
||||
|
||||
producer.sendBookEvent(event);
|
||||
}
|
||||
|
||||
public void recordBookRating(String userId, String bookId, int rating) {
|
||||
BookEvent event = new BookEvent();
|
||||
event.setEventId(UUID.randomUUID().toString());
|
||||
event.setEventType("RATE");
|
||||
event.setUserId(userId);
|
||||
event.setBookId(bookId);
|
||||
event.setTimestamp(LocalDateTime.now());
|
||||
event.setEventData(Map.of("rating", rating));
|
||||
|
||||
producer.sendBookEvent(event);
|
||||
}
|
||||
|
||||
public void recordBookmarkEvent(String userId, String bookId) {
|
||||
BookEvent event = new BookEvent();
|
||||
event.setEventId(UUID.randomUUID().toString());
|
||||
event.setEventType("BOOKMARK");
|
||||
event.setUserId(userId);
|
||||
event.setBookId(bookId);
|
||||
event.setTimestamp(LocalDateTime.now());
|
||||
|
||||
producer.sendBookEvent(event);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.guwan.backend.kafka;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class EveryReadDetailOfKafka {
|
||||
|
||||
private Long UserId;
|
||||
|
||||
private Long bookId;
|
||||
|
||||
private LocalDateTime startTime;
|
||||
|
||||
private LocalDateTime endTime;
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.guwan.backend.pojo.entity.BookCategory;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* @author 12455
|
||||
* @description 针对表【book_category】的数据库操作Mapper
|
||||
* @createDate 2024-12-20 17:04:09
|
||||
* @Entity com.guwan.backend.entity.BookCategory
|
||||
*/
|
||||
public interface BookCategoryMapper extends BaseMapper<BookCategory> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.pojo.entity.BookContent;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface BookContentMapper extends BaseMapper<BookContent> {
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.entity.LiveRoom;
|
||||
import com.guwan.backend.pojo.entity.Book;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LiveRoomMapper extends BaseMapper<LiveRoom> {
|
||||
public interface BookMapper extends BaseMapper<Book> {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.pojo.entity.ReadingNote;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ReadingNoteMapper extends BaseMapper<ReadingNote> {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.pojo.entity.ReadingProgress;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ReadingProgressMapper extends BaseMapper<ReadingProgress> {
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.entity.SysLog;
|
||||
import com.guwan.backend.pojo.entity.SysLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.entity.User;
|
||||
import com.guwan.backend.pojo.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.guwan.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.guwan.backend.entity.VideoLike;
|
||||
import com.guwan.backend.pojo.entity.VideoLike;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue