refactor(project): Reorganize frontend and backend directories

This commit is contained in:
2026-03-18 10:45:15 +08:00
parent cc9eaf4cdf
commit 92a139e470
513 changed files with 28251 additions and 287 deletions
+30
View File
@@ -0,0 +1,30 @@
@echo off
echo 开始构建和部署视频平台项目...
echo 1. 构建项目...
call mvn clean package -DskipTests
echo 2. 构建Sentinel Dashboard镜像...
cd sentinel-dashboard
docker build -t sentinel-dashboard:1.8.6 .
cd ..
echo 3. 获取本机IP地址...
for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /r "IPv4.*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"') do (
set HOST_IP=%%a
goto :found_ip
)
:found_ip
set HOST_IP=%HOST_IP:~1%
echo 使用IP地址: %HOST_IP%
echo 4. 启动Docker Compose...
set HOST_IP=%HOST_IP%
docker-compose up -d
echo 部署完成!
echo 服务访问地址:
echo - 网关: http://%HOST_IP%:9000
echo - Nacos: http://%HOST_IP%:8848/nacos
echo - Sentinel: http://%HOST_IP%:8880
echo - MinIO: http://%HOST_IP%:9001
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
echo "开始构建和部署视频平台项目..."
echo "1. 安装common模块到本地Maven仓库..."
cd video-common
# 使用-Dspring-boot.repackage.skip=true跳过Spring Boot的repackage阶段
mvn clean install -DskipTests -Dspring-boot.repackage.skip=true
cd ..
echo "2. 构建其他模块..."
mvn clean package -DskipTests -pl '!video-common'
echo "3. 构建Sentinel Dashboard镜像..."
# 检查sentinel-dashboard目录是否存在
if [ -d "sentinel-dashboard" ]; then
cd sentinel-dashboard
docker build -t sentinel-dashboard:1.8.6 .
cd ..
else
echo "警告: sentinel-dashboard目录不存在,跳过镜像构建"
# 创建简单的Dockerfile用于构建Sentinel镜像
mkdir -p sentinel-dashboard
cat > sentinel-dashboard/Dockerfile << 'EOF'
FROM eclipse-temurin:8-jre
# 下载 Sentinel 1.8.6 官方 JAR
RUN apt-get update && \
apt-get install -y wget && \
wget -O /sentinel-dashboard.jar \
https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar && \
apt-get remove -y wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
EXPOSE 8080
CMD ["java", "-Dserver.port=8080", "-Dcsp.sentinel.dashboard.server=localhost:8080", "-jar", "/sentinel-dashboard.jar"]
EOF
cd sentinel-dashboard
docker build -t sentinel-dashboard:1.8.6 .
cd ..
fi
echo "4. 获取本机IP地址..."
HOST_IP=$(hostname -I | awk '{print $1}')
echo "使用IP地址: $HOST_IP"
echo "5. 启动Docker Compose..."
export HOST_IP=$HOST_IP
# 检查docker-compose是否可用
if command -v docker-compose &> /dev/null; then
HOST_IP=$HOST_IP docker-compose up -d
else
echo "警告: docker-compose命令不可用,尝试使用docker compose命令"
HOST_IP=$HOST_IP docker compose up -d
fi
echo "部署完成!"
echo "服务访问地址:"
echo "- 网关: http://${HOST_IP}:9000"
echo "- Nacos: http://${HOST_IP}:8848/nacos"
echo "- Sentinel: http://${HOST_IP}:8880"
echo "- MinIO: http://${HOST_IP}:9001"
+57
View File
@@ -0,0 +1,57 @@
# ============================================================
# Node-01 环境变量配置 (192.168.1.101)
# 主节点: MySQL Master, Redis Master, Nacos, NameServer, MinIO, 微服务
# ============================================================
# 节点信息
NODE_ID=1
HOST_IP=192.168.1.101
HOSTNAME=node-01
# 集群节点列表
NACOS_SERVERS=192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848
NAMESRV_ADDR=192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876
# MySQL配置 (主节点)
MYSQL_SERVER_ID=1
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root123456
MYSQL_DATABASE=video_platform
MYSQL_USER=video
MYSQL_PASSWORD=video123
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_HA_Cluster_2024
REDIS_MASTER_HOST=192.168.1.101
REDIS_MASTER_PORT=6379
# Nacos配置
NACOS_HOST=nacos
NACOS_PORT=8848
NACOS_NAMESPACE=public
NACOS_GROUP=DEFAULT_GROUP
# Sentinel配置
SENTINEL_HOST=sentinel
SENTINEL_PORT=8080
# MinIO配置
MINIO_HOST=minio
MINIO_PORT=9000
MINIO_EXTERNAL_PORT=9100
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=video-platform
# RocketMQ配置
ROCKETMQ_HOST=rmqnamesrv
ROCKETMQ_PORT=9876
# Java配置
JAVA_OPTS=-Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
# 本节点部署的服务
DEPLOY_SERVICES=mysql,redis,redis-sentinel,nacos,rmqnamesrv,minio,sentinel,video-gateway,video-auth,video-user,video-content,video-comment,video-recommend,video-admin,nginx
+58
View File
@@ -0,0 +1,58 @@
# ============================================================
# Node-02 环境变量配置 (192.168.1.102)
# 从节点: MySQL Slave, Redis Slave, Nacos, NameServer, Broker-A, MinIO, 微服务
# ============================================================
# 节点信息
NODE_ID=2
HOST_IP=192.168.1.102
HOSTNAME=node-02
# 集群节点列表
NACOS_SERVERS=192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848
NAMESRV_ADDR=192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876
# MySQL配置 (从节点)
MYSQL_SERVER_ID=2
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root123456
MYSQL_DATABASE=video_platform
MYSQL_USER=video
MYSQL_PASSWORD=video123
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_HA_Cluster_2024
REDIS_MASTER_HOST=192.168.1.101
REDIS_MASTER_PORT=6379
# Nacos配置
NACOS_HOST=nacos
NACOS_PORT=8848
NACOS_NAMESPACE=public
NACOS_GROUP=DEFAULT_GROUP
# Sentinel配置
SENTINEL_HOST=sentinel
SENTINEL_PORT=8080
# MinIO配置
MINIO_HOST=minio
MINIO_PORT=9000
MINIO_EXTERNAL_PORT=9100
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=video-platform
# RocketMQ配置 (Broker-A)
ROCKETMQ_HOST=rmqnamesrv
ROCKETMQ_PORT=9876
BROKER_NAME=broker-a
# Java配置
JAVA_OPTS=-Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
# 本节点部署的服务
DEPLOY_SERVICES=mysql,redis,redis-sentinel,nacos,rmqnamesrv,rmqbroker,minio,sentinel,video-gateway,video-auth,video-user,video-content,video-comment,video-recommend,video-admin
+59
View File
@@ -0,0 +1,59 @@
# ============================================================
# Node-03 环境变量配置 (192.168.1.103)
# 从节点 - MySQL Slave, Redis Slave, Nacos, NameServer, Broker-B, MinIO
# ============================================================
# 节点信息
NODE_ID=3
HOST_IP=192.168.1.103
HOSTNAME=node-03
# 集群节点列表
NACOS_SERVERS=192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848
NAMESRV_ADDR=192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876
# MySQL配置 (从节点)
MYSQL_SERVER_ID=3
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=root123456
MYSQL_DATABASE=video_platform
MYSQL_USER=video
MYSQL_PASSWORD=video123
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_HA_Cluster_2024
REDIS_MASTER_HOST=192.168.1.101
REDIS_MASTER_PORT=6379
# Nacos配置
NACOS_HOST=nacos
NACOS_PORT=8848
NACOS_NAMESPACE=public
NACOS_GROUP=DEFAULT_GROUP
# Sentinel配置
SENTINEL_HOST=sentinel
SENTINEL_PORT=8080
# MinIO配置
MINIO_HOST=minio
MINIO_PORT=9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=video-platform
# MinIO分布式节点
MINIO_ENDPOINTS=http://192.168.1.101:9000,http://192.168.1.102:9000,http://192.168.1.103:9000
# RocketMQ配置 (Broker-B)
ROCKETMQ_HOST=rmqnamesrv
ROCKETMQ_PORT=9876
BROKER_NAME=broker-b
# Java配置
JAVA_OPTS=-Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
# 本节点部署的服务
DEPLOY_SERVICES=mysql,redis,redis-sentinel,nacos,rmqnamesrv,rmqbroker,minio,sentinel
+71
View File
@@ -0,0 +1,71 @@
# MySQL 主节点配置 (Node-01)
# GTID 主从复制
[mysqld]
# 基础配置
user = mysql
port = 3306
bind-address = 0.0.0.0
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-authentication-plugin = mysql_native_password
# 连接配置
max_connections = 500
max_connect_errors = 100
wait_timeout = 28800
interactive_timeout = 28800
connect_timeout = 10
# 缓冲配置 (8GB内存建议)
innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 4
innodb_log_buffer_size = 64M
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
# 日志配置
log-error = /var/lib/mysql/mysql-error.log
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = 0
# 临时目录
tmpdir = /tmp
# 二进制日志配置 (主节点)
server-id = 1
log-bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
binlog_cache_size = 1M
max_binlog_size = 500M
expire_logs_days = 7
sync_binlog = 1
# GTID 配置
gtid-mode = ON
enforce-gtid-consistency = ON
log-slave-updates = ON
# 复制配置
binlog-do-db = video_auth
binlog-do-db = video_user
binlog-do-db = video_content
binlog-do-db = video_comment
binlog-do-db = video_recommend
binlog-do-db = video_admin
binlog-do-db = nacos_config
# 安全配置
skip-name-resolve
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
[mysql]
default-character-set = utf8mb4
prompt = '\u@\h [\d]> '
[client]
default-character-set = utf8mb4
+63
View File
@@ -0,0 +1,63 @@
# MySQL 高可用配置
# 从节点配置 (Node-02, Node-03)
# 注意: 每个从节点需要设置不同的 server-id
[mysqld]
# 基础配置
user = mysql
port = 3306
bind-address = 0.0.0.0
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-authentication-plugin = mysql_native_password
# 连接配置
max_connections = 500
max_connect_errors = 100
wait_timeout = 28800
interactive_timeout = 28800
# 缓冲配置
innodb_buffer_pool_size = 1G
innodb_buffer_pool_instances = 4
innodb_log_buffer_size = 64M
innodb_log_file_size = 512M
# 日志配置
log-error = /var/lib/mysql/mysql-error.log
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/mysql-slow.log
long_query_time = 2
# 主从复制配置 - 从节点
# server-id 在docker-compose中通过环境变量设置
# server-id = 2 # Node-02
# server-id = 3 # Node-03
log-bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
relay-log = relay-bin
relay-log-index = relay-bin.index
# 注意: read_only 和 super_read_only 应在主从复制配置完成后手动启用
# 避免阻止容器初始化脚本执行
# read_only = ON
# super_read_only = ON
# GTID配置
gtid-mode = ON
enforce-gtid-consistency = ON
# 从节点并行复制
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 4
slave_preserve_commit_order = ON
# 半同步复制 (可选)
# plugin-load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
# rpl_semi_sync_slave_enabled = 1
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
+179
View File
@@ -0,0 +1,179 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/******************************************/
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
`c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
`effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述',
`type` varchar(64) DEFAULT NULL COMMENT '配置的类型',
`c_schema` text COMMENT '配置的模式',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 表名称 = config_info since 2.5.0 */
/******************************************/
CREATE TABLE `config_info_gray` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`src_user` text COMMENT 'src_user',
`src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',
`gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
`gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`gray_name` varchar(128) NOT NULL COMMENT 'gray_name',
`gray_rule` text NOT NULL COMMENT 'gray_rule',
`encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray';
/******************************************/
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(20) unsigned NOT NULL COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`op_type` char(10) DEFAULT NULL COMMENT 'operation type',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
`publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal',
`gray_name` varchar(50) DEFAULT NULL COMMENT 'gray name',
`ext_info` longtext DEFAULT NULL COMMENT 'ext info',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username',
`password` varchar(500) NOT NULL COMMENT 'password',
`enabled` boolean NOT NULL COMMENT 'enabled'
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL COMMENT 'username',
`role` varchar(50) NOT NULL COMMENT 'role',
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL COMMENT 'role',
`resource` varchar(128) NOT NULL COMMENT 'resource',
`action` varchar(8) NOT NULL COMMENT 'action',
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
+52
View File
@@ -0,0 +1,52 @@
# Nginx Upstream 配置
# 高可用负载均衡
# API网关集群
upstream gateway_cluster {
# 负载均衡策略: 轮询 (默认)
# 其他选项: ip_hash, least_conn, hash $request_uri
least_conn; # 最少连接数策略
server 192.168.1.101:9000 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.1.102:9000 weight=1 max_fails=3 fail_timeout=30s backup;
# 健康检查
keepalive 32;
}
# Nacos集群
upstream nacos_cluster {
server 192.168.1.101:8848 weight=1;
server 192.168.1.102:8848 weight=1;
server 192.168.1.103:8848 weight=1;
keepalive 16;
}
# Sentinel集群
upstream sentinel_cluster {
server 192.168.1.101:8880 weight=1;
server 192.168.1.102:8880 weight=1;
server 192.168.1.103:8880 weight=1;
keepalive 16;
}
# MinIO API集群 (统一使用9100端口)
upstream minio_cluster {
server 192.168.1.101:9100 weight=1;
server 192.168.1.102:9100 weight=1;
server 192.168.1.103:9000 weight=1 backup;
keepalive 16;
}
# MinIO控制台集群 (统一使用9101端口,Node-03使用9001)
upstream minio_console_cluster {
server 192.168.1.101:9101 weight=1;
server 192.168.1.102:9101 weight=1;
server 192.168.1.103:9001 weight=1 backup;
keepalive 16;
}
+207
View File
@@ -0,0 +1,207 @@
# Nginx 主配置文件
# 高可用负载均衡
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 10240;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'upstream: $upstream_addr '
'response_time: $upstream_response_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩配置
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml;
# 请求体大小限制 (用于视频上传)
client_max_body_size 1024M;
client_body_buffer_size 128k;
# 代理超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# 代理头配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 加载upstream配置
include /etc/nginx/conf.d/upstream.conf;
# 主服务配置
server {
listen 80;
listen [::]:80;
server_name _;
# 首页
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# API网关路由
location /auth/ {
proxy_pass http://gateway_cluster/auth/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /user/ {
proxy_pass http://gateway_cluster/user/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /content/ {
proxy_pass http://gateway_cluster/content/;
proxy_http_version 1.1;
proxy_set_header Connection "";
# 视频流优化
proxy_buffering off;
proxy_request_buffering off;
}
location /comment/ {
proxy_pass http://gateway_cluster/comment/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /recommend/ {
proxy_pass http://gateway_cluster/recommend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /admin/ {
proxy_pass http://gateway_cluster/admin/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Nacos控制台
location /nacos/ {
proxy_pass http://nacos_cluster/nacos/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Sentinel控制台
location /sentinel/ {
proxy_pass http://sentinel_cluster/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# MinIO API
location /minio/ {
proxy_pass http://minio_cluster/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
}
# MinIO控制台
location /minio-console/ {
proxy_pass http://minio_console_cluster/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 健康检查端点
location /health {
access_log off;
return 200 'OK';
add_header Content-Type text/plain;
}
# 状态监控页面
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.0/24;
deny all;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
# TCP/UDP 流代理 (用于Redis, MySQL等)
stream {
# MySQL负载均衡
upstream mysql_cluster {
server 192.168.1.101:3306 weight=1;
server 192.168.1.102:3306 weight=1 backup;
server 192.168.1.103:3306 weight=1 backup;
}
server {
listen 13306;
proxy_pass mysql_cluster;
proxy_connect_timeout 10s;
}
# Redis负载均衡 (读写分离)
upstream redis_master {
server 192.168.1.101:6379;
}
server {
listen 16379;
proxy_pass redis_master;
proxy_connect_timeout 5s;
}
# Redis Sentinel
upstream redis_sentinel {
server 192.168.1.101:26379;
server 192.168.1.102:26379;
server 192.168.1.103:26379;
}
server {
listen 26379;
proxy_pass redis_sentinel;
}
}
+55
View File
@@ -0,0 +1,55 @@
# Redis 主节点配置 (Node-01)
# 网络配置
bind 0.0.0.0
port 6379
protected-mode no
# 进程配置
daemonize no
pidfile /var/run/redis/redis-server.pid
# 日志输出到标准输出(Docker容器标准做法)
logfile ""
# 数据目录
dir /data
# 内存配置
maxmemory 2gb
maxmemory-policy allkeys-lru
# 持久化配置 - RDB
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
# 持久化配置 - AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 密码配置
requirepass redis_HA_Cluster_2024
masterauth redis_HA_Cluster_2024
# 性能配置
tcp-keepalive 300
tcp-backlog 511
timeout 0
# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128
# 客户端配置
maxclients 10000
# 集群配置 (单机模式不需要)
# cluster-enabled no
+61
View File
@@ -0,0 +1,61 @@
# Redis 从节点配置 (Node-02, Node-03)
# 网络配置
bind 0.0.0.0
port 6379
protected-mode no
# 进程配置
daemonize no
pidfile /var/run/redis/redis-server.pid
# 日志输出到标准输出(Docker容器标准做法)
logfile ""
# 数据目录
dir /data
# 内存配置
maxmemory 2gb
maxmemory-policy allkeys-lru
# 持久化配置 - RDB
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
# 持久化配置 - AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 密码配置
requirepass redis_HA_Cluster_2024
masterauth redis_HA_Cluster_2024
# 从节点配置 (启动后手动执行 REPLICAOF 命令)
# replicaof 192.168.1.101 6379
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
repl-backlog-size 1mb
# 性能配置
tcp-keepalive 300
tcp-backlog 511
timeout 0
# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128
# 客户端配置
maxclients 10000
+41
View File
@@ -0,0 +1,41 @@
# Redis Sentinel 配置
# 所有节点使用相同配置
port 26379
daemonize no
pidfile /var/run/redis/redis-sentinel.pid
# 日志输出到标准输出(Docker容器标准做法)
logfile ""
dir /tmp
# 监控主节点配置
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# quorum: 需要多少个sentinel同意才能判断主节点客观下线
sentinel monitor mymaster 192.168.1.101 6379 2
# 主节点密码
sentinel auth-pass mymaster redis_HA_Cluster_2024
# 主观下线时间 (毫秒)
# 如果主节点在这个时间内没有响应PING,则标记为主观下线
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时时间 (毫秒)
sentinel failover-timeout mymaster 180000
# 同时可以有多少个从节点对新主节点进行同步
sentinel parallel-syncs mymaster 1
# 通知脚本 (可选)
# sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重配置脚本 (可选)
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
# 允许Sentinel在启动时更新配置文件
# sentinel announce-ip <ip>
# sentinel announce-port <port>
# 其他配置
protected-mode no
bind 0.0.0.0
+47
View File
@@ -0,0 +1,47 @@
# RocketMQ Broker 配置
# 主节点配置 (Node-02) - 与Node-03互为主备
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
# NameServer地址
namesrvAddr = 192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876
# 网络配置
brokerIP1 = 192.168.1.102
listenPort = 10911
# 存储配置
# 只需指定根目录,RocketMQ 会自动创建子目录
storePathRootDir = /home/rocketmq/store
# 性能配置
sendMessageThreadPoolNums = 16
pullMessageThreadPoolNums = 16
queryMessageThreadPoolNums = 8
# 刷盘配置
flushDiskType = ASYNC_FLUSH
# 主从复制配置
brokerRole = ASYNC_MASTER
# 消息保留时间 (小时)
fileReservedTime = 72
# 提交日志文件大小
mapedFileSizeCommitLog = 1073741824
# 消费队列文件大小
mapedFileSizeConsumeQueue = 300000
# 自动创建Topic
autoCreateTopicEnable = true
autoCreateSubscriptionGroup = true
# 消息最大大小
maxMessageSize = 4194304
# 内存配置
brokerMemory = 512m
+47
View File
@@ -0,0 +1,47 @@
# RocketMQ Broker 配置
# 主节点配置 (Node-03) - 与Node-02互为主备
brokerClusterName = DefaultCluster
brokerName = broker-b
brokerId = 0
# NameServer地址
namesrvAddr = 192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876
# 网络配置
brokerIP1 = 192.168.1.103
listenPort = 10911
# 存储配置
# 只需指定根目录,RocketMQ 会自动创建子目录
storePathRootDir = /home/rocketmq/store
# 性能配置
sendMessageThreadPoolNums = 16
pullMessageThreadPoolNums = 16
queryMessageThreadPoolNums = 8
# 刷盘配置
flushDiskType = ASYNC_FLUSH
# 主从复制配置
brokerRole = ASYNC_MASTER
# 消息保留时间 (小时)
fileReservedTime = 72
# 提交日志文件大小
mapedFileSizeCommitLog = 1073741824
# 消费队列文件大小
mapedFileSizeConsumeQueue = 300000
# 自动创建Topic
autoCreateTopicEnable = true
autoCreateSubscriptionGroup = true
# 消息最大大小
maxMessageSize = 4194304
# 内存配置
brokerMemory = 512m
+333
View File
@@ -0,0 +1,333 @@
#!/bin/bash
# ============================================================
# 高可用集群部署脚本
# 用法: ./deploy-ha.sh <node-id> <action>
# node-id: 1, 2, 3
# action: init, start, stop, restart, status
# ============================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# 检查参数
if [ $# -lt 2 ]; then
echo "用法: $0 <node-id> <action>"
echo "node-id: 1, 2, 3"
echo "action: init, start, stop, restart, status, mysql-master, mysql-slave, redis-master, redis-slave"
exit 1
fi
NODE_ID=$1
ACTION=$2
ENV_FILE="config/.env.node-0${NODE_ID}"
# 检查环境文件
if [ ! -f "$ENV_FILE" ]; then
log_error "环境文件不存在: $ENV_FILE"
exit 1
fi
# 加载环境变量
log_info "加载环境变量: $ENV_FILE"
export $(grep -v '^#' $ENV_FILE | xargs)
log_info "=========================================="
log_info "节点ID: $NODE_ID"
log_info "主机IP: $HOST_IP"
log_info "操作: $ACTION"
log_info "=========================================="
# 初始化函数
init_node() {
log_step "初始化节点 $NODE_ID..."
# 创建必要目录
log_info "创建数据目录..."
mkdir -p data/mysql data/redis data/nacos data/rocketmq data/minio logs/nginx
# 复制配置文件
log_info "配置文件准备..."
# MySQL配置
if [ "$NODE_ID" == "1" ]; then
cp config/mysql/my-master.cnf config/mysql/my.cnf
log_info "MySQL配置: 主节点"
else
cp config/mysql/my-slave.cnf config/mysql/my.cnf
log_info "MySQL配置: 从节点"
fi
# Redis配置
if [ "$NODE_ID" == "1" ]; then
cp config/redis/redis-master.conf config/redis/redis.conf
log_info "Redis配置: 主节点"
else
cp config/redis/redis-slave.conf config/redis/redis.conf
fi
# RocketMQ Broker配置
if [ "$NODE_ID" == "2" ]; then
cp config/rocketmq/broker-a.conf config/rocketmq/broker.conf
log_info "RocketMQ配置: Broker-A"
elif [ "$NODE_ID" == "3" ]; then
cp config/rocketmq/broker-b.conf config/rocketmq/broker.conf
log_info "RocketMQ配置: Broker-B"
fi
# 设置Docker网络
log_info "创建Docker网络..."
docker network create ha-network 2>/dev/null || true
log_info "节点初始化完成!"
}
# 启动基础设施
start_infrastructure() {
log_step "启动基础设施..."
# 按顺序启动服务
log_info "启动MySQL..."
docker compose --env-file $ENV_FILE up -d mysql
sleep 30
log_info "等待MySQL就绪..."
until docker exec mysql mysqladmin ping -h localhost -uroot -p${MYSQL_ROOT_PASSWORD} --silent 2>/dev/null; do
log_warn "等待MySQL启动..."
sleep 5
done
log_info "MySQL已就绪"
# 初始化数据库 (仅主节点)
if [ "$NODE_ID" == "1" ]; then
log_info "初始化数据库..."
docker exec -i mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} < sql/init.sql 2>/dev/null || true
log_info "数据库初始化完成"
fi
log_info "启动Redis..."
docker compose --env-file $ENV_FILE up -d redis redis-sentinel
sleep 10
log_info "启动Nacos..."
docker compose --env-file $ENV_FILE up -d nacos
sleep 30
log_info "等待Nacos就绪..."
until curl -s http://localhost:8848/nacos/v1/console/health/liveness | grep -q "UP" 2>/dev/null; do
log_warn "等待Nacos启动..."
sleep 5
done
log_info "Nacos已就绪"
log_info "启动RocketMQ NameServer..."
docker compose --env-file $ENV_FILE up -d rmqnamesrv
sleep 10
# Broker仅Node-02和Node-03启动
if [ "$NODE_ID" != "1" ]; then
log_info "启动RocketMQ Broker..."
docker compose --env-file $ENV_FILE up -d rmqbroker
sleep 10
fi
log_info "启动MinIO..."
docker compose --env-file $ENV_FILE up -d minio
sleep 10
log_info "启动Sentinel..."
docker compose --env-file $ENV_FILE up -d sentinel
log_info "基础设施启动完成!"
}
# 启动微服务
start_microservices() {
log_step "启动微服务..."
log_info "启动网关服务..."
docker compose --env-file $ENV_FILE up -d video-gateway
sleep 15
log_info "启动认证服务..."
docker compose --env-file $ENV_FILE up -d video-auth
sleep 15
log_info "启动用户服务..."
docker compose --env-file $ENV_FILE up -d video-user
sleep 15
log_info "启动内容服务..."
docker compose --env-file $ENV_FILE up -d video-content
sleep 15
log_info "启动评论服务..."
docker compose --env-file $ENV_FILE up -d video-comment
sleep 15
log_info "启动推荐服务..."
docker compose --env-file $ENV_FILE up -d video-recommend
sleep 15
log_info "启动管理后台服务..."
docker compose --env-file $ENV_FILE up -d video-admin
log_info "微服务启动完成!"
}
# 启动Nginx
start_nginx() {
log_step "启动Nginx负载均衡..."
docker compose --env-file $ENV_FILE up -d nginx
log_info "Nginx启动完成!"
}
# 停止所有服务
stop_all() {
log_step "停止所有服务..."
docker compose --env-file $ENV_FILE down
log_info "所有服务已停止"
}
# 查看状态
check_status() {
log_step "服务状态检查..."
echo ""
docker compose --env-file $ENV_FILE ps
echo ""
log_info "检查服务健康状态..."
services=("video-gateway:9000" "video-auth:9001" "video-user:9002" "video-content:9003" "video-comment:9004" "video-recommend:9005" "video-admin:9006")
for service in "${services[@]}"; do
name=$(echo $service | cut -d':' -f1)
port=$(echo $service | cut -d':' -f2)
if curl -s "http://localhost:$port/actuator/health" | grep -q "UP" 2>/dev/null; then
log_info "$name: ${GREEN}健康${NC}"
else
log_warn "$name: ${YELLOW}未响应${NC}"
fi
done
}
# 配置MySQL主从复制
setup_mysql_replication() {
log_step "配置MySQL主从复制..."
if [ "$NODE_ID" == "1" ]; then
log_info "配置主节点..."
# 创建复制用户
docker exec mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} <<EOF
CREATE USER IF NOT EXISTS 'repl'@'%' IDENTIFIED BY 'repl_password_2024';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;
EOF
log_info "主节点配置完成"
else
log_info "配置从节点..."
log_warn "请在主节点获取MASTER_LOG_FILE和MASTER_LOG_POS后手动执行:"
echo ""
echo "CHANGE MASTER TO"
echo " MASTER_HOST='192.168.1.101',"
echo " MASTER_USER='repl',"
echo " MASTER_PASSWORD='repl_password_2024',"
echo " MASTER_LOG_FILE='<从主节点获取>',"
echo " MASTER_LOG_POS=<从主节点获取>;"
echo "START SLAVE;"
echo ""
fi
}
# 配置Redis主从复制
setup_redis_replication() {
log_step "配置Redis主从复制..."
if [ "$NODE_ID" != "1" ]; then
log_info "配置从节点连接主节点..."
docker exec redis redis-cli -a ${REDIS_PASSWORD} REPLICAOF 192.168.1.101 6379
log_info "Redis从节点配置完成"
else
log_info "当前为主节点,无需配置"
fi
}
# 初始化MinIO
init_minio() {
log_step "初始化MinIO..."
# 等待MinIO就绪
until curl -s http://localhost:9000/minio/health/live > /dev/null 2>&1; do
log_warn "等待MinIO启动..."
sleep 5
done
log_info "创建存储桶..."
docker run --rm --network ha-network minio/mc <<EOF
mc alias set myminio http://minio:9000 minioadmin minioadmin123
mc mb myminio/video-platform --ignore-existing
mc anonymous set download myminio/video-platform
mc ls myminio
EOF
log_info "MinIO初始化完成"
}
# 主流程
case $ACTION in
init)
init_node
;;
start)
start_infrastructure
init_minio
start_microservices
start_nginx
;;
stop)
stop_all
;;
restart)
stop_all
sleep 10
start_infrastructure
start_microservices
start_nginx
;;
status)
check_status
;;
mysql-master|mysql-slave)
setup_mysql_replication
;;
redis-master|redis-slave)
setup_redis_replication
;;
infra)
start_infrastructure
;;
services)
start_microservices
;;
*)
log_error "未知操作: $ACTION"
echo "可用操作: init, start, stop, restart, status, mysql-master, mysql-slave, redis-master, redis-slave, infra, services"
exit 1
;;
esac
log_info "=========================================="
log_info "操作完成: $ACTION"
log_info "=========================================="
+511
View File
@@ -0,0 +1,511 @@
# ============================================================
# Node-01 Docker Compose 配置
# 主节点: MySQL Master, Redis Master, Nacos, NameServer, MinIO, 微服务
# 使用方式: docker compose --env-file config/.env.node-01 up -d
# ============================================================
services:
# ==================== 基础设施 ====================
mysql:
image: mysql:8.0
container_name: mysql
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-video_platform}
MYSQL_USER: ${MYSQL_USER:-video}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-video123}
volumes:
- mysql_data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
- ./config/mysql/my-master.cnf:/etc/mysql/conf.d/my.cnf:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --server-id=1
- --log-bin=mysql-bin
- --binlog-format=ROW
- --gtid-mode=ON
- --enforce-gtid-consistency=ON
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root123456}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- ha-network
redis:
image: redis:7.0-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./config/redis/redis-master.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis_HA_Cluster_2024}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
redis-sentinel:
image: redis:7.0-alpine
container_name: redis-sentinel
restart: always
ports:
- "26379:26379"
volumes:
- sentinel_data:/data
- ./config/redis/sentinel.conf:/usr/local/etc/redis/sentinel.conf
command: sh -c "cp /usr/local/etc/redis/sentinel.conf /data/sentinel.conf && redis-sentinel /data/sentinel.conf"
depends_on:
redis:
condition: service_healthy
networks:
- ha-network
nacos:
image: nacos/nacos-server:v2.3.2
container_name: nacos
restart: always
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
environment:
- MODE=cluster
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- NACOS_SERVER_IP=${HOST_IP:-192.168.1.101}
- NACOS_APPLICATION_PORT=8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos_config
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_TOKEN=SecretKey012345678901234567890123456789012345678901234567890123456789
- NACOS_AUTH_IDENTITY_KEY=serverIdentity
- NACOS_AUTH_IDENTITY_VALUE=security
- JVM_XMS=512m
- JVM_XMX=512m
volumes:
- nacos_logs:/home/nacos/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/liveness"]
interval: 10s
timeout: 5s
retries: 10
depends_on:
mysql:
condition: service_healthy
networks:
- ha-network
rmqnamesrv:
image: apache/rocketmq:5.1.4
container_name: rmqnamesrv
restart: always
ports:
- "9876:9876"
environment:
- JAVA_OPT_EXT=-server -Xms256m -Xmx256m
command: sh mqnamesrv
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'NamesrvStartup' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- ha-network
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- "9100:9000"
- "9101:9001"
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin123
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
sentinel:
image: sentinel-dashboard:1.8.6
container_name: sentinel
restart: always
ports:
- "8880:8080"
environment:
- JAVA_OPTS=-Xms128m -Xmx256m
- SERVER_PORT=8080
- DASHBOARD_SERVER=localhost:8080
- PROJECT_NAME=sentinel-dashboard
networks:
- ha-network
# ==================== 微服务 ====================
video-gateway:
build:
context: ./video-gateway
dockerfile: Dockerfile
container_name: video-gateway
restart: always
ports:
- "9000:9000"
- "8719:8719"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8719
- GATEWAY_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
nacos:
condition: service_healthy
networks:
- ha-network
video-auth:
build:
context: ./video-auth
dockerfile: Dockerfile
container_name: video-auth
restart: always
ports:
- "9001:9001"
- "8720:8720"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8720
- AUTH_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9001/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-user:
build:
context: ./video-user
dockerfile: Dockerfile
container_name: video-user
restart: always
ports:
- "9002:9002"
- "8721:8721"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- MINIO_HOST=minio
- MINIO_PORT=9000
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8721
- USER_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-content:
build:
context: ./video-content
dockerfile: Dockerfile
container_name: video-content
restart: always
ports:
- "9003:9003"
- "8722:8722"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- ROCKETMQ_HOST=rmqnamesrv
- ROCKETMQ_PORT=9876
- MINIO_HOST=minio
- MINIO_PORT=9000
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin123
- MINIO_BUCKET=video-platform
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8722
- CONTENT_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
rmqnamesrv:
condition: service_healthy
minio:
condition: service_healthy
networks:
- ha-network
video-comment:
build:
context: ./video-comment
dockerfile: Dockerfile
container_name: video-comment
restart: always
ports:
- "9004:9004"
- "8723:8723"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8723
- COMMENT_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-recommend:
build:
context: ./video-recommend
dockerfile: Dockerfile
container_name: video-recommend
restart: always
ports:
- "9005:9005"
- "8724:8724"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8724
- RECOMMEND_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-admin:
build:
context: ./video-admin
dockerfile: Dockerfile
container_name: video-admin
restart: always
ports:
- "9006:9006"
- "8725:8725"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.101}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=${HOST_IP:-192.168.1.101}
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.101}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8725
- ADMIN_IP=${HOST_IP:-192.168.1.101}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
# ==================== 负载均衡 ====================
nginx:
image: nginx:alpine
container_name: nginx-lb
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx/conf.d:/etc/nginx/conf.d:ro
- nginx_logs:/var/log/nginx
depends_on:
- video-gateway
networks:
- ha-network
networks:
ha-network:
driver: bridge
volumes:
mysql_data:
redis_data:
sentinel_data:
nacos_logs:
minio_data:
nginx_logs:
+485
View File
@@ -0,0 +1,485 @@
# ============================================================
# Node-02 Docker Compose 配置
# 从节点: MySQL Slave, Redis Slave, Nacos, NameServer, Broker-A, MinIO, 微服务
# 使用方式: docker compose --env-file config/.env.node-02 up -d
# ============================================================
services:
# ==================== 基础设施 ====================
mysql:
image: mysql:8.0
container_name: mysql
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-video_platform}
MYSQL_USER: ${MYSQL_USER:-video}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-video123}
volumes:
- mysql_data:/var/lib/mysql
# - ./sql:/docker-entrypoint-initdb.d # 从节点不需要初始化脚本,数据从主库同步
- ./config/mysql/my-slave.cnf:/etc/mysql/conf.d/my.cnf:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --server-id=2
- --log-bin=mysql-bin
- --binlog-format=ROW
- --gtid-mode=ON
- --enforce-gtid-consistency=ON
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root123456}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- ha-network
redis:
image: redis:7.0-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./config/redis/redis-slave.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis_HA_Cluster_2024}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
redis-sentinel:
image: redis:7.0-alpine
container_name: redis-sentinel
restart: always
ports:
- "26379:26379"
volumes:
- sentinel_data:/data
- ./config/redis/sentinel.conf:/usr/local/etc/redis/sentinel.conf
command: sh -c "cp /usr/local/etc/redis/sentinel.conf /data/sentinel.conf && redis-sentinel /data/sentinel.conf"
depends_on:
redis:
condition: service_healthy
networks:
- ha-network
nacos:
image: nacos/nacos-server:v2.3.2
container_name: nacos
restart: always
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
environment:
- MODE=cluster
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- NACOS_SERVER_IP=${HOST_IP:-192.168.1.102}
- NACOS_APPLICATION_PORT=8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos_config
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_TOKEN=SecretKey012345678901234567890123456789012345678901234567890123456789
- NACOS_AUTH_IDENTITY_KEY=serverIdentity
- NACOS_AUTH_IDENTITY_VALUE=security
- JVM_XMS=512m
- JVM_XMX=512m
volumes:
- nacos_logs:/home/nacos/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/liveness"]
interval: 10s
timeout: 5s
retries: 10
depends_on:
mysql:
condition: service_healthy
networks:
- ha-network
rmqnamesrv:
image: apache/rocketmq:5.1.4
container_name: rmqnamesrv
restart: always
ports:
- "9876:9876"
environment:
- JAVA_OPT_EXT=-server -Xms256m -Xmx256m
command: sh mqnamesrv
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'NamesrvStartup' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- ha-network
rmqbroker:
image: apache/rocketmq:5.1.4
user: root
container_name: rmqbroker
restart: always
ports:
- "10909:10909"
- "10911:10911"
- "10912:10912"
environment:
- NAMESRV_ADDR=${NAMESRV_ADDR:-192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876}
- JAVA_OPT_EXT=-server -Xms512m -Xmx512m
volumes:
- ./config/rocketmq/broker-a.conf:/home/rocketmq/rocketmq-5.1.4/conf/broker.conf:ro
- rmqbroker_logs:/home/rocketmq/logs
- rmqbroker_store:/home/rocketmq/store
command: sh mqbroker -c /home/rocketmq/rocketmq-5.1.4/conf/broker.conf
depends_on:
rmqnamesrv:
condition: service_healthy
networks:
- ha-network
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- "9100:9000"
- "9101:9001"
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin123
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
# ==================== 微服务 ====================
video-gateway:
build:
context: ./video-gateway
dockerfile: Dockerfile
container_name: video-gateway
restart: always
ports:
- "9000:9000"
- "8739:8739"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8739
- GATEWAY_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
networks:
- ha-network
video-auth:
build:
context: ./video-auth
dockerfile: Dockerfile
container_name: video-auth
restart: always
ports:
- "9001:9001"
- "8740:8740"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8740
- AUTH_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-user:
build:
context: ./video-user
dockerfile: Dockerfile
container_name: video-user
restart: always
ports:
- "9002:9002"
- "8741:8741"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8741
- USER_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-content:
build:
context: ./video-content
dockerfile: Dockerfile
container_name: video-content
restart: always
ports:
- "9003:9003"
- "8742:8742"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- ROCKETMQ_HOST=rmqnamesrv
- ROCKETMQ_PORT=9876
- MINIO_HOST=minio
- MINIO_PORT=9000
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin123
- MINIO_BUCKET=video-platform
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8742
- CONTENT_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
rmqnamesrv:
condition: service_healthy
minio:
condition: service_healthy
networks:
- ha-network
video-comment:
build:
context: ./video-comment
dockerfile: Dockerfile
container_name: video-comment
restart: always
ports:
- "9004:9004"
- "8743:8743"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8743
- COMMENT_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-recommend:
build:
context: ./video-recommend
dockerfile: Dockerfile
container_name: video-recommend
restart: always
ports:
- "9005:9005"
- "8744:8744"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8744
- RECOMMEND_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
video-admin:
build:
context: ./video-admin
dockerfile: Dockerfile
container_name: video-admin
restart: always
ports:
- "9006:9006"
- "8745:8745"
environment:
- SPRING_PROFILES_ACTIVE=ha
- NACOS_HOST=nacos
- NACOS_PORT=8848
- NACOS_USERNAME=nacos
- NACOS_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_USERNAME=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_PASSWORD=nacos
- SPRING_CLOUD_NACOS_CONFIG_USERNAME=nacos
- SPRING_CLOUD_NACOS_CONFIG_PASSWORD=nacos
- SPRING_CLOUD_NACOS_DISCOVERY_IP=${HOST_IP:-192.168.1.102}
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_HA_Cluster_2024}
- REDIS_SENTINEL_NODES=${REDIS_SENTINEL_NODES:-192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
- SENTINEL_DASHBOARD_HOST=192.168.1.101
- SENTINEL_DASHBOARD_PORT=8880
- SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP=${HOST_IP:-192.168.1.102}
- SPRING_CLOUD_SENTINEL_TRANSPORT_PORT=8745
- ADMIN_IP=${HOST_IP:-192.168.1.102}
- JAVA_OPTS=-Xms256m -Xmx512m
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ha-network
networks:
ha-network:
driver: bridge
volumes:
mysql_data:
redis_data:
sentinel_data:
nacos_logs:
rmqbroker_logs:
rmqbroker_store:
minio_data:
+188
View File
@@ -0,0 +1,188 @@
# ============================================================
# Node-03 Docker Compose 配置
# 从节点: MySQL Slave, Redis Slave, Nacos, NameServer, Broker-B, MinIO
# 仅部署基础设施,作为热备份节点
# 使用方式: docker compose --env-file config/.env.node-03 up -d
# ============================================================
services:
# ==================== 基础设施 ====================
mysql:
image: mysql:8.0
container_name: mysql
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-video_platform}
MYSQL_USER: ${MYSQL_USER:-video}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-video123}
volumes:
- mysql_data:/var/lib/mysql
# - ./sql:/docker-entrypoint-initdb.d # 从节点不需要初始化脚本,数据从主库同步
- ./config/mysql/my-slave.cnf:/etc/mysql/conf.d/my.cnf:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --server-id=3
- --log-bin=mysql-bin
- --binlog-format=ROW
- --gtid-mode=ON
- --enforce-gtid-consistency=ON
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-root123456}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- ha-network
redis:
image: redis:7.0-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./config/redis/redis-slave.conf:/usr/local/etc/redis/redis.conf:ro
command: redis-server /usr/local/etc/redis/redis.conf
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis_HA_Cluster_2024}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
redis-sentinel:
image: redis:7.0-alpine
container_name: redis-sentinel
restart: always
ports:
- "26379:26379"
volumes:
- sentinel_data:/data
- ./config/redis/sentinel.conf:/usr/local/etc/redis/sentinel.conf
command: sh -c "cp /usr/local/etc/redis/sentinel.conf /data/sentinel.conf && redis-sentinel /data/sentinel.conf"
depends_on:
redis:
condition: service_healthy
networks:
- ha-network
nacos:
image: nacos/nacos-server:v2.3.2
container_name: nacos
restart: always
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
environment:
- MODE=cluster
- NACOS_SERVERS=${NACOS_SERVERS:-192.168.1.101:8848,192.168.1.102:8848,192.168.1.103:8848}
- NACOS_SERVER_IP=${HOST_IP:-192.168.1.103}
- NACOS_APPLICATION_PORT=8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos_config
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=${MYSQL_ROOT_PASSWORD:-root123456}
- MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_TOKEN=SecretKey012345678901234567890123456789012345678901234567890123456789
- NACOS_AUTH_IDENTITY_KEY=serverIdentity
- NACOS_AUTH_IDENTITY_VALUE=security
- JVM_XMS=512m
- JVM_XMX=512m
volumes:
- nacos_logs:/home/nacos/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/liveness"]
interval: 10s
timeout: 5s
retries: 10
depends_on:
mysql:
condition: service_healthy
networks:
- ha-network
rmqnamesrv:
image: apache/rocketmq:5.1.4
container_name: rmqnamesrv
restart: always
ports:
- "9876:9876"
environment:
- JAVA_OPT_EXT=-server -Xms256m -Xmx256m
command: sh mqnamesrv
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'NamesrvStartup' || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- ha-network
rmqbroker:
image: apache/rocketmq:5.1.4
user: root
container_name: rmqbroker
restart: always
ports:
- "10909:10909"
- "10911:10911"
- "10912:10912"
environment:
- NAMESRV_ADDR=${NAMESRV_ADDR:-192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876}
- JAVA_OPT_EXT=-server -Xms512m -Xmx512m
volumes:
- ./config/rocketmq/broker-b.conf:/home/rocketmq/rocketmq-5.1.4/conf/broker.conf:ro
- rmqbroker_logs:/home/rocketmq/logs
- rmqbroker_store:/home/rocketmq/store
command: sh mqbroker -c /home/rocketmq/rocketmq-5.1.4/conf/broker.conf
depends_on:
rmqnamesrv:
condition: service_healthy
networks:
- ha-network
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- "9000:9000"
- "9001:9001"
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin123
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ha-network
networks:
ha-network:
driver: bridge
volumes:
mysql_data:
redis_data:
sentinel_data:
nacos_logs:
rmqbroker_logs:
rmqbroker_store:
minio_data:
+287
View File
@@ -0,0 +1,287 @@
version: '3.8'
name: video-platform
services:
# 基础设施服务
mysql:
image: mysql:8.0
container_name: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: video_platform
MYSQL_USER: video
MYSQL_PASSWORD: video123
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password
networks:
- video-network
redis:
image: redis:7.0-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --requirepass redis_EsJ8FX
networks:
- video-network
nacos:
image: nacos/nacos-server:v2.3.2
container_name: nacos
restart: always
environment:
- MODE=standalone
- JVM_XMS=512m
- JVM_XMX=512m
# 添加Nacos参数,确保正确处理服务注册
- NACOS_AUTH_ENABLE=false
- PREFER_HOST_MODE=hostname
- NACOS_SERVER_IP=${HOST_IP:-127.0.0.1}
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
networks:
- video-network
rmqnamesrv:
image: apache/rocketmq:5.1.4
container_name: rmqnamesrv
restart: always
ports:
- "9876:9876"
command: sh mqnamesrv
networks:
- video-network
rmqbroker:
image: apache/rocketmq:5.1.4
container_name: rmqbroker
restart: always
ports:
- "10909:10909"
- "10911:10911"
environment:
- NAMESRV_ADDR=rmqnamesrv:9876
- JAVA_OPTS=-Duser.home=/opt
command: sh mqbroker -c /home/rocketmq/rocketmq-5.1.4/conf/broker.conf
depends_on:
- rmqnamesrv
networks:
- video-network
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- "9002:9000"
- "9001:9001"
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
networks:
- video-network
sentinel:
image: sentinel-dashboard:1.8.6
container_name: sentinel
restart: always
ports:
- "8880:8080"
networks:
- video-network
# 微服务
video-gateway:
build:
context: ./video-gateway
dockerfile: Dockerfile
container_name: video-gateway
restart: always
ports:
- "9000:9000"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- SENTINEL_HOST=sentinel
- SENTINEL_PORT=8080
- JAVA_OPTS=-Xms128m -Xmx128m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
# 添加IP环境变量,用于服务注册
- GATEWAY_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
deploy:
resources:
limits:
memory: 256M
networks:
- video-network
video-auth:
build:
context: ./video-auth
dockerfile: Dockerfile
container_name: video-auth
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
# 添加IP环境变量,用于服务注册
- AUTH_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
networks:
- video-network
video-user:
build:
context: ./video-user
dockerfile: Dockerfile
container_name: video-user
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
# 添加IP环境变量,用于服务注册
- USER_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
networks:
- video-network
video-content:
build:
context: ./video-content
dockerfile: Dockerfile
container_name: video-content
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
- ROCKETMQ_HOST=rmqnamesrv
- ROCKETMQ_PORT=9876
- MINIO_HOST=minio
- MINIO_PORT=9000
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin
- MINIO_BUCKET=video-platform
# 添加IP环境变量,用于服务注册
- CONTENT_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
- rmqnamesrv
- minio
networks:
- video-network
video-admin:
build:
context: ./video-admin
dockerfile: Dockerfile
container_name: video-admin
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
# 添加IP环境变量,用于服务注册
- ADMIN_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
networks:
- video-network
video-comment:
build:
context: ./video-comment
dockerfile: Dockerfile
container_name: video-comment
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
# 添加IP环境变量,用于服务注册
- COMMENT_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
networks:
- video-network
video-recommend:
build:
context: ./video-recommend
dockerfile: Dockerfile
container_name: video-recommend
restart: always
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=8848
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- REDIS_HOST=redis
- REDIS_PORT=6379
# 添加IP环境变量,用于服务注册
- RECOMMEND_IP=${HOST_IP:-127.0.0.1}
depends_on:
- nacos
- mysql
- redis
networks:
- video-network
networks:
video-network:
driver: bridge
volumes:
mysql_data:
redis_data:
minio_data:
+131
View File
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.nuaa.video</groupId>
<artifactId>video-platform</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>video-platform</name>
<description>基于Spring Cloud的高可用在线视频平台</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 全局dependencies部分 -->
<dependencies>
<!-- 所有子模块共享的依赖 -->
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- H2 Database for testing -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<modules>
<module>video-common</module>
<module>video-gateway</module>
<module>video-auth</module>
<module>video-user</module>
<module>video-content</module>
<module>video-comment</module>
<module>video-recommend</module>
<module>video-admin</module>
</modules>
<build>
<pluginManagement>
<plugins>
<!-- Spring Boot Maven Plugin - 子模块需要显式声明才生效 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
+266
View File
@@ -0,0 +1,266 @@
#!/bin/bash
# ============================================================
# Debian 12 系统初始化脚本
# 用途: 在 Debian 12 上安装 Docker、Java 17、Maven 等依赖
# 使用: chmod +x init-debian12.sh && sudo ./init-debian12.sh
# ============================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# 检查是否为 root 用户
if [ "$EUID" -ne 0 ]; then
log_error "请使用 root 用户或 sudo 执行此脚本"
exit 1
fi
# 检查系统版本
if [ ! -f /etc/os-release ]; then
log_error "无法检测系统版本"
exit 1
fi
. /etc/os-release
log_info "检测到系统: $PRETTY_NAME"
if [ "$ID" != "debian" ]; then
log_warn "此脚本专为 Debian 设计,当前系统: $ID"
read -p "是否继续? (y/n): " confirm
if [ "$confirm" != "y" ]; then
exit 0
fi
fi
log_step "=========================================="
log_step "开始初始化 Debian 12 系统"
log_step "=========================================="
# 1. 更新系统
log_step "[1/8] 更新系统软件包..."
apt-get update
apt-get upgrade -y
apt-get dist-upgrade -y
# 2. 安装基础工具
log_step "[2/8] 安装基础工具..."
apt-get install -y \
curl \
wget \
git \
vim \
net-tools \
iputils-ping \
telnet \
netcat-openbsd \
lsof \
htop \
tree \
unzip \
gnupg \
apt-transport-https \
ca-certificates \
software-properties-common
# 3. 安装 Java 17
log_step "[3/8] 安装 OpenJDK 17..."
apt-get install -y openjdk-17-jdk openjdk-17-jre
# 验证 Java 安装
java -version
log_info "Java 安装完成: $(java -version 2>&1 | head -n 1)"
# 设置 JAVA_HOME
if ! grep -q "JAVA_HOME" /etc/profile; then
echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' >> /etc/profile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile
log_info "已设置 JAVA_HOME 环境变量"
fi
# 4. 安装 Maven
log_step "[4/8] 安装 Maven..."
apt-get install -y maven
# 验证 Maven 安装
mvn -version
log_info "Maven 安装完成"
# 配置 Maven 镜像 (阿里云)
MAVEN_SETTINGS="/usr/share/maven/conf/settings.xml"
if [ -f "$MAVEN_SETTINGS" ]; then
if ! grep -q "aliyun" "$MAVEN_SETTINGS"; then
log_info "配置 Maven 阿里云镜像..."
sed -i '/<mirrors>/a \
<mirror>\
<id>aliyun</id>\
<mirrorOf>central</mirrorOf>\
<name>Aliyun Maven Mirror</name>\
<url>https://maven.aliyun.com/repository/public</url>\
</mirror>' "$MAVEN_SETTINGS"
fi
fi
# 5. 安装 Docker
log_step "[5/8] 安装 Docker..."
# 添加 Docker 官方 GPG 密钥
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 软件源
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 启动并设置开机自启
systemctl start docker
systemctl enable docker
# 验证 Docker 安装
docker --version
docker compose version
log_info "Docker 安装完成"
# 将当前非 root 用户添加到 docker 组
if [ -n "$SUDO_USER" ]; then
usermod -aG docker "$SUDO_USER"
log_info "已将用户 $SUDO_USER 添加到 docker 组"
fi
# 6. 配置 Docker 镜像加速 (国内镜像)
log_step "[6/8] 配置 Docker 镜像加速..."
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"storage-driver": "overlay2",
"live-restore": true
}
EOF
systemctl daemon-reload
systemctl restart docker
log_info "Docker 镜像加速配置完成"
# 7. 配置系统参数
log_step "[7/8] 优化系统参数..."
# 内核参数优化
cat > /etc/sysctl.d/99-ha-ovp.conf << 'EOF'
# 网络优化
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
# 内存优化
vm.swappiness = 10
vm.overcommit_memory = 1
vm.max_map_count = 262144
# 文件描述符
fs.file-max = 655350
fs.nr_open = 655350
# 对于 Redis
net.core.somaxconn = 65535
vm.overcommit_memory = 1
EOF
sysctl -p /etc/sysctl.d/99-ha-ovp.conf
log_info "系统参数优化完成"
# 文件描述符限制
cat > /etc/security/limits.d/99-ha-ovp.conf << 'EOF'
* soft nofile 655350
* hard nofile 655350
* soft nproc 655350
* hard nproc 655350
root soft nofile 655350
root hard nofile 655350
root soft nproc 655350
root hard nproc 655350
EOF
log_info "文件描述符限制配置完成"
# 8. 配置防火墙
log_step "[8/8] 配置防火墙..."
apt-get install -y ufw
# 设置默认策略
ufw default deny incoming
ufw default allow outgoing
# 允许 SSH
ufw allow 22/tcp
# 允许项目端口
# 基础设施端口
ufw allow 3306/tcp comment 'MySQL'
ufw allow 6379/tcp comment 'Redis'
ufw allow 26379/tcp comment 'Redis Sentinel'
ufw allow 8848:8849/tcp comment 'Nacos'
ufw allow 9876/tcp comment 'RocketMQ NameServer'
ufw allow 10909:10912/tcp comment 'RocketMQ Broker'
ufw allow 9000:9001/tcp comment 'MinIO'
ufw allow 8880/tcp comment 'Sentinel Dashboard'
# 微服务端口
ufw allow 9000:9006/tcp comment 'Microservices'
# Nginx
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
# 启用防火墙 (谨慎操作,确保 SSH 已开放)
# ufw enable
log_warn "防火墙已配置但未启用,请手动执行 'ufw enable' 启用"
log_step "=========================================="
log_step "系统初始化完成!"
log_step "=========================================="
# 显示安装信息
echo ""
log_info "安装版本信息:"
echo " - Java: $(java -version 2>&1 | head -n 1)"
echo " - Maven: $(mvn -version 2>&1 | head -n 1)"
echo " - Docker: $(docker --version)"
echo " - Docker Compose: $(docker compose version)"
echo ""
log_info "请执行以下命令使环境变量生效:"
echo " source /etc/profile"
echo ""
log_info "防火墙配置已完成,请根据需要执行:"
echo " ufw enable # 启用防火墙"
echo " ufw status # 查看状态"
echo ""
log_info "下一步: 请克隆项目并执行部署脚本"
+22
View File
@@ -0,0 +1,22 @@
FROM eclipse-temurin:8-jre
# 下载 Sentinel 1.8.6 官方 JAR
RUN apt-get update && \
apt-get install -y wget && \
wget -O /sentinel-dashboard.jar \
https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar && \
apt-get remove -y wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
# 创建启动脚本
RUN echo '#!/bin/sh\n\
exec java ${JAVA_OPTS} -Dserver.port=${SERVER_PORT:-8080} -Dcsp.sentinel.dashboard.server=${DASHBOARD_SERVER:-localhost:8080} -Dproject.name=${PROJECT_NAME:-sentinel-dashboard} -jar /sentinel-dashboard.jar\n\
' > /entrypoint.sh && chmod +x /entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
+778
View File
@@ -0,0 +1,778 @@
-- 1. 创建数据库
CREATE DATABASE IF NOT EXISTS `video_auth` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `video_user` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `video_content` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `video_comment` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `video_recommend` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `video_admin` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建Nacos配置数据库
CREATE DATABASE IF NOT EXISTS nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 2. 认证数据库(video_auth)
USE `video_auth`;
-- 用户认证表
CREATE TABLE IF NOT EXISTS `auth_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(256) NOT NULL COMMENT '密码(加密)',
`salt` varchar(64) NOT NULL COMMENT '加密盐值',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:正常)',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`last_login_ip` varchar(64) DEFAULT NULL COMMENT '最后登录IP',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`),
UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户认证表';
-- 用户角色表
CREATE TABLE IF NOT EXISTS `auth_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(64) NOT NULL COMMENT '角色编码(USER_开头)',
`description` varchar(256) DEFAULT NULL COMMENT '角色描述',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户角色表';
-- 用户权限表
CREATE TABLE IF NOT EXISTS `auth_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`name` varchar(64) NOT NULL COMMENT '权限名称',
`code` varchar(64) NOT NULL COMMENT '权限编码',
`description` varchar(256) DEFAULT NULL COMMENT '权限描述',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户权限表';
-- 用户角色关联表
CREATE TABLE IF NOT EXISTS `auth_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_role` (`user_id`,`role_id`),
CONSTRAINT `fk_aur_user` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_aur_role` FOREIGN KEY (`role_id`) REFERENCES `auth_role` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户角色关联表';
-- 角色权限关联表
CREATE TABLE IF NOT EXISTS `auth_role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`permission_id` bigint NOT NULL COMMENT '权限ID',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_role_permission` (`role_id`,`permission_id`),
CONSTRAINT `fk_arp_role` FOREIGN KEY (`role_id`) REFERENCES `auth_role` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_arp_permission` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色权限关联表';
-- OAuth2客户端表
CREATE TABLE IF NOT EXISTS `auth_oauth_client` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`client_id` varchar(64) NOT NULL COMMENT '客户端ID',
`client_secret` varchar(256) NOT NULL COMMENT '客户端密钥',
`client_name` varchar(128) NOT NULL COMMENT '客户端名称',
`client_uri` varchar(256) DEFAULT NULL COMMENT '客户端主页',
`redirect_uri` varchar(256) DEFAULT NULL COMMENT '重定向URI',
`scope` varchar(128) NOT NULL COMMENT '授权范围',
`grant_types` varchar(128) NOT NULL COMMENT '授权类型',
`access_token_validity` int NOT NULL DEFAULT '3600' COMMENT '访问令牌有效期(秒)',
`refresh_token_validity` int NOT NULL DEFAULT '86400' COMMENT '刷新令牌有效期(秒)',
`additional_information` text COMMENT '附加信息',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:正常)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_client_id` (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='OAuth2客户端表';
-- 初始化认证数据
INSERT IGNORE INTO `auth_role` (`id`, `name`, `code`, `description`) VALUES
(1, '管理员', 'USER_ADMIN', '系统管理员'),
(2, '普通用户', 'USER_NORMAL', '普通用户'),
(3, 'VIP用户', 'USER_VIP', 'VIP用户'),
(4, '内容创作者', 'USER_CREATOR', '内容创作专用角色');
INSERT IGNORE INTO `auth_permission` (`id`, `name`, `code`, `description`) VALUES
(1, '视频上传', 'video:upload', '视频上传权限'),
(2, '视频管理', 'video:manage', '视频管理权限'),
(3, '评论管理', 'comment:manage', '评论管理权限'),
(4, '个人资料管理', 'profile:manage', '个人资料管理权限');
INSERT IGNORE INTO `auth_role_permission` (`role_id`, `permission_id`) VALUES
(1, 1), (1, 2), (1, 3), (1, 4),
(3, 1), (3, 4),
(4, 1), (4, 3), (4, 4);
INSERT IGNORE INTO `auth_oauth_client` (`client_id`, `client_secret`, `client_name`, `redirect_uri`, `scope`, `grant_types`, `access_token_validity`, `refresh_token_validity`) VALUES
('video-web', '$2a$10$oRZ7x/.dW.6Q9z0QY1JZ5e9ZQ9z0QY1JZ5e9ZQ9z0QY1JZ5e9ZQ', '视频网站前端', 'http://localhost:8080', 'all', 'password,refresh_token', 3600, 86400),
('video-admin', '$2a$10$oRZ7x/.dW.6Q9z0QY1JZ5e9ZQ9z0QY1JZ5e9ZQ9z0QY1JZ5e9ZQ', '视频管理后台', 'http://localhost:8081', 'all', 'authorization_code,refresh_token', 3600, 86400);
-- 3. 用户数据库(video_user)
USE `video_user`;
-- 用户信息表
CREATE TABLE IF NOT EXISTS `user_info` (
`id` bigint NOT NULL COMMENT '用户ID',
`nickname` varchar(64) NOT NULL COMMENT '昵称',
`avatar` varchar(256) DEFAULT NULL COMMENT '头像URL',
`gender` tinyint DEFAULT '0' COMMENT '性别(0:未知,1:男,2:女)',
`birthday` date DEFAULT NULL COMMENT '生日',
`signature` varchar(256) DEFAULT NULL COMMENT '个性签名',
`region` varchar(128) DEFAULT NULL COMMENT '地区',
`follower_count` int NOT NULL DEFAULT '0' COMMENT '粉丝数',
`following_count` int NOT NULL DEFAULT '0' COMMENT '关注数',
`video_count` int NOT NULL DEFAULT '0' COMMENT '视频数',
`like_count` int NOT NULL DEFAULT '0' COMMENT '获赞数',
`coin_count` int NOT NULL DEFAULT '0' COMMENT '硬币数',
`favorite_count` int NOT NULL DEFAULT '0' COMMENT '收藏数',
`view_count` bigint NOT NULL DEFAULT '0' COMMENT '播放数',
`level` tinyint NOT NULL DEFAULT '1' COMMENT '等级',
`vip_level` tinyint NOT NULL DEFAULT '0' COMMENT 'VIP等级',
`vip_expire_time` datetime DEFAULT NULL COMMENT 'VIP到期时间',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
CONSTRAINT `fk_ui_user` FOREIGN KEY (`id`) REFERENCES `video_auth`.`auth_user` (`id`) ON DELETE CASCADE,
KEY `idx_nickname` (`nickname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户信息表';
-- 用户统计表(按天)
CREATE TABLE IF NOT EXISTS `user_stat_daily` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`stat_date` date NOT NULL COMMENT '统计日期',
`follower_increment` int NOT NULL DEFAULT '0' COMMENT '新增粉丝数',
`following_increment` int NOT NULL DEFAULT '0' COMMENT '新增关注数',
`video_increment` int NOT NULL DEFAULT '0' COMMENT '新增视频数',
`like_increment` int NOT NULL DEFAULT '0' COMMENT '新增获赞数',
`coin_increment` int NOT NULL DEFAULT '0' COMMENT '新增硬币数',
`favorite_increment` int NOT NULL DEFAULT '0' COMMENT '新增收藏数',
`view_increment` bigint NOT NULL DEFAULT '0' COMMENT '新增播放数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_date` (`user_id`,`stat_date`),
CONSTRAINT `fk_usd_user` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户每日统计表';
-- 用户关注表
CREATE TABLE IF NOT EXISTS `user_follow` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`follow_id` bigint NOT NULL COMMENT '关注用户ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_follow` (`user_id`,`follow_id`),
KEY `idx_follow` (`follow_id`),
CONSTRAINT `fk_uf_user` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_uf_follow` FOREIGN KEY (`follow_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注表';
-- 用户私信表
CREATE TABLE IF NOT EXISTS `user_message` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`sender_id` bigint NOT NULL COMMENT '发送者ID',
`receiver_id` bigint NOT NULL COMMENT '接收者ID',
`content` text NOT NULL COMMENT '消息内容',
`is_read` tinyint NOT NULL DEFAULT '0' COMMENT '是否已读(0:未读,1:已读)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_sender` (`sender_id`),
KEY `idx_receiver` (`receiver_id`),
KEY `idx_receiver_read` (`receiver_id`,`is_read`),
CONSTRAINT `fk_um_sender` FOREIGN KEY (`sender_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_um_receiver` FOREIGN KEY (`receiver_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信表';
-- 用户通知表
CREATE TABLE IF NOT EXISTS `user_notification` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`type` tinyint NOT NULL COMMENT '通知类型(1:点赞,2:评论,3:关注,4:系统通知)',
`content` text NOT NULL COMMENT '通知内容',
`related_id` bigint DEFAULT NULL COMMENT '相关ID',
`is_read` tinyint NOT NULL DEFAULT '0' COMMENT '是否已读(0:未读,1:已读)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_user_read` (`user_id`,`is_read`),
CONSTRAINT `fk_un_user` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户通知表';
-- 用户设置表
CREATE TABLE IF NOT EXISTS `user_setting` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`privacy_level` tinyint NOT NULL DEFAULT '1' COMMENT '隐私等级(1:公开,2:仅关注,3:私密)',
`comment_notify` tinyint NOT NULL DEFAULT '1' COMMENT '评论通知(0:关闭,1:开启)',
`like_notify` tinyint NOT NULL DEFAULT '1' COMMENT '点赞通知(0:关闭,1:开启)',
`follow_notify` tinyint NOT NULL DEFAULT '1' COMMENT '关注通知(0:关闭,1:开启)',
`system_notify` tinyint NOT NULL DEFAULT '1' COMMENT '系统通知(0:关闭,1:开启)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user` (`user_id`),
CONSTRAINT `fk_us_user` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户设置表';
-- 4. 内容数据库(video_content)
USE `video_content`;
-- 视频分类表
CREATE TABLE IF NOT EXISTS `content_category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`name` varchar(64) NOT NULL COMMENT '分类名称',
`code` varchar(64) NOT NULL COMMENT '分类编码',
`parent_id` bigint DEFAULT '0' COMMENT '父分类ID',
`level` tinyint NOT NULL DEFAULT '1' COMMENT '分类层级',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序',
`icon` varchar(256) DEFAULT NULL COMMENT '图标',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:正常)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`),
KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频分类表';
-- 视频标签表
CREATE TABLE IF NOT EXISTS `content_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '标签ID',
`name` varchar(64) NOT NULL COMMENT '标签名称',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频标签表';
-- 视频信息表
CREATE TABLE IF NOT EXISTS `content_video` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`title` varchar(256) NOT NULL COMMENT '视频标题',
`description` text COMMENT '视频描述',
`cover_url` varchar(256) NOT NULL COMMENT '封面URL',
`video_url` varchar(256) NOT NULL COMMENT '视频URL',
`duration` int NOT NULL COMMENT '视频时长(秒)',
`width` int NOT NULL COMMENT '视频宽度',
`height` int NOT NULL COMMENT '视频高度',
`format` varchar(32) NOT NULL COMMENT '视频格式',
`size` bigint NOT NULL COMMENT '视频大小(字节)',
`category_id` bigint NOT NULL COMMENT '分类ID',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态(0:审核中,1:已发布,2:已下架,3:审核不通过)',
`like_count` int NOT NULL DEFAULT '0' COMMENT '点赞数',
`coin_count` int NOT NULL DEFAULT '0' COMMENT '投币数',
`favorite_count` int NOT NULL DEFAULT '0' COMMENT '收藏数',
`comment_count` int NOT NULL DEFAULT '0' COMMENT '评论数',
`view_count` bigint NOT NULL DEFAULT '0' COMMENT '播放数',
`share_count` int NOT NULL DEFAULT '0' COMMENT '分享数',
`danmaku_count` int NOT NULL DEFAULT '0' COMMENT '弹幕数',
`is_recommend` tinyint NOT NULL DEFAULT '0' COMMENT '是否推荐(0:否,1:是)',
`is_top` tinyint NOT NULL DEFAULT '0' COMMENT '是否置顶(0:否,1:是)',
`publish_time` datetime DEFAULT NULL COMMENT '发布时间',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user_status` (`user_id`, `status`),
KEY `idx_category_status` (`category_id`, `status`),
KEY `idx_recommend` (`is_recommend`),
KEY `idx_top` (`is_top`),
FULLTEXT KEY `ft_title_desc` (`title`,`description`),
CONSTRAINT `fk_cv_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cv_category` FOREIGN KEY (`category_id`) REFERENCES `content_category` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频信息表';
-- 视频标签关联表
CREATE TABLE IF NOT EXISTS `content_video_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`tag_id` bigint NOT NULL COMMENT '标签ID',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_tag` (`video_id`,`tag_id`),
KEY `idx_tag` (`tag_id`),
CONSTRAINT `fk_cvt_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvt_tag` FOREIGN KEY (`tag_id`) REFERENCES `content_tag` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频标签关联表';
-- 视频统计表(按天)
CREATE TABLE IF NOT EXISTS `content_video_stat_daily` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`stat_date` date NOT NULL COMMENT '统计日期',
`like_increment` int NOT NULL DEFAULT '0' COMMENT '新增点赞数',
`coin_increment` int NOT NULL DEFAULT '0' COMMENT '新增投币数',
`favorite_increment` int NOT NULL DEFAULT '0' COMMENT '新增收藏数',
`comment_increment` int NOT NULL DEFAULT '0' COMMENT '新增评论数',
`view_increment` bigint NOT NULL DEFAULT '0' COMMENT '新增播放数',
`share_increment` int NOT NULL DEFAULT '0' COMMENT '新增分享数',
`danmaku_increment` int NOT NULL DEFAULT '0' COMMENT '新增弹幕数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_date` (`video_id`,`stat_date`),
CONSTRAINT `fk_cvsd_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频每日统计表';
-- 视频审核记录表
CREATE TABLE IF NOT EXISTS `content_video_review` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`reviewer_id` bigint NOT NULL COMMENT '审核员ID',
`status` tinyint NOT NULL COMMENT '审核状态(1:通过,2:不通过)',
`reason` varchar(256) DEFAULT NULL COMMENT '审核原因',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_video` (`video_id`),
KEY `idx_reviewer` (`reviewer_id`),
CONSTRAINT `fk_cvr_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvr_reviewer` FOREIGN KEY (`reviewer_id`) REFERENCES `video_auth`.`auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频审核记录表';
-- 视频点赞表
CREATE TABLE IF NOT EXISTS `content_video_like` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_user` (`video_id`,`user_id`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cvl_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvl_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频点赞表';
-- 视频投币表
CREATE TABLE IF NOT EXISTS `content_video_coin` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`amount` int NOT NULL DEFAULT '1' COMMENT '投币数量',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_user` (`video_id`,`user_id`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cvc_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvc_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频投币表';
-- 用户收藏夹表
CREATE TABLE IF NOT EXISTS `content_user_favorite` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '收藏夹ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`name` varchar(64) NOT NULL COMMENT '收藏夹名称',
`description` varchar(256) DEFAULT NULL COMMENT '收藏夹描述',
`is_private` tinyint NOT NULL DEFAULT '0' COMMENT '是否私有(0:公开,1:私有)',
`video_count` int NOT NULL DEFAULT '0' COMMENT '视频数量',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cuf_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户收藏夹表';
-- 视频收藏表
CREATE TABLE IF NOT EXISTS `content_video_favorite` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`favorite_id` bigint NOT NULL COMMENT '收藏夹ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_user_favorite` (`video_id`,`user_id`,`favorite_id`),
KEY `idx_user_favorite` (`user_id`,`favorite_id`),
KEY `idx_video` (`video_id`),
CONSTRAINT `fk_cvf_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvf_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvf_favorite` FOREIGN KEY (`favorite_id`) REFERENCES `content_user_favorite` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频收藏表';
-- 弹幕表
CREATE TABLE IF NOT EXISTS `content_danmaku` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '弹幕ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`content` varchar(256) NOT NULL COMMENT '弹幕内容',
`color` varchar(16) NOT NULL COMMENT '弹幕颜色',
`font_size` tinyint NOT NULL COMMENT '字体大小',
`position` tinyint NOT NULL COMMENT '位置(0:滚动,1:顶部,2:底部)',
`time` float NOT NULL COMMENT '发送时间(秒)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_video_time` (`video_id`,`time`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cd_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cd_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='弹幕表';
-- 视频播放记录表
CREATE TABLE IF NOT EXISTS `content_video_play_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`play_time` int NOT NULL DEFAULT '0' COMMENT '播放时长(秒)',
`is_finished` tinyint NOT NULL DEFAULT '0' COMMENT '是否看完(0:未看完,1:已看完)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_user` (`video_id`,`user_id`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cvpr_video` FOREIGN KEY (`video_id`) REFERENCES `content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cvpr_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频播放记录表';
-- 初始化分类数据
INSERT IGNORE INTO `content_category` (`id`, `name`, `code`, `parent_id`, `level`, `sort`) VALUES
(1, '生活', 'life', 0, 1, 1),
(2, '科技', 'tech', 0, 1, 2),
(3, '游戏', 'game', 0, 1, 3),
(4, '美食', 'food', 0, 1, 4),
(5, '音乐', 'music', 0, 1, 5),
(6, '影视', 'movie', 0, 1, 6),
(7, '知识', 'knowledge', 0, 1, 7),
(8, '数码', 'digital', 2, 2, 1),
(9, '软件', 'software', 2, 2, 2),
(10, '硬件', 'hardware', 2, 2, 3),
(11, '手机游戏', 'mobile_game', 3, 2, 1),
(12, '电脑游戏', 'pc_game', 3, 2, 2),
(13, '主机游戏', 'console_game', 3, 2, 3);
-- 5. 评论数据库(video_comment)
USE `video_comment`;
-- 评论表
CREATE TABLE IF NOT EXISTS `comment` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '评论ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`content` text NOT NULL COMMENT '评论内容',
`like_count` int NOT NULL DEFAULT '0' COMMENT '点赞数',
`reply_count` int NOT NULL DEFAULT '0' COMMENT '回复数',
`top_status` tinyint NOT NULL DEFAULT '0' COMMENT '置顶状态(0:普通,1:置顶)',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:删除,1:正常)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_video` (`video_id`),
KEY `idx_user` (`user_id`),
KEY `idx_video_top` (`video_id`,`top_status`),
FULLTEXT KEY `ft_content` (`content`),
CONSTRAINT `fk_c_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_c_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论表';
-- 评论回复表
CREATE TABLE IF NOT EXISTS `comment_reply` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '回复ID',
`comment_id` bigint NOT NULL COMMENT '评论ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`reply_to_id` bigint DEFAULT NULL COMMENT '回复目标用户ID',
`content` text NOT NULL COMMENT '回复内容',
`like_count` int NOT NULL DEFAULT '0' COMMENT '点赞数',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:删除,1:正常)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_comment` (`comment_id`),
KEY `idx_video` (`video_id`),
KEY `idx_user` (`user_id`),
KEY `idx_reply_to` (`reply_to_id`),
KEY `idx_video_time` (`video_id`, `create_time`),
FULLTEXT KEY `ft_content` (`content`),
CONSTRAINT `fk_cr_comment` FOREIGN KEY (`comment_id`) REFERENCES `comment` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cr_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`),
CONSTRAINT `fk_cr_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cr_reply_to` FOREIGN KEY (`reply_to_id`) REFERENCES `video_user`.`user_info` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论回复表';
-- 评论点赞表
CREATE TABLE IF NOT EXISTS `comment_like` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`comment_id` bigint NOT NULL COMMENT '评论ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`type` tinyint NOT NULL COMMENT '类型(1:评论,2:回复)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_comment_user_type` (`comment_id`,`user_id`,`type`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_cl_comment` FOREIGN KEY (`comment_id`) REFERENCES `comment` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_cl_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论点赞表';
-- 6. 推荐数据库(video_recommend)
USE `video_recommend`;
-- 用户兴趣标签表
CREATE TABLE IF NOT EXISTS `recommend_user_interest` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`tag_id` bigint NOT NULL COMMENT '标签ID',
`weight` float NOT NULL DEFAULT '1.0' COMMENT '权重',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_tag` (`user_id`,`tag_id`),
KEY `idx_tag` (`tag_id`),
CONSTRAINT `fk_rui_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_rui_tag` FOREIGN KEY (`tag_id`) REFERENCES `video_content`.`content_tag` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户兴趣标签表';
-- 用户行为记录表
CREATE TABLE IF NOT EXISTS `recommend_user_behavior` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`behavior_type` tinyint NOT NULL COMMENT '行为类型(1:浏览,2:点赞,3:收藏,4:评论,5:分享,6:完整观看)',
`behavior_weight` float NOT NULL DEFAULT '1.0' COMMENT '行为权重',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_video` (`user_id`,`video_id`),
KEY `idx_video` (`video_id`),
KEY `idx_user_behavior` (`user_id`,`behavior_type`),
KEY `idx_create_time` (`create_time`),
CONSTRAINT `fk_rub_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_rub_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户行为记录表';
-- 视频相似度表
CREATE TABLE IF NOT EXISTS `recommend_video_similarity` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`similar_video_id` bigint NOT NULL COMMENT '相似视频ID',
`similarity` float NOT NULL COMMENT '相似度',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_similar` (`video_id`,`similar_video_id`),
KEY `idx_similar_video` (`similar_video_id`),
CONSTRAINT `fk_rvs_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_rvs_similar_video` FOREIGN KEY (`similar_video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频相似度表';
-- 用户推荐表
CREATE TABLE IF NOT EXISTS `recommend_user_recommendation` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`recommend_type` tinyint NOT NULL COMMENT '推荐类型(1:基于内容,2:协同过滤,3:热门推荐,4:新视频推荐)',
`score` float NOT NULL COMMENT '推荐分数',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_video_type` (`user_id`,`video_id`,`recommend_type`),
KEY `idx_user_type` (`user_id`,`recommend_type`),
KEY `idx_video` (`video_id`),
CONSTRAINT `fk_rur_user` FOREIGN KEY (`user_id`) REFERENCES `video_user`.`user_info` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_rur_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户推荐表';
-- 热门视频表
CREATE TABLE IF NOT EXISTS `recommend_hot_video` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`video_id` bigint NOT NULL COMMENT '视频ID',
`hot_score` float NOT NULL COMMENT '热度分数',
`period_type` tinyint NOT NULL COMMENT '周期类型(1:天,2:周,3:月)',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_video_period` (`video_id`,`period_type`),
KEY `idx_period_score` (`period_type`,`hot_score`),
CONSTRAINT `fk_rhv_video` FOREIGN KEY (`video_id`) REFERENCES `video_content`.`content_video` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='热门视频表';
-- 7. 管理后台数据库(video_admin)
USE `video_admin`;
-- 管理员表
CREATE TABLE IF NOT EXISTS `admin_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '管理员ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(256) NOT NULL COMMENT '密码(加密)',
`salt` varchar(64) NOT NULL COMMENT '加密盐值',
`real_name` varchar(64) DEFAULT NULL COMMENT '真实姓名',
`avatar` varchar(256) DEFAULT NULL COMMENT '头像',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:正常)',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`last_login_ip` varchar(64) DEFAULT NULL COMMENT '最后登录IP',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`),
UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员表';
-- 管理员角色表
CREATE TABLE IF NOT EXISTS `admin_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(64) NOT NULL COMMENT '角色编码(ADMIN_开头)',
`description` varchar(256) DEFAULT NULL COMMENT '角色描述',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员角色表';
-- 管理员权限表
CREATE TABLE IF NOT EXISTS `admin_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`name` varchar(64) NOT NULL COMMENT '权限名称',
`code` varchar(64) NOT NULL COMMENT '权限编码',
`description` varchar(256) DEFAULT NULL COMMENT '权限描述',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员权限表';
-- 管理员角色关联表
CREATE TABLE IF NOT EXISTS `admin_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`admin_id` bigint NOT NULL COMMENT '管理员ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_admin_role` (`admin_id`,`role_id`),
CONSTRAINT `fk_aur_admin` FOREIGN KEY (`admin_id`) REFERENCES `admin_user` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_aur_role` FOREIGN KEY (`role_id`) REFERENCES `admin_role` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员角色关联表';
-- 角色权限关联表
CREATE TABLE IF NOT EXISTS `admin_role_permission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`permission_id` bigint NOT NULL COMMENT '权限ID',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_role_permission` (`role_id`,`permission_id`),
CONSTRAINT `fk_arp_role` FOREIGN KEY (`role_id`) REFERENCES `admin_role` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_arp_permission` FOREIGN KEY (`permission_id`) REFERENCES `admin_permission` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色权限关联表';
-- 操作日志表
CREATE TABLE IF NOT EXISTS `admin_operation_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`admin_id` bigint DEFAULT NULL COMMENT '管理员ID',
`username` varchar(64) DEFAULT NULL COMMENT '用户名',
`operation` varchar(64) NOT NULL COMMENT '操作',
`method` varchar(128) DEFAULT NULL COMMENT '方法名',
`params` text COMMENT '参数',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
`status` tinyint DEFAULT NULL COMMENT '状态(0:失败,1:成功)',
`error_msg` text COMMENT '错误消息',
`operation_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_admin` (`admin_id`),
KEY `idx_operation_time` (`operation_time`),
CONSTRAINT `fk_aol_admin` FOREIGN KEY (`admin_id`) REFERENCES `admin_user` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='操作日志表';
-- 系统配置表
CREATE TABLE IF NOT EXISTS `admin_system_config` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`config_key` varchar(64) NOT NULL COMMENT '配置键',
`config_value` text NOT NULL COMMENT '配置值',
`description` varchar(256) DEFAULT NULL COMMENT '配置描述',
`created_by` varchar(64) DEFAULT 'system' COMMENT '创建人',
`updated_by` varchar(64) DEFAULT 'system' COMMENT '更新人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表';
-- 初始化管理员数据
INSERT IGNORE INTO `admin_role` (`id`, `name`, `code`, `description`) VALUES
(1, '超级管理员', 'ADMIN_SUPER', '拥有所有权限'),
(2, '内容管理员', 'ADMIN_CONTENT', '内容管理权限'),
(3, '用户管理员', 'ADMIN_USER', '用户管理权限'),
(4, '系统管理员', 'ADMIN_SYSTEM', '系统配置权限');
INSERT IGNORE INTO `admin_permission` (`id`, `name`, `code`, `description`) VALUES
(1, '用户管理', 'user:manage', '用户管理权限'),
(2, '角色管理', 'role:manage', '角色管理权限'),
(3, '权限管理', 'permission:manage', '权限管理权限'),
(4, '视频管理', 'video:manage', '视频管理权限'),
(5, '评论管理', 'comment:manage', '评论管理权限'),
(6, '标签管理', 'tag:manage', '标签管理权限'),
(7, '分类管理', 'category:manage', '分类管理权限'),
(8, '推荐管理', 'recommend:manage', '推荐管理权限'),
(9, '系统配置', 'system:config', '系统配置权限'),
(10, '操作日志', 'operation:log', '操作日志权限');
INSERT IGNORE INTO `admin_role_permission` (`role_id`, `permission_id`) VALUES
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10),
(2, 4), (2, 5), (2, 6), (2, 7), (2, 8),
(3, 1),
(4, 9), (4, 10);
-- 初始化系统配置
INSERT IGNORE INTO `admin_system_config` (`config_key`, `config_value`, `description`) VALUES
('site_name', '高可用在线视频平台', '网站名称'),
('site_logo', '/static/logo.png', '网站Logo'),
('site_favicon', '/static/favicon.ico', '网站图标'),
('site_copyright', 'Copyright © 2024 XXXXXX', '版权信息'),
('video_upload_limit', '1024', '视频上传大小限制(MB)'),
('video_review_enabled', '1', '视频审核开关(0:关闭,1:开启)'),
('comment_review_enabled', '0', '评论审核开关(0:关闭,1:开启)'),
('user_register_enabled', '1', '用户注册开关(0:关闭,1:开启)');
+440
View File
@@ -0,0 +1,440 @@
-- Nacos 配置初始化脚本
-- 在 Nacos 数据库中执行此脚本,创建所有配置
-- 注意:tenant_id 为空字符串 '' 表示 public 命名空间
-- Nacos 2.x 版本需要 encrypted_data_key 字段
-- ============================================
-- 1. 公共基础配置 (common.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('common.yaml', 'DEFAULT_GROUP', '# 公共配置 - 所有微服务共享
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 300000
connection-timeout: 20000
connection-test-query: SELECT 1
keepalive-time: 60000
validation-timeout: 3000
max-lifetime: 58000
data:
redis:
timeout: 10000
lettuce:
pool:
max-active: 200
max-wait: -1
max-idle: 10
min-idle: 0
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
global-config:
db-config:
id-type: auto
logic-delete-field: is_deleted
logic-delete-value: 1
logic-not-delete-value: 0
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
feign:
circuitbreaker:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
logging:
level:
com.nuaa.video: info
org.springframework.security: info', '', '', 'nacos', '');
-- ============================================
-- 2. 开发环境 Redis 配置 (common-redis-dev.yaml)
-- 单机模式
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('common-redis-dev.yaml', 'DEFAULT_GROUP', '# Redis 单机配置 - 开发环境
spring:
data:
redis:
host: ${REDIS_HOST:5.180.253.115}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:redis_EsJ8FX}', '', '', 'nacos', '');
-- ============================================
-- 3. 高可用环境 Redis 配置 (common-redis-ha.yaml)
-- Sentinel 哨兵模式
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('common-redis-ha.yaml', 'DEFAULT_GROUP', '# Redis Sentinel 高可用配置 - 生产环境
spring:
data:
redis:
password: ${REDIS_PASSWORD:redis_HA_Cluster_2024}
sentinel:
master: mymaster
nodes: ${REDIS_SENTINEL_NODES:192.168.1.101:26379,192.168.1.102:26379,192.168.1.103:26379}
password: ${REDIS_PASSWORD:redis_HA_Cluster_2024}', '', '', 'nacos', '');
-- ============================================
-- 4. Gateway 服务配置 (video-gateway.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-gateway.yaml', 'DEFAULT_GROUP', '# Gateway 服务配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: video-auth
uri: lb://video-auth
predicates:
- Path=/auth/**
- id: video-user
uri: lb://video-user
predicates:
- Path=/user/**
- id: video-content
uri: lb://video-content
predicates:
- Path=/content/**
- id: video-comment
uri: lb://video-comment
predicates:
- Path=/comment/**
- id: video-recommend
uri: lb://video-recommend
predicates:
- Path=/recommend/**
- id: video-admin
uri: lb://video-admin
predicates:
- Path=/admin/**
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8720}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
scg:
fallback:
mode: response
response-body: ''{"code":500,"message":"服务不可用,请稍后再试"}''
gateway:
ignore:
urls:
- /**/auth/login
- /**/auth/register
- /**/auth/captcha
- /content/video/public/**
- /content/category/tree
- /content/category/list
- /comment/reply/public/**
- /comment/public/**
- /admin/auth/captcha
- /admin/auth/login', '', '', 'nacos', '');
-- ============================================
-- 5. Auth 服务配置 (video-auth.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-auth.yaml', 'DEFAULT_GROUP', '# Auth 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_auth?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 0
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8720}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.auth.entity
jwt:
secret: video-platform-secret-key-2024-nuaa
expiration: 86400
token-header: Authorization
token-prefix: "Bearer "', '', '', 'nacos', '');
-- ============================================
-- 6. User 服务配置 (video-user.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-user.yaml', 'DEFAULT_GROUP', '# User 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 1
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8721}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.user.entity
auth:
ignore-urls:
- /user/profile/public/**
- /user/profile/init
- /actuator/**
minio:
endpoint: http://${MINIO_HOST:minio}:${MINIO_PORT:9000}
accessKey: minioadmin
secretKey: minioadmin123
bucketName: video-platform', '', '', 'nacos', '');
-- ============================================
-- 7. Content 服务配置 (video-content.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-content.yaml', 'DEFAULT_GROUP', '# Content 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_content?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 2
servlet:
multipart:
max-file-size: 1024MB
max-request-size: 1024MB
enabled: true
rocketmq:
topic-prefix: video-platform-
name-server: ${NAMESRV_ADDR:192.168.1.101:9876;192.168.1.102:9876;192.168.1.103:9876}
producer:
group: video-content-producer
send-message-timeout: 3000
retry-times-when-send-failed: 3
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8722}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.content.entity
minio:
endpoint: http://${MINIO_HOST:minio}:${MINIO_PORT:9000}
accessKey: ${MINIO_ACCESS_KEY:minioadmin}
secretKey: ${MINIO_SECRET_KEY:minioadmin123}
bucketName: ${MINIO_BUCKET:video-platform}
ffmpeg:
binary:
path: ${FFMPEG_PATH:/usr/bin/ffmpeg}
probe:
path: ${FFPROBE_PATH:/usr/bin/ffprobe}
temp:
path: ${FFMPEG_TEMP_PATH:/tmp/ffmpeg}
auth:
ignore-urls:
- /content/video/public/**
- /content/category/list
- /content/tag/list
- /actuator/**
jwt:
secret: video-platform-secret-key-2024-nuaa
expiration: 86400
token-header: Authorization
token-prefix: "Bearer "', '', '', 'nacos', '');
-- ============================================
-- 8. Comment 服务配置 (video-comment.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-comment.yaml', 'DEFAULT_GROUP', '# Comment 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_comment?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 3
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8723}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.comment.entity
global-config:
db-config:
logic-delete-field: status
logic-delete-value: 0
logic-not-delete-value: 1
comment:
sensitive-filter:
enabled: true
review:
enabled: false
jwt:
secret: video-platform-secret-key-2024-nuaa
expiration: 86400
token-header: Authorization
token-prefix: "Bearer "
auth:
ignore-urls:
- /comment/public/**
- /actuator/**', '', '', 'nacos', '');
-- ============================================
-- 9. Recommend 服务配置 (video-recommend.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-recommend.yaml', 'DEFAULT_GROUP', '# Recommend 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_recommend?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 4
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8724}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.recommend.entity
recommend:
hot:
default-size: 20
refresh-interval: 60
weights:
view: 1
like: 3
favorite: 5
coin: 5
comment: 2
share: 3
similarity:
tag-weight: 0.5
category-weight: 0.3
behavior-weight: 0.2
personalized:
default-size: 30
behavior-weights:
view: 1
like: 3
favorite: 5
coin: 5
comment: 2
share: 3
complete-watch: 4
jwt:
secret: video-platform-secret-key-2024-nuaa
expiration: 86400
token-header: Authorization
token-prefix: "Bearer "
auth:
ignore-urls:
- /recommend/hot/**
- /recommend/tag/**
- /actuator/**', '', '', 'nacos', '');
-- ============================================
-- 10. Admin 服务配置 (video-admin.yaml)
-- ============================================
INSERT INTO config_info (data_id, group_id, content, tenant_id, app_name, src_user, encrypted_data_key) VALUES
('video-admin.yaml', 'DEFAULT_GROUP', '# Admin 服务配置
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: ${MYSQL_ROOT_PASSWORD:root123456}
data:
redis:
database: 5
cloud:
sentinel:
transport:
dashboard: ${SENTINEL_DASHBOARD_HOST:sentinel}:${SENTINEL_DASHBOARD_PORT:8880}
port: ${SPRING_CLOUD_SENTINEL_TRANSPORT_PORT:8725}
client-ip: ${SPRING_CLOUD_SENTINEL_TRANSPORT_CLIENT_IP:}
eager: true
mybatis-plus:
type-aliases-package: com.nuaa.video.admin.entity
global-config:
db-config:
logic-delete-field: status
logic-delete-value: 0
logic-not-delete-value: 1
jwt:
secret: video-platform-admin-secret-key-2024-nuaa
expiration: 86400
token-header: Authorization
token-prefix: "Bearer "
login:
retry-count: 5
lock-time: 30
operation-log:
enabled: true
exclude-paths:
- /admin/auth/login
- /admin/auth/logout
- /admin/auth/captcha
- /actuator/**', '', '', 'nacos', '');
+176
View File
@@ -0,0 +1,176 @@
-- 创建Nacos配置数据库
CREATE DATABASE IF NOT EXISTS nacos_config CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE nacos_config;
-- Nacos数据库初始化脚本
CREATE TABLE IF NOT EXISTS `config_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
`c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
`effect` varchar(64) DEFAULT NULL COMMENT '配置生效目标',
`type` varchar(64) DEFAULT NULL COMMENT '配置类型',
`c_schema` text COMMENT '配置模式',
`encrypted_data_key` text NOT NULL COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='config_info';
CREATE TABLE IF NOT EXISTS `config_info_aggr` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='增加租户字段';
CREATE TABLE IF NOT EXISTS `config_info_beta` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='config_info_beta';
CREATE TABLE IF NOT EXISTS `config_info_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='config_info_tag';
CREATE TABLE IF NOT EXISTS `config_tags_relation` (
`id` bigint NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint NOT NULL AUTO_INCREMENT COMMENT 'nid',
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='config_tag_relation';
CREATE TABLE IF NOT EXISTS `group_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='集群、各Group容量信息表';
CREATE TABLE IF NOT EXISTS `his_config_info` (
`id` bigint unsigned NOT NULL COMMENT 'id',
`nid` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`op_type` char(10) DEFAULT NULL COMMENT 'operation type',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '密钥',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='多租户改造';
CREATE TABLE IF NOT EXISTS `tenant_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='租户容量信息表';
CREATE TABLE IF NOT EXISTS `tenant_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tenant_name` varchar(128) DEFAULT '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint NOT NULL COMMENT '创建时间',
`gmt_modified` bigint NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin COMMENT='tenant_info';
CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(50) NOT NULL COMMENT 'username',
`password` varchar(500) NOT NULL COMMENT 'password',
`enabled` tinyint(1) NOT NULL COMMENT 'enabled',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin;
CREATE TABLE IF NOT EXISTS `roles` (
`username` varchar(50) NOT NULL COMMENT 'username',
`role` varchar(50) NOT NULL COMMENT 'role',
UNIQUE KEY `uk_username_role` (`username`,`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin;
CREATE TABLE IF NOT EXISTS `permissions` (
`role` varchar(50) NOT NULL COMMENT 'role',
`resource` varchar(255) NOT NULL COMMENT 'resource',
`action` varchar(8) NOT NULL COMMENT 'action',
UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin;
-- 初始化Nacos用户
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', 1);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
+7
View File
@@ -0,0 +1,7 @@
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/video-admin-*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>video-platform</artifactId>
<groupId>com.nuaa.video</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>video-admin</artifactId>
<dependencies>
<!-- 引入公共模块 -->
<dependency>
<groupId>com.nuaa.video</groupId>
<artifactId>video-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,23 @@
package com.nuaa.video.admin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 管理后台启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.nuaa.video.admin.map" +
"" +
"" +
"per")
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
@@ -0,0 +1,16 @@
package com.nuaa.video.admin.annotation;
import java.lang.annotation.*;
/**
* 操作日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作描述
*/
String value() default "";
}
@@ -0,0 +1,220 @@
package com.nuaa.video.admin.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.entity.AdminOperationLog;
import com.nuaa.video.admin.mapper.AdminOperationLogMapper;
import com.nuaa.video.admin.security.AdminUserDetails;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
* 操作日志切面
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperationLogAspect {
private final AdminOperationLogMapper operationLogMapper;
private final ObjectMapper objectMapper;
@Value("${operation-log.enabled:true}")
private boolean enabled;
@Value("#{'${operation-log.exclude-paths:}'.split(',')}")
private List<String> excludePaths;
/**
* 切入点
*/
@Pointcut("@annotation(com.nuaa.video.admin.annotation.OperationLog)")
public void operationLogPointcut() {
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
* @param result 返回值
*/
@AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
if (!enabled) {
return;
}
handleLog(joinPoint, null, result);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "operationLogPointcut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
if (!enabled) {
return;
}
handleLog(joinPoint, e, null);
}
/**
* 处理日志
*/
private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
try {
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
String requestURI = request.getRequestURI();
// 检查是否需要排除
if (isExclude(requestURI)) {
return;
}
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog annotation = method.getAnnotation(OperationLog.class);
// 如果未配置注解,则不记录日志
if (annotation == null) {
return;
}
// 获取当前登录用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long adminId = null;
String username = "unknown";
if (authentication != null && authentication.getPrincipal() instanceof AdminUserDetails) {
AdminUserDetails userDetails = (AdminUserDetails) authentication.getPrincipal();
adminId = userDetails.getId();
username = userDetails.getUsername();
} else if (authentication != null && authentication.getPrincipal() instanceof String) {
username = (String) authentication.getPrincipal();
}
// 获取操作
String operation = annotation.value();
// 获取请求方法
String methodName = joinPoint.getTarget().getClass().getName() + "." + method.getName();
// 获取请求参数
String params = getParams(joinPoint);
// 获取IP地址
String ip = getIpAddress(request);
// 创建日志对象
AdminOperationLog operationLog = new AdminOperationLog();
operationLog.setAdminId(adminId);
operationLog.setUsername(username);
operationLog.setOperation(operation);
operationLog.setMethod(methodName);
operationLog.setParams(params);
operationLog.setIp(ip);
operationLog.setOperationTime(LocalDateTime.now());
// 处理异常和结果
if (e != null) {
operationLog.setStatus(0); // 失败
operationLog.setErrorMsg(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : e.toString());
} else {
operationLog.setStatus(1); // 成功
}
// 保存日志
operationLogMapper.insert(operationLog);
} catch (Exception ex) {
log.error("记录操作日志失败", ex);
}
}
/**
* 获取请求参数
*/
private String getParams(JoinPoint joinPoint) {
try {
Object[] args = joinPoint.getArgs();
// 过滤掉一些不需要记录的参数类型
args = Arrays.stream(args)
.filter(arg -> !(arg instanceof HttpServletRequest || arg instanceof HttpServletRequest))
.toArray();
return objectMapper.writeValueAsString(args);
} catch (Exception e) {
log.error("获取请求参数失败", e);
return "获取参数失败";
}
}
/**
* 获取请求的IP地址
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理的情况,第一个IP为客户端真实IP
if (ip != null && ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
/**
* 判断是否需要排除请求
*/
private boolean isExclude(String requestURI) {
if (excludePaths != null && !excludePaths.isEmpty()) {
for (String pattern : excludePaths) {
if (StringUtils.hasText(pattern) && requestURI.matches(pattern.trim())) {
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,15 @@
package com.nuaa.video.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,38 @@
package com.nuaa.video.admin.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 创建配置好的ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 使用Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,111 @@
package com.nuaa.video.admin.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.dto.AdminUserDTO;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.service.AdminUserService;
import com.nuaa.video.admin.vo.AdminUserVO;
import com.nuaa.video.common.model.vo.Result;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
/**
* 管理员控制器
*/
@Slf4j
@RestController
@RequestMapping("/admin/user")
@RequiredArgsConstructor
public class AdminUserController {
private final AdminUserService adminUserService;
/**
* 获取管理员列表
*/
@PreAuthorize("hasAuthority('user:manage')")
@GetMapping("/list")
public Result<Page<AdminUserVO>> listUsers(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "keyword", required = false) String keyword) {
Page<AdminUser> pageParam = new Page<>(page, size);
return adminUserService.listUsers(pageParam, keyword);
}
/**
* 获取管理员详情
*/
@PreAuthorize("hasAuthority('user:manage')")
@GetMapping("/{id}")
public Result<AdminUserVO> getUser(@PathVariable Long id) {
return adminUserService.getUserDetail(id);
}
/**
* 添加管理员
*/
@OperationLog("添加管理员")
@PreAuthorize("hasAuthority('user:manage')")
@PostMapping
public Result<Void> addUser(@RequestBody @Valid AdminUserDTO adminUserDTO) {
return adminUserService.addUser(adminUserDTO, getCurrentUsername());
}
/**
* 更新管理员
*/
@OperationLog("更新管理员")
@PreAuthorize("hasAuthority('user:manage')")
@PutMapping
public Result<Void> updateUser(@RequestBody @Valid AdminUserDTO adminUserDTO) {
return adminUserService.updateUser(adminUserDTO, getCurrentUsername());
}
/**
* 删除管理员
*/
@OperationLog("删除管理员")
@PreAuthorize("hasAuthority('user:manage')")
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
return adminUserService.deleteUser(id);
}
/**
* 启用/禁用管理员
*/
@OperationLog("修改管理员状态")
@PreAuthorize("hasAuthority('user:manage')")
@PutMapping("/{id}/status/{status}")
public Result<Void> updateStatus(@PathVariable Long id, @PathVariable Integer status) {
return adminUserService.updateStatus(id, status, getCurrentUsername());
}
/**
* 重置密码
*/
@OperationLog("重置管理员密码")
@PreAuthorize("hasAuthority('user:manage')")
@PutMapping("/{id}/reset-password")
public Result<Void> resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
return adminUserService.resetPassword(id, newPassword, getCurrentUsername());
}
/**
* 获取当前登录用户名
*/
private String getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName();
}
return "system";
}
}
@@ -0,0 +1,65 @@
package com.nuaa.video.admin.controller;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.dto.LoginDTO;
import com.nuaa.video.admin.security.AdminUserDetails;
import com.nuaa.video.admin.service.AuthService;
import com.nuaa.video.admin.vo.CaptchaVO;
import com.nuaa.video.admin.vo.LoginVO;
import com.nuaa.video.common.model.vo.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
/**
* 认证控制器
*/
@Slf4j
@RestController
@RequestMapping("/admin/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
/**
* 获取验证码
*/
@GetMapping("/captcha")
public Result<CaptchaVO> getCaptcha() {
return authService.generateCaptcha();
}
/**
* 登录
*/
@PostMapping("/login")
public Result<LoginVO> login(@RequestBody @Valid LoginDTO loginDTO, HttpServletRequest request) {
return authService.login(loginDTO, request);
}
/**
* 登出
*/
@OperationLog("退出登录")
@PostMapping("/logout")
public Result<Void> logout(@RequestHeader("Authorization") String authorization) {
// 从请求头中提取token
String token = null;
if (authorization != null && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
}
return authService.logout(token);
}
/**
* 获取当前用户信息
*/
@GetMapping("/info")
public Result<LoginVO> getCurrentUser(@AuthenticationPrincipal AdminUserDetails userDetails) {
return authService.getCurrentUser(userDetails);
}
}
@@ -0,0 +1,119 @@
package com.nuaa.video.admin.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.feign.ContentFeignClient;
import com.nuaa.video.common.model.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* 内容管理控制器
*/
@Slf4j
@RestController
@RequestMapping("/admin/platform/content")
@RequiredArgsConstructor
public class ContentManageController {
private final ContentFeignClient contentFeignClient;
/**
* 获取视频列表
*/
@PreAuthorize("hasAuthority('video:manage')")
@GetMapping("/video/list")
public Result<Page<Object>> getVideoList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "status", required = false) Integer status,
@RequestHeader("Authorization") String token) {
return contentFeignClient.getVideoList(page, size, keyword, categoryId, status, token);
}
/**
* 获取视频详情
*/
@PreAuthorize("hasAuthority('video:manage')")
@GetMapping("/video/{videoId}")
public Result<Object> getVideoDetail(
@PathVariable Long videoId,
@RequestHeader("Authorization") String token) {
return contentFeignClient.getVideoDetail(videoId, token);
}
/**
* 审核视频
*/
@OperationLog("审核视频")
@PreAuthorize("hasAuthority('video:manage')")
@PostMapping("/video/review/{videoId}")
public Result<Void> reviewVideo(
@PathVariable Long videoId,
@RequestParam Integer status,
@RequestParam(required = false) String reason,
@RequestHeader("Authorization") String token) {
return contentFeignClient.reviewVideo(videoId, status, reason, token);
}
/**
* 下架视频
*/
@OperationLog("下架视频")
@PreAuthorize("hasAuthority('video:manage')")
@PutMapping("/video/disable/{videoId}")
public Result<Void> disableVideo(
@PathVariable Long videoId,
@RequestHeader("Authorization") String token) {
return contentFeignClient.disableVideo(videoId, token);
}
/**
* 获取分类列表
*/
@PreAuthorize("hasAuthority('category:manage')")
@GetMapping("/category/list")
public Result<Object> getCategoryList(@RequestHeader("Authorization") String token) {
return contentFeignClient.getCategoryList(token);
}
/**
* 添加分类
*/
@OperationLog("添加分类")
@PreAuthorize("hasAuthority('category:manage')")
@PostMapping("/category")
public Result<Void> addCategory(
@RequestBody Object category,
@RequestHeader("Authorization") String token) {
return contentFeignClient.addCategory(category, token);
}
/**
* 更新分类
*/
@OperationLog("更新分类")
@PreAuthorize("hasAuthority('category:manage')")
@PutMapping("/category")
public Result<Void> updateCategory(
@RequestBody Object category,
@RequestHeader("Authorization") String token) {
return contentFeignClient.updateCategory(category, token);
}
/**
* 删除分类
*/
@OperationLog("删除分类")
@PreAuthorize("hasAuthority('category:manage')")
@DeleteMapping("/category/{categoryId}")
public Result<Void> deleteCategory(
@PathVariable Long categoryId,
@RequestHeader("Authorization") String token) {
return contentFeignClient.deleteCategory(categoryId, token);
}
}
@@ -0,0 +1,72 @@
package com.nuaa.video.admin.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.feign.UserFeignClient;
import com.nuaa.video.common.model.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* 用户管理控制器
*/
@Slf4j
@RestController
@RequestMapping("/admin/platform/user")
@RequiredArgsConstructor
public class UserManageController {
private final UserFeignClient userFeignClient;
/**
* 获取用户列表
*/
@PreAuthorize("hasAuthority('user:manage')")
@GetMapping("/list")
public Result<Page<Object>> getUserList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestHeader("Authorization") String token) {
return userFeignClient.getUserList(page, size, keyword, token);
}
/**
* 获取用户详情
*/
@PreAuthorize("hasAuthority('user:manage')")
@GetMapping("/{userId}")
public Result<Object> getUserDetail(
@PathVariable Long userId,
@RequestHeader("Authorization") String token) {
return userFeignClient.getUserDetail(userId, token);
}
/**
* 禁用/启用用户
*/
@OperationLog("修改用户状态")
@PreAuthorize("hasAuthority('user:manage')")
@PutMapping("/{userId}/status/{status}")
public Result<Void> updateUserStatus(
@PathVariable Long userId,
@PathVariable Integer status,
@RequestHeader("Authorization") String token) {
return userFeignClient.updateUserStatus(userId, status, token);
}
/**
* 重置用户密码
*/
@OperationLog("重置用户密码")
@PreAuthorize("hasAuthority('user:manage')")
@PutMapping("/{userId}/reset-password")
public Result<Void> resetUserPassword(
@PathVariable Long userId,
@RequestParam String newPassword,
@RequestHeader("Authorization") String token) {
return userFeignClient.resetUserPassword(userId, newPassword, token);
}
}
@@ -0,0 +1,36 @@
package com.nuaa.video.admin.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 权限DTO
*/
@Data
public class AdminPermissionDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 权限ID
*/
private Long id;
/**
* 权限名称
*/
@NotBlank(message = "权限名称不能为空")
private String name;
/**
* 权限编码
*/
@NotBlank(message = "权限编码不能为空")
private String code;
/**
* 权限描述
*/
private String description;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.admin.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 角色DTO
*/
@Data
public class AdminRoleDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long id;
/**
* 角色名称
*/
@NotBlank(message = "角色名称不能为空")
private String name;
/**
* 角色编码
*/
@NotBlank(message = "角色编码不能为空")
@Pattern(regexp = "^ADMIN_[A-Z_]+$", message = "角色编码必须以ADMIN_开头")
private String code;
/**
* 角色描述
*/
private String description;
/**
* 权限ID集合
*/
private List<Long> permissionIds;
}
@@ -0,0 +1,69 @@
package com.nuaa.video.admin.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 管理员DTO
*/
@Data
public class AdminUserDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 管理员ID
*/
private Long id;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Size(min = 5, max = 20, message = "用户名长度必须在5-20之间")
private String username;
/**
* 密码
*/
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
private String password;
/**
* 真实姓名
*/
@NotBlank(message = "真实姓名不能为空")
private String realName;
/**
* 头像URL
*/
private String avatar;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 手机号
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
/**
* 状态(0:禁用,1:正常)
*/
private Integer status;
/**
* 角色ID集合
*/
private List<Long> roleIds;
}
@@ -0,0 +1,36 @@
package com.nuaa.video.admin.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 登录DTO
*/
@Data
public class LoginDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 验证码
*/
private String captcha;
/**
* 验证码Key
*/
private String captchaKey;
}
@@ -0,0 +1,36 @@
package com.nuaa.video.admin.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 系统配置DTO
*/
@Data
public class SystemConfigDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 配置ID
*/
private Long id;
/**
* 配置键
*/
@NotBlank(message = "配置键不能为空")
private String configKey;
/**
* 配置值
*/
@NotBlank(message = "配置值不能为空")
private String configValue;
/**
* 配置描述
*/
private String description;
}
@@ -0,0 +1,69 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 操作日志实体
*/
@Data
@TableName("admin_operation_log")
public class AdminOperationLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 管理员ID
*/
private Long adminId;
/**
* 用户名
*/
private String username;
/**
* 操作
*/
private String operation;
/**
* 方法名
*/
private String method;
/**
* 参数
*/
private String params;
/**
* IP地址
*/
private String ip;
/**
* 状态(0:失败,1:成功)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private LocalDateTime operationTime;
}
@@ -0,0 +1,59 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 管理员权限实体
*/
@Data
@TableName("admin_permission")
public class AdminPermission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 权限ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 权限名称
*/
private String name;
/**
* 权限编码
*/
private String code;
/**
* 权限描述
*/
private String description;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@@ -0,0 +1,73 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 管理员角色实体
*/
@Data
@TableName("admin_role")
public class AdminRole implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色名称
*/
private String name;
/**
* 角色编码
*/
private String code;
/**
* 角色描述
*/
private String description;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 权限ID集合(非数据库字段)
*/
@TableField(exist = false)
private List<Long> permissionIds;
/**
* 权限集合(非数据库字段)
*/
@TableField(exist = false)
private List<AdminPermission> permissions;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 角色权限关联实体
*/
@Data
@TableName("admin_role_permission")
public class AdminRolePermission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色ID
*/
private Long roleId;
/**
* 权限ID
*/
private Long permissionId;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,59 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 系统配置实体
*/
@Data
@TableName("admin_system_config")
public class AdminSystemConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 配置键
*/
private String configKey;
/**
* 配置值
*/
private String configValue;
/**
* 配置描述
*/
private String description;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@@ -0,0 +1,108 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 管理员实体
*/
@Data
@TableName("admin_user")
public class AdminUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 管理员ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码(加密)
*/
private String password;
/**
* 加密盐值
*/
private String salt;
/**
* 真实姓名
*/
private String realName;
/**
* 头像URL
*/
private String avatar;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 状态(0:禁用,1:正常)
*/
private Integer status;
/**
* 最后登录时间
*/
private LocalDateTime lastLoginTime;
/**
* 最后登录IP
*/
private String lastLoginIp;
/**
* 创建人
*/
private String createdBy;
/**
* 更新人
*/
private String updatedBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 角色ID集合(非数据库字段)
*/
@TableField(exist = false)
private List<Long> roleIds;
/**
* 角色集合(非数据库字段)
*/
@TableField(exist = false)
private List<AdminRole> roles;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.admin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 管理员角色关联实体
*/
@Data
@TableName("admin_user_role")
public class AdminUserRole implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 管理员ID
*/
private Long adminId;
/**
* 角色ID
*/
private Long roleId;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,45 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* 评论服务Feign客户端
*/
@FeignClient(name = "video-comment", fallbackFactory = CommentFeignClientFallbackFactory.class)
public interface CommentFeignClient {
/**
* 获取评论列表
*/
@GetMapping("/comment/admin/list")
Result<Page<Object>> getCommentList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "videoId", required = false) Long videoId,
@RequestParam(value = "userId", required = false) Long userId,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestHeader("Authorization") String token);
/**
* 获取评论详情
*/
@GetMapping("/comment/admin/{commentId}")
Result<Object> getCommentDetail(@PathVariable("commentId") Long commentId,
@RequestHeader("Authorization") String token);
/**
* 删除评论
*/
@DeleteMapping("/comment/admin/{commentId}")
Result<Void> deleteComment(@PathVariable("commentId") Long commentId,
@RequestHeader("Authorization") String token);
/**
* 获取评论统计信息
*/
@GetMapping("/comment/admin/stats")
Result<Object> getCommentStats(@RequestHeader("Authorization") String token);
}
@@ -0,0 +1,42 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 评论服务Feign客户端降级工厂
*/
@Slf4j
@Component
public class CommentFeignClientFallbackFactory implements FallbackFactory<CommentFeignClient> {
@Override
public CommentFeignClient create(Throwable throwable) {
log.error("CommentFeignClient调用失败,原因:", throwable);
return new CommentFeignClient() {
@Override
public Result<Page<Object>> getCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword, String token) {
return Result.error("获取评论列表失败:服务不可用");
}
@Override
public Result<Object> getCommentDetail(Long commentId, String token) {
return Result.error("获取评论详情失败:服务不可用");
}
@Override
public Result<Void> deleteComment(Long commentId, String token) {
return Result.error("删除评论失败:服务不可用");
}
@Override
public Result<Object> getCommentStats(String token) {
return Result.error("获取评论统计信息失败:服务不可用");
}
};
}
}
@@ -0,0 +1,73 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* 内容服务Feign客户端
*/
@FeignClient(name = "video-content", fallbackFactory = ContentFeignClientFallbackFactory.class)
public interface ContentFeignClient {
/**
* 获取视频列表
*/
@GetMapping("/content/video/admin/list")
Result<Page<Object>> getVideoList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "status", required = false) Integer status,
@RequestHeader("Authorization") String token);
/**
* 获取视频详情
*/
@GetMapping("/content/video/admin/{videoId}")
Result<Object> getVideoDetail(@PathVariable("videoId") Long videoId,
@RequestHeader("Authorization") String token);
/**
* 审核视频
*/
@PostMapping("/content/video/admin/review/{videoId}")
Result<Void> reviewVideo(@PathVariable("videoId") Long videoId,
@RequestParam("status") Integer status,
@RequestParam(value = "reason", required = false) String reason,
@RequestHeader("Authorization") String token);
/**
* 下架视频
*/
@PutMapping("/content/video/admin/disable/{videoId}")
Result<Void> disableVideo(@PathVariable("videoId") Long videoId,
@RequestHeader("Authorization") String token);
/**
* 分类列表
*/
@GetMapping("/content/category/admin/list")
Result<Object> getCategoryList(@RequestHeader("Authorization") String token);
/**
* 添加分类
*/
@PostMapping("/content/category/admin")
Result<Void> addCategory(@RequestBody Object category, @RequestHeader("Authorization") String token);
/**
* 更新分类
*/
@PutMapping("/content/category/admin")
Result<Void> updateCategory(@RequestBody Object category, @RequestHeader("Authorization") String token);
/**
* 删除分类
*/
@DeleteMapping("/content/category/admin/{categoryId}")
Result<Void> deleteCategory(@PathVariable("categoryId") Long categoryId,
@RequestHeader("Authorization") String token);
}
@@ -0,0 +1,62 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 内容服务Feign客户端降级工厂
*/
@Slf4j
@Component
public class ContentFeignClientFallbackFactory implements FallbackFactory<ContentFeignClient> {
@Override
public ContentFeignClient create(Throwable throwable) {
log.error("ContentFeignClient调用失败,原因:", throwable);
return new ContentFeignClient() {
@Override
public Result<Page<Object>> getVideoList(Integer page, Integer size, String keyword, Long categoryId, Integer status, String token) {
return Result.error("获取视频列表失败:服务不可用");
}
@Override
public Result<Object> getVideoDetail(Long videoId, String token) {
return Result.error("获取视频详情失败:服务不可用");
}
@Override
public Result<Void> reviewVideo(Long videoId, Integer status, String reason, String token) {
return Result.error("审核视频失败:服务不可用");
}
@Override
public Result<Void> disableVideo(Long videoId, String token) {
return Result.error("下架视频失败:服务不可用");
}
@Override
public Result<Object> getCategoryList(String token) {
return Result.error("获取分类列表失败:服务不可用");
}
@Override
public Result<Void> addCategory(Object category, String token) {
return Result.error("添加分类失败:服务不可用");
}
@Override
public Result<Void> updateCategory(Object category, String token) {
return Result.error("更新分类失败:服务不可用");
}
@Override
public Result<Void> deleteCategory(Long categoryId, String token) {
return Result.error("删除分类失败:服务不可用");
}
};
}
}
@@ -0,0 +1,46 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* 用户服务Feign客户端
*/
@FeignClient(name = "video-user", fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
/**
* 获取用户列表
*/
@GetMapping("/user/profile/admin/list")
Result<Page<Object>> getUserList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestHeader("Authorization") String token);
/**
* 获取用户详情
*/
@GetMapping("/user/profile/admin/{userId}")
Result<Object> getUserDetail(@PathVariable("userId") Long userId,
@RequestHeader("Authorization") String token);
/**
* 禁用/启用用户
*/
@PutMapping("/user/profile/admin/status/{userId}/{status}")
Result<Void> updateUserStatus(@PathVariable("userId") Long userId,
@PathVariable("status") Integer status,
@RequestHeader("Authorization") String token);
/**
* 重置用户密码
*/
@PutMapping("/user/profile/admin/reset-password/{userId}")
Result<Void> resetUserPassword(@PathVariable("userId") Long userId,
@RequestParam String newPassword,
@RequestHeader("Authorization") String token);
}
@@ -0,0 +1,45 @@
package com.nuaa.video.admin.feign;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.common.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 用户服务Feign客户端降级工厂
*/
@Slf4j
@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
log.error("用户服务调用失败:", cause);
return new UserFeignClient() {
@Override
public Result<Page<Object>> getUserList(Integer page, Integer size, String keyword, String token) {
log.error("获取用户列表失败");
return Result.error("获取用户列表失败,用户服务不可用");
}
@Override
public Result<Object> getUserDetail(Long userId, String token) {
log.error("获取用户详情失败,用户ID: {}", userId);
return Result.error("获取用户详情失败,用户服务不可用");
}
@Override
public Result<Void> updateUserStatus(Long userId, Integer status, String token) {
log.error("更新用户状态失败,用户ID: {}, 状态: {}", userId, status);
return Result.error("更新用户状态失败,用户服务不可用");
}
@Override
public Result<Void> resetUserPassword(Long userId, String newPassword, String token) {
log.error("重置用户密码失败,用户ID: {}", userId);
return Result.error("重置用户密码失败,用户服务不可用");
}
};
}
}
@@ -0,0 +1,33 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.entity.AdminOperationLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 操作日志Mapper
*/
@Mapper
public interface AdminOperationLogMapper extends BaseMapper<AdminOperationLog> {
/**
* 分页查询操作日志
*
* @param page 分页参数
* @param username 用户名
* @param operation 操作
* @param status 状态
* @param startTime 开始时间
* @param endTime 结束时间
* @return 操作日志列表
*/
Page<AdminOperationLog> selectLogList(
Page<AdminOperationLog> page,
@Param("username") String username,
@Param("operation") String operation,
@Param("status") Integer status,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}
@@ -0,0 +1,31 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.admin.entity.AdminPermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 权限Mapper
*/
@Mapper
public interface AdminPermissionMapper extends BaseMapper<AdminPermission> {
/**
* 获取角色的权限列表
*
* @param roleId 角色ID
* @return 权限列表
*/
List<AdminPermission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
/**
* 获取管理员的权限列表
*
* @param adminId 管理员ID
* @return 权限列表
*/
List<AdminPermission> selectPermissionsByAdminId(@Param("adminId") Long adminId);
}
@@ -0,0 +1,41 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.entity.AdminRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色Mapper
*/
@Mapper
public interface AdminRoleMapper extends BaseMapper<AdminRole> {
/**
* 查询角色列表(包括权限)
*
* @param page 分页参数
* @param keyword 关键字
* @return 角色列表
*/
Page<AdminRole> selectRoleList(Page<AdminRole> page, @Param("keyword") String keyword);
/**
* 获取角色详情(包括权限)
*
* @param id 角色ID
* @return 角色详情
*/
AdminRole selectRoleDetail(@Param("id") Long id);
/**
* 获取管理员的角色列表
*
* @param adminId 管理员ID
* @return 角色列表
*/
List<AdminRole> selectRolesByAdminId(@Param("adminId") Long adminId);
}
@@ -0,0 +1,25 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.admin.entity.AdminRolePermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色权限关联Mapper
*/
@Mapper
public interface AdminRolePermissionMapper extends BaseMapper<AdminRolePermission> {
/**
* 批量插入角色权限关联
*
* @param roleId 角色ID
* @param permissionIds 权限ID列表
* @param createdBy 创建人
* @return 影响行数
*/
int batchInsert(@Param("roleId") Long roleId, @Param("permissionIds") List<Long> permissionIds, @Param("createdBy") String createdBy);
}
@@ -0,0 +1,21 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.admin.entity.AdminSystemConfig;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 系统配置Mapper
*/
@Mapper
public interface AdminSystemConfigMapper extends BaseMapper<AdminSystemConfig> {
/**
* 根据配置键获取配置值
*
* @param configKey 配置键
* @return 配置值
*/
String getConfigValue(@Param("configKey") String configKey);
}
@@ -0,0 +1,39 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.admin.entity.AdminUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 管理员Mapper
*/
@Mapper
public interface AdminUserMapper extends BaseMapper<AdminUser> {
/**
* 根据用户名查询管理员信息(包括角色和权限)
*
* @param username 用户名
* @return 管理员信息
*/
AdminUser selectByUsername(@Param("username") String username);
/**
* 查询管理员列表(包括角色)
*
* @param page 分页参数
* @param keyword 关键字
* @return 管理员列表
*/
Page<AdminUser> selectUserList(Page<AdminUser> page, @Param("keyword") String keyword);
/**
* 获取管理员详情(包括角色和权限)
*
* @param id 管理员ID
* @return 管理员详情
*/
AdminUser selectUserDetail(@Param("id") Long id);
}
@@ -0,0 +1,25 @@
package com.nuaa.video.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nuaa.video.admin.entity.AdminUserRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户角色关联Mapper
*/
@Mapper
public interface AdminUserRoleMapper extends BaseMapper<AdminUserRole> {
/**
* 批量插入用户角色关联
*
* @param adminId 管理员ID
* @param roleIds 角色ID列表
* @param createdBy 创建人
* @return 影响行数
*/
int batchInsert(@Param("adminId") Long adminId, @Param("roleIds") List<Long> roleIds, @Param("createdBy") String createdBy);
}
@@ -0,0 +1,80 @@
package com.nuaa.video.admin.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* Spring Security用户详情
*/
@Data
public class AdminUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
/**
* 管理员ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
@JsonIgnore
private String password;
/**
* 盐
*/
@JsonIgnore
private String salt;
/**
* 授权列表
*/
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@@ -0,0 +1,57 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.service.AdminUserService;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class AdminUserDetailsServiceImpl implements UserDetailsService {
private final AdminUserService adminUserService;
// 添加@Lazy注解打破循环依赖
public AdminUserDetailsServiceImpl(@Lazy AdminUserService adminUserService) {
this.adminUserService = adminUserService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库加载用户
AdminUser adminUser = adminUserService.getByUsername(username);
if (adminUser == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 构建UserDetails对象
AdminUserDetails userDetails = new AdminUserDetails();
userDetails.setId(adminUser.getId());
userDetails.setUsername(adminUser.getUsername());
userDetails.setPassword(adminUser.getPassword());
userDetails.setSalt(adminUser.getSalt());
// 设置权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 从用户的角色中设置权限...
if (adminUser.getRoles() != null) {
adminUser.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
if (role.getPermissions() != null) {
role.getPermissions().forEach(permission ->
authorities.add(new SimpleGrantedAuthority(permission.getCode()))
);
}
});
}
userDetails.setAuthorities(authorities);
return userDetails;
}
}
@@ -0,0 +1,43 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.service.PasswordService;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
private final PasswordService passwordService;
public CustomAuthenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
PasswordService passwordService) {
super();
setUserDetailsService(userDetailsService);
setPasswordEncoder(passwordEncoder);
this.passwordService = passwordService;
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("认证失败:没有提供密码");
}
String presentedPassword = authentication.getCredentials().toString();
// 使用密码服务验证密码,不再需要盐值
if (!passwordService.matches(presentedPassword, "", userDetails.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
}
}
@@ -0,0 +1,85 @@
package com.nuaa.video.admin.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
/**
* JWT认证过滤器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 从请求头获取JWT令牌
String token = getJwtFromRequest(request);
// 验证令牌
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
// 从令牌中获取用户信息
Long adminId = jwtTokenProvider.getAdminIdFromToken(token);
String username = jwtTokenProvider.getUsernameFromToken(token);
List<String> authorities = jwtTokenProvider.getAuthoritiesFromToken(token);
// 转换为Spring Security的授权对象
List<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, simpleGrantedAuthorities);
// 设置用户详情
AdminUserDetails userDetails = new AdminUserDetails();
userDetails.setId(adminId);
userDetails.setUsername(username);
userDetails.setAuthorities(simpleGrantedAuthorities);
authentication.setDetails(userDetails);
// 设置认证信息到安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 将管理员ID和用户名设置到请求属性中,便于后续使用
request.setAttribute("adminId", adminId);
request.setAttribute("username", username);
}
} catch (Exception e) {
log.error("无法设置用户认证", e);
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
/**
* 从请求头中提取JWT令牌
*/
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
@@ -0,0 +1,110 @@
package com.nuaa.video.admin.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
/**
* JWT令牌提供者
*/
@Slf4j
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 生成JWT令牌
*/
public String generateToken(AdminUserDetails userDetails) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.subject(userDetails.getId().toString())
.claim("username", userDetails.getUsername())
.claim("authorities", userDetails.getAuthorities().stream()
.map(auth -> auth.getAuthority())
.toList())
.issuedAt(now)
.expiration(expiryDate)
.signWith(getSignKey())
.compact();
}
/**
* 验证JWT令牌
*/
public boolean validateToken(String token) {
try {
Jwts.parser().verifyWith(getSignKey()).build().parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.error("无效的JWT令牌: {}", e.getMessage());
return false;
}
}
/**
* 从JWT令牌中获取管理员ID
*/
public Long getAdminIdFromToken(String token) {
String subject = getClaimFromToken(token, Claims::getSubject);
return Long.parseLong(subject);
}
/**
* 从JWT令牌中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, claims -> claims.get("username", String.class));
}
/**
* 从JWT令牌中获取权限列表
*/
@SuppressWarnings("unchecked")
public List<String> getAuthoritiesFromToken(String token) {
try {
return getClaimFromToken(token, claims -> claims.get("authorities", List.class));
} catch (Exception e) {
log.error("获取权限列表失败", e);
return new ArrayList<>();
}
}
/**
* 获取JWT令牌的签名密钥
*/
private SecretKey getSignKey() {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* 从JWT令牌中获取特定声明
*/
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
Claims claims = Jwts.parser()
.verifyWith(getSignKey())
.build()
.parseSignedClaims(token)
.getPayload();
return claimsResolver.apply(claims);
}
}
@@ -0,0 +1,70 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.service.PasswordService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 安全配置类
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
private final PasswordService passwordService;
private final PasswordEncoder passwordEncoder; // Inject instead of creating
// Remove this method
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/auth/login", "/admin/auth/captcha", "/admin/auth/logout", "/actuator/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
CustomAuthenticationProvider provider = new CustomAuthenticationProvider(
userDetailsService,
passwordEncoder, // Use injected passwordEncoder
passwordService);
return provider;
}
}
@@ -0,0 +1,85 @@
package com.nuaa.video.admin.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nuaa.video.admin.dto.AdminUserDTO;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.vo.AdminUserVO;
import com.nuaa.video.common.model.vo.Result;
/**
* 管理员服务接口
*/
public interface AdminUserService extends IService<AdminUser> {
/**
* 根据用户名查询管理员信息
*
* @param username 用户名
* @return 管理员信息
*/
AdminUser getByUsername(String username);
/**
* 分页查询管理员列表
*
* @param page 分页参数
* @param keyword 关键字
* @return 管理员列表
*/
Result<Page<AdminUserVO>> listUsers(Page<AdminUser> page, String keyword);
/**
* 获取管理员详情
*
* @param id 管理员ID
* @return 管理员详情
*/
Result<AdminUserVO> getUserDetail(Long id);
/**
* 添加管理员
*
* @param adminUserDTO 管理员信息
* @param operator 操作人
* @return 结果
*/
Result<Void> addUser(AdminUserDTO adminUserDTO, String operator);
/**
* 更新管理员
*
* @param adminUserDTO 管理员信息
* @param operator 操作人
* @return 结果
*/
Result<Void> updateUser(AdminUserDTO adminUserDTO, String operator);
/**
* 删除管理员
*
* @param id 管理员ID
* @return 结果
*/
Result<Void> deleteUser(Long id);
/**
* 更新管理员状态
*
* @param id 管理员ID
* @param status 状态
* @param operator 操作人
* @return 结果
*/
Result<Void> updateStatus(Long id, Integer status, String operator);
/**
* 重置密码
*
* @param id 管理员ID
* @param newPassword 新密码
* @param operator 操作人
* @return 结果
*/
Result<Void> resetPassword(Long id, String newPassword, String operator);
}
@@ -0,0 +1,46 @@
package com.nuaa.video.admin.service;
import com.nuaa.video.admin.dto.LoginDTO;
import com.nuaa.video.admin.security.AdminUserDetails;
import com.nuaa.video.admin.vo.CaptchaVO;
import com.nuaa.video.admin.vo.LoginVO;
import com.nuaa.video.common.model.vo.Result;
import jakarta.servlet.http.HttpServletRequest;
/**
* 认证服务接口
*/
public interface AuthService {
/**
* 生成验证码
*
* @return 验证码数据
*/
Result<CaptchaVO> generateCaptcha();
/**
* 登录
*
* @param loginDTO 登录参数
* @param request HTTP请求
* @return 登录结果
*/
Result<LoginVO> login(LoginDTO loginDTO, HttpServletRequest request);
/**
* 登出
*
* @param token JWT令牌
* @return 结果
*/
Result<Void> logout(String token);
/**
* 获取当前登录用户信息
*
* @param adminUserDetails 用户详情
* @return 用户信息
*/
Result<LoginVO> getCurrentUser(AdminUserDetails adminUserDetails);
}
@@ -0,0 +1,39 @@
package com.nuaa.video.admin.service;
import com.nuaa.video.admin.entity.AdminUser;
/**
* 密码处理服务接口 - 使用标准BCrypt方式
*/
public interface PasswordService {
/**
* 生成新的盐值 (BCrypt已内置盐值,此方法保留但不再使用)
* @return 盐值字符串
*/
String generateSalt();
/**
* 加密密码
* @param rawPassword 原始密码
* @param salt 盐值 (BCrypt已内置盐值,此参数不再使用)
* @return 加密后的密码
*/
String encryptPassword(String rawPassword, String salt);
/**
* 验证密码
* @param rawPassword 原始密码
* @param salt 盐值 (BCrypt已内置盐值,此参数不再使用)
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
boolean matches(String rawPassword, String salt, String encodedPassword);
/**
* 为用户设置密码
* @param user 用户对象
* @param rawPassword 原始密码
*/
void setPassword(AdminUser user, String rawPassword);
}
@@ -0,0 +1,72 @@
package com.nuaa.video.admin.service.impl;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.entity.AdminUserRole;
import com.nuaa.video.admin.mapper.AdminUserMapper;
import com.nuaa.video.admin.mapper.AdminUserRoleMapper;
import com.nuaa.video.admin.service.PasswordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
public class AdminUserInitService implements ApplicationRunner {
private final AdminUserMapper adminUserMapper;
private final AdminUserRoleMapper adminUserRoleMapper;
private final PasswordService passwordService;
@Value("${app.init.admin-password:admin123}")
private String initAdminPassword;
@Override
public void run(ApplicationArguments args) throws Exception {
initAdminAccount();
}
// 移除 @Override 注解,因为这不是覆盖父类或接口的方法
public void initAdminAccount() {
// 检查是否已存在admin账号
AdminUser existingAdmin = adminUserMapper.selectByUsername("admin");
if (existingAdmin != null) {
log.info("管理员账号已存在,无需初始化");
return;
}
// 创建管理员账号
AdminUser adminUser = new AdminUser();
adminUser.setUsername("admin");
// 使用密码服务设置密码,不再需要手动设置盐值
passwordService.setPassword(adminUser, initAdminPassword);
adminUser.setRealName("系统管理员");
adminUser.setEmail("admin@example.com");
adminUser.setStatus(1); // 正常状态
adminUser.setSalt("aaaaaa"); // 盐值可以设置为默认值,该字段已抛弃
adminUser.setCreatedBy("system");
adminUser.setUpdatedBy("system");
adminUser.setCreateTime(LocalDateTime.now());
adminUser.setUpdateTime(LocalDateTime.now());
// 保存管理员
adminUserMapper.insert(adminUser);
// 创建管理员角色关联
AdminUserRole userRole = new AdminUserRole();
userRole.setAdminId(adminUser.getId());
userRole.setRoleId(1L); // 假设ID为1的角色是超级管理员
userRole.setCreatedBy("system");
userRole.setCreateTime(LocalDateTime.now());
adminUserRoleMapper.insert(userRole);
log.info("管理员账号初始化完成");
}
}
@@ -0,0 +1,310 @@
package com.nuaa.video.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nuaa.video.admin.dto.AdminUserDTO;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.entity.AdminUserRole;
import com.nuaa.video.admin.mapper.AdminPermissionMapper;
import com.nuaa.video.admin.mapper.AdminRoleMapper;
import com.nuaa.video.admin.mapper.AdminUserMapper;
import com.nuaa.video.admin.mapper.AdminUserRoleMapper;
import com.nuaa.video.admin.service.AdminUserService;
import com.nuaa.video.admin.service.PasswordService;
import com.nuaa.video.admin.vo.AdminRoleVO;
import com.nuaa.video.admin.vo.AdminUserVO;
import com.nuaa.video.common.model.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 管理员服务实现类
*/
@Slf4j
// 在类定义中添加 PasswordService 依赖
@Service
@RequiredArgsConstructor
public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser> implements AdminUserService {
private final AdminUserMapper adminUserMapper;
private final AdminUserRoleMapper adminUserRoleMapper;
private final AdminRoleMapper adminRoleMapper;
private final AdminPermissionMapper adminPermissionMapper;
private final PasswordService passwordService;
// 移除 PasswordEncoder 依赖
// private final PasswordEncoder passwordEncoder;
@Override
public AdminUser getByUsername(String username) {
return adminUserMapper.selectByUsername(username);
}
@Override
public Result<Page<AdminUserVO>> listUsers(Page<AdminUser> page, String keyword) {
Page<AdminUser> userPage = adminUserMapper.selectUserList(page, keyword);
// 转换为VO
Page<AdminUserVO> voPage = new Page<>();
BeanUtils.copyProperties(userPage, voPage, "records");
List<AdminUserVO> userVOList = new ArrayList<>();
for (AdminUser user : userPage.getRecords()) {
AdminUserVO vo = new AdminUserVO();
BeanUtils.copyProperties(user, vo);
// 设置角色ID列表
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
vo.setRoleIds(user.getRoles().stream().map(role -> role.getId()).collect(Collectors.toList()));
// 设置角色列表
List<AdminRoleVO> roleVOList = user.getRoles().stream().map(role -> {
AdminRoleVO roleVO = new AdminRoleVO();
BeanUtils.copyProperties(role, roleVO);
return roleVO;
}).collect(Collectors.toList());
vo.setRoles(roleVOList);
}
userVOList.add(vo);
}
voPage.setRecords(userVOList);
return Result.success(voPage);
}
@Override
public Result<AdminUserVO> getUserDetail(Long id) {
AdminUser user = adminUserMapper.selectUserDetail(id);
if (user == null) {
return Result.error("管理员不存在");
}
// 转换为VO
AdminUserVO vo = new AdminUserVO();
BeanUtils.copyProperties(user, vo);
// 设置角色ID列表
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
vo.setRoleIds(user.getRoles().stream().map(role -> role.getId()).collect(Collectors.toList()));
// 设置角色列表
List<AdminRoleVO> roleVOList = user.getRoles().stream().map(role -> {
AdminRoleVO roleVO = new AdminRoleVO();
BeanUtils.copyProperties(role, roleVO);
return roleVO;
}).collect(Collectors.toList());
vo.setRoles(roleVOList);
}
return Result.success(vo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> addUser(AdminUserDTO adminUserDTO, String operator) {
// 检查用户名是否存在
AdminUser existUser = getOne(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getUsername, adminUserDTO.getUsername()));
if (existUser != null) {
return Result.error("用户名已存在");
}
// 检查邮箱是否存在
if (StringUtils.hasText(adminUserDTO.getEmail())) {
long count = count(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getEmail, adminUserDTO.getEmail()));
if (count > 0) {
return Result.error("邮箱已存在");
}
}
// 检查手机号是否存在
if (StringUtils.hasText(adminUserDTO.getPhone())) {
long count = count(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getPhone, adminUserDTO.getPhone()));
if (count > 0) {
return Result.error("手机号已存在");
}
}
// 创建管理员
AdminUser adminUser = new AdminUser();
BeanUtils.copyProperties(adminUserDTO, adminUser);
// 使用密码服务设置密码 - 使用标准BCrypt方式
String rawPassword = StringUtils.hasText(adminUserDTO.getPassword()) ?
adminUserDTO.getPassword() : "123456";
passwordService.setPassword(adminUser, rawPassword);
// 设置默认状态
if (adminUser.getStatus() == null) {
adminUser.setStatus(1); // 正常状态
}
adminUser.setCreatedBy(operator);
adminUser.setUpdatedBy(operator);
adminUser.setCreateTime(LocalDateTime.now());
adminUser.setUpdateTime(LocalDateTime.now());
// 保存管理员
save(adminUser);
// 保存角色关联
if (adminUserDTO.getRoleIds() != null && !adminUserDTO.getRoleIds().isEmpty()) {
adminUserRoleMapper.batchInsert(adminUser.getId(), adminUserDTO.getRoleIds(), operator);
}
return Result.success();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> updateUser(AdminUserDTO adminUserDTO, String operator) {
// 检查管理员是否存在
AdminUser existUser = getById(adminUserDTO.getId());
if (existUser == null) {
return Result.error("管理员不存在");
}
// 检查用户名是否重复
if (!existUser.getUsername().equals(adminUserDTO.getUsername())) {
AdminUser userWithSameName = getOne(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getUsername, adminUserDTO.getUsername()));
if (userWithSameName != null) {
return Result.error("用户名已存在");
}
}
// 检查邮箱是否重复
if (StringUtils.hasText(adminUserDTO.getEmail()) && !adminUserDTO.getEmail().equals(existUser.getEmail())) {
long count = count(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getEmail, adminUserDTO.getEmail())
.ne(AdminUser::getId, adminUserDTO.getId()));
if (count > 0) {
return Result.error("邮箱已存在");
}
}
// 检查手机号是否重复
if (StringUtils.hasText(adminUserDTO.getPhone()) && !adminUserDTO.getPhone().equals(existUser.getPhone())) {
long count = count(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getPhone, adminUserDTO.getPhone())
.ne(AdminUser::getId, adminUserDTO.getId()));
if (count > 0) {
return Result.error("手机号已存在");
}
}
// 更新管理员信息
AdminUser adminUser = new AdminUser();
BeanUtils.copyProperties(adminUserDTO, adminUser);
// 如果密码不为空,则更新密码
if (StringUtils.hasText(adminUserDTO.getPassword())) {
// 使用密码服务设置新密码
adminUser.setSalt(existUser.getSalt()); // 保留原有盐值
passwordService.setPassword(adminUser, adminUserDTO.getPassword());
} else {
// 不更新密码
adminUser.setPassword(null);
adminUser.setSalt(null); // 同时不更新盐值
}
adminUser.setUpdatedBy(operator);
adminUser.setUpdateTime(LocalDateTime.now());
// 保存管理员
updateById(adminUser);
// 更新角色关联
if (adminUserDTO.getRoleIds() != null) {
// 删除原有角色关联
adminUserRoleMapper.delete(new LambdaQueryWrapper<AdminUserRole>()
.eq(AdminUserRole::getAdminId, adminUser.getId()));
// 添加新的角色关联
if (!adminUserDTO.getRoleIds().isEmpty()) {
adminUserRoleMapper.batchInsert(adminUser.getId(), adminUserDTO.getRoleIds(), operator);
}
}
return Result.success();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Void> deleteUser(Long id) {
// 检查管理员是否存在
AdminUser existUser = getById(id);
if (existUser == null) {
return Result.error("管理员不存在");
}
// 删除管理员
removeById(id);
// 删除角色关联
adminUserRoleMapper.delete(new LambdaQueryWrapper<AdminUserRole>()
.eq(AdminUserRole::getAdminId, id));
return Result.success();
}
@Override
public Result<Void> updateStatus(Long id, Integer status, String operator) {
// 检查管理员是否存在
AdminUser existUser = getById(id);
if (existUser == null) {
return Result.error("管理员不存在");
}
// 更新状态
AdminUser adminUser = new AdminUser();
adminUser.setId(id);
adminUser.setStatus(status);
adminUser.setUpdatedBy(operator);
adminUser.setUpdateTime(LocalDateTime.now());
updateById(adminUser);
return Result.success();
}
@Override
public Result<Void> resetPassword(Long id, String newPassword, String operator) {
// 检查管理员是否存在
AdminUser existUser = getById(id);
if (existUser == null) {
return Result.error("管理员不存在");
}
// 更新密码
AdminUser adminUser = new AdminUser();
adminUser.setId(id);
adminUser.setSalt(existUser.getSalt()); // 保留原有盐值
// 使用密码服务设置新密码
passwordService.setPassword(adminUser, newPassword);
adminUser.setUpdatedBy(operator);
adminUser.setUpdateTime(LocalDateTime.now());
updateById(adminUser);
return Result.success();
}
}
@@ -0,0 +1,279 @@
package com.nuaa.video.admin.service.impl;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.util.IdUtil;
import com.nuaa.video.admin.dto.LoginDTO;
import com.nuaa.video.admin.entity.AdminPermission;
import com.nuaa.video.admin.entity.AdminRole;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.security.AdminUserDetails;
import com.nuaa.video.admin.security.JwtTokenProvider;
import com.nuaa.video.admin.service.AdminUserService;
import com.nuaa.video.admin.service.AuthService;
import com.nuaa.video.admin.vo.CaptchaVO;
import com.nuaa.video.admin.vo.LoginVO;
import com.nuaa.video.common.model.vo.Result;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 认证服务实现类
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
private final AdminUserService adminUserService;
private final RedisTemplate<String, Object> redisTemplate;
@Value("${login.retry-count:5}")
private int maxRetryCount;
@Value("${login.lock-time:30}")
private int lockTime;
private static final String CAPTCHA_KEY_PREFIX = "admin:captcha:";
private static final String TOKEN_KEY_PREFIX = "admin:token:";
private static final String LOGIN_RETRY_KEY_PREFIX = "admin:login:retry:";
private static final String LOGIN_LOCK_KEY_PREFIX = "admin:login:lock:";
@Override
public Result<CaptchaVO> generateCaptcha() {
// 生成验证码
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 100);
String code = captcha.getCode();
String imageBase64 = captcha.getImageBase64();
// 生成验证码key
String captchaKey = IdUtil.fastSimpleUUID();
// 将验证码存入Redis,有效期5分钟
redisTemplate.opsForValue().set(CAPTCHA_KEY_PREFIX + captchaKey, code, 5, TimeUnit.MINUTES);
// 构建返回数据
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setCaptchaKey(captchaKey);
captchaVO.setCaptchaImage("data:image/png;base64," + imageBase64);
return Result.success(captchaVO);
}
@Override
public Result<LoginVO> login(LoginDTO loginDTO, HttpServletRequest request) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
// 检查账户是否被锁定
Boolean isLocked = (Boolean) redisTemplate.opsForValue().get(LOGIN_LOCK_KEY_PREFIX + username);
if (Boolean.TRUE.equals(isLocked)) {
return Result.error("账户已被锁定,请稍后再试");
}
// 验证验证码
if (StringUtils.hasText(loginDTO.getCaptchaKey()) && StringUtils.hasText(loginDTO.getCaptcha())) {
String captchaKey = CAPTCHA_KEY_PREFIX + loginDTO.getCaptchaKey();
String captchaValue = (String) redisTemplate.opsForValue().get(captchaKey);
if (!StringUtils.hasText(captchaValue) || !captchaValue.equalsIgnoreCase(loginDTO.getCaptcha())) {
return Result.error("验证码错误");
}
// 验证通过后删除验证码
redisTemplate.delete(captchaKey);
}
try {
// 进行认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
// 认证成功,清除登录失败记录
redisTemplate.delete(LOGIN_RETRY_KEY_PREFIX + username);
// 获取用户详情
AdminUser adminUser = adminUserService.getByUsername(username);
// 设置最后登录时间和IP
AdminUser updateUser = new AdminUser();
updateUser.setId(adminUser.getId());
updateUser.setLastLoginTime(LocalDateTime.now());
updateUser.setLastLoginIp(getIpAddress(request));
adminUserService.updateById(updateUser);
// 构建用户详情
AdminUserDetails userDetails = buildUserDetails(adminUser);
// 生成token
String token = jwtTokenProvider.generateToken(userDetails);
// 将token存入Redis,有效期等同于token过期时间
redisTemplate.opsForValue().set(TOKEN_KEY_PREFIX + token, token, 24, TimeUnit.HOURS);
// 构建登录返回数据
LoginVO loginVO = buildLoginVO(adminUser, token);
return Result.success(loginVO);
} catch (BadCredentialsException e) {
// 认证失败,记录登录失败次数
Integer retryCount = (Integer) redisTemplate.opsForValue().get(LOGIN_RETRY_KEY_PREFIX + username);
if (retryCount == null) {
retryCount = 0;
}
retryCount++;
// 如果超过最大重试次数,锁定账户
if (retryCount >= maxRetryCount) {
redisTemplate.opsForValue().set(LOGIN_LOCK_KEY_PREFIX + username, true, lockTime, TimeUnit.MINUTES);
return Result.error("账户已被锁定,请" + lockTime + "分钟后再试");
}
// 保存重试次数
redisTemplate.opsForValue().set(LOGIN_RETRY_KEY_PREFIX + username, retryCount, 1, TimeUnit.HOURS);
return Result.error("用户名或密码错误,还剩" + (maxRetryCount - retryCount) + "次机会");
} catch (Exception e) {
log.error("登录异常", e);
return Result.error("登录失败:" + e.getMessage());
}
}
@Override
public Result<Void> logout(String token) {
if (StringUtils.hasText(token)) {
// 从Redis中删除token
redisTemplate.delete(TOKEN_KEY_PREFIX + token);
}
return Result.success();
}
@Override
public Result<LoginVO> getCurrentUser(AdminUserDetails adminUserDetails) {
// 获取用户信息
AdminUser adminUser = adminUserService.getById(adminUserDetails.getId());
if (adminUser == null) {
return Result.error("用户不存在");
}
// 构建登录返回数据
LoginVO loginVO = buildLoginVO(adminUser, null);
return Result.success(loginVO);
}
/**
* 构建用户详情
*/
private AdminUserDetails buildUserDetails(AdminUser adminUser) {
AdminUserDetails userDetails = new AdminUserDetails();
userDetails.setId(adminUser.getId());
userDetails.setUsername(adminUser.getUsername());
userDetails.setPassword(adminUser.getPassword());
userDetails.setSalt(adminUser.getSalt());
// 设置权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 添加角色权限
if (adminUser.getRoles() != null && !adminUser.getRoles().isEmpty()) {
for (AdminRole role : adminUser.getRoles()) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
// 添加权限
if (role.getPermissions() != null && !role.getPermissions().isEmpty()) {
for (AdminPermission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getCode()));
}
}
}
}
userDetails.setAuthorities(authorities);
return userDetails;
}
/**
* 构建登录返回数据
*/
private LoginVO buildLoginVO(AdminUser adminUser, String token) {
LoginVO loginVO = new LoginVO();
loginVO.setToken(token);
loginVO.setTokenType("Bearer");
loginVO.setExpiresIn(24 * 60 * 60L); // 24小时
loginVO.setAdminId(adminUser.getId());
loginVO.setUsername(adminUser.getUsername());
loginVO.setRealName(adminUser.getRealName());
loginVO.setAvatar(adminUser.getAvatar());
// 设置角色和权限
List<String> roles = new ArrayList<>();
List<String> permissions = new ArrayList<>();
if (adminUser.getRoles() != null && !adminUser.getRoles().isEmpty()) {
// 设置角色
roles = adminUser.getRoles().stream()
.map(AdminRole::getCode)
.collect(Collectors.toList());
// 设置权限
for (AdminRole role : adminUser.getRoles()) {
if (role.getPermissions() != null && !role.getPermissions().isEmpty()) {
List<String> rolePermissions = role.getPermissions().stream()
.map(AdminPermission::getCode)
.collect(Collectors.toList());
permissions.addAll(rolePermissions);
}
}
}
loginVO.setRoles(roles);
loginVO.setPermissions(permissions);
return loginVO;
}
/**
* 获取请求的IP地址
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理的情况,第一个IP为客户端真实IP
if (ip != null && ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
}
@@ -0,0 +1,50 @@
package com.nuaa.video.admin.service.impl;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.service.PasswordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* 密码处理服务实现 - 使用标准BCrypt方式
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PasswordServiceImpl implements PasswordService {
private final PasswordEncoder passwordEncoder;
@Override
public String generateSalt() {
// BCrypt已包含盐值,此方法不再需要
return "";
}
@Override
public String encryptPassword(String rawPassword, String salt) {
// 忽略salt参数,直接使用BCrypt加密
return passwordEncoder.encode(rawPassword);
}
@Override
public boolean matches(String rawPassword, String salt, String encodedPassword) {
// 添加日志
log.debug("密码验证 - 原始密码: [隐藏], 加密密码: {}", encodedPassword);
// 忽略salt参数,直接使用BCrypt验证
boolean result = passwordEncoder.matches(rawPassword, encodedPassword);
log.debug("密码验证结果: {}", result);
return result;
}
@Override
public void setPassword(AdminUser user, String rawPassword) {
// 直接使用BCrypt加密密码
user.setPassword(passwordEncoder.encode(rawPassword));
// 不再需要设置盐值
user.setSalt(null);
}
}
@@ -0,0 +1,39 @@
package com.nuaa.video.admin.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 权限VO
*/
@Data
public class AdminPermissionVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 权限ID
*/
private Long id;
/**
* 权限名称
*/
private String name;
/**
* 权限编码
*/
private String code;
/**
* 权限描述
*/
private String description;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,50 @@
package com.nuaa.video.admin.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 角色VO
*/
@Data
public class AdminRoleVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long id;
/**
* 角色名称
*/
private String name;
/**
* 角色编码
*/
private String code;
/**
* 角色描述
*/
private String description;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 权限ID集合
*/
private List<Long> permissionIds;
/**
* 权限列表
*/
private List<AdminPermissionVO> permissions;
}
@@ -0,0 +1,75 @@
package com.nuaa.video.admin.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 管理员VO
*/
@Data
public class AdminUserVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 管理员ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 真实姓名
*/
private String realName;
/**
* 头像URL
*/
private String avatar;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 状态(0:禁用,1:正常)
*/
private Integer status;
/**
* 最后登录时间
*/
private LocalDateTime lastLoginTime;
/**
* 最后登录IP
*/
private String lastLoginIp;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 角色ID集合
*/
private List<Long> roleIds;
/**
* 角色列表
*/
private List<AdminRoleVO> roles;
}
@@ -0,0 +1,27 @@
package com.nuaa.video.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 验证码VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 验证码Key
*/
private String captchaKey;
/**
* 验证码图片(Base64编码)
*/
private String captchaImage;
}
@@ -0,0 +1,59 @@
package com.nuaa.video.admin.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 登录结果VO
*/
@Data
public class LoginVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 访问令牌
*/
private String token;
/**
* 令牌类型
*/
private String tokenType;
/**
* 过期时间(秒)
*/
private Long expiresIn;
/**
* 管理员ID
*/
private Long adminId;
/**
* 用户名
*/
private String username;
/**
* 真实姓名
*/
private String realName;
/**
* 头像URL
*/
private String avatar;
/**
* 角色编码列表
*/
private List<String> roles;
/**
* 权限编码列表
*/
private List<String> permissions;
}
@@ -0,0 +1,44 @@
package com.nuaa.video.admin.vo;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 系统配置VO
*/
@Data
public class SystemConfigVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 配置ID
*/
private Long id;
/**
* 配置键
*/
private String configKey;
/**
* 配置值
*/
private String configValue;
/**
* 配置描述
*/
private String description;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@@ -0,0 +1,53 @@
server:
port: 9004
spring:
application:
name: video-admin
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
sentinel:
transport:
dashboard: ${SENTINEL_HOST:sentinel}:${SENTINEL_PORT:8080}
port: 8724
eager: true
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: ${REDIS_HOST:redis}
port: ${REDIS_PORT:6379}
password: redis_EsJ8FX
database: 3
timeout: 10000
lettuce:
pool:
max-active: 200
max-wait: -1
max-idle: 10
min-idle: 0
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.nuaa.video.admin.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: is_deleted
logic-delete-value: 1
logic-not-delete-value: 0
app:
init:
admin-password: admin123
@@ -0,0 +1,38 @@
server:
port: 9006
spring:
application:
name: video-admin
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
config:
import:
- optional:nacos:common.yaml?refreshEnabled=true
- optional:nacos:common-redis-${spring.profiles.active}.yaml?refreshEnabled=true
- optional:nacos:${spring.application.name}.yaml?refreshEnabled=true
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}}
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
ip: ${SPRING_CLOUD_NACOS_DISCOVERY_IP:}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
group: ${spring.cloud.nacos.discovery.group}
file-extension: yaml
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
@@ -0,0 +1,23 @@
spring:
application:
name: video-admin
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
ephemeral: true
ip: ${ADMIN_IP:}
port: ${server.port:9004}
fail-fast: true
register-enabled: true
config:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
file-extension: yaml
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminOperationLogMapper">
<!-- 分页查询操作日志 -->
<select id="selectLogList" resultType="com.nuaa.video.admin.entity.AdminOperationLog">
SELECT
*
FROM
admin_operation_log
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="operation != null and operation != ''">
AND operation LIKE CONCAT('%', #{operation}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="startTime != null and startTime != ''">
AND operation_time &gt;= #{startTime}
</if>
<if test="endTime != null and endTime != ''">
AND operation_time &lt;= #{endTime}
</if>
</where>
ORDER BY operation_time DESC
</select>
</mapper>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminPermissionMapper">
<!-- 获取角色的权限列表 -->
<select id="selectPermissionsByRoleId" resultType="com.nuaa.video.admin.entity.AdminPermission">
SELECT
p.*
FROM
admin_permission p
INNER JOIN
admin_role_permission rp ON p.id = rp.permission_id
WHERE
rp.role_id = #{roleId}
</select>
<!-- 获取管理员的权限列表 -->
<select id="selectPermissionsByAdminId" resultType="com.nuaa.video.admin.entity.AdminPermission">
SELECT DISTINCT
p.*
FROM
admin_permission p
INNER JOIN
admin_role_permission rp ON p.id = rp.permission_id
INNER JOIN
admin_role r ON rp.role_id = r.id
INNER JOIN
admin_user_role ur ON r.id = ur.role_id
WHERE
ur.admin_id = #{adminId}
</select>
</mapper>
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminRoleMapper">
<!-- 分页查询角色列表 -->
<select id="selectRoleList" resultMap="roleMap">
SELECT
r.*,
p.id AS perm_id,
p.name AS perm_name,
p.code AS perm_code,
p.description AS perm_description
FROM
admin_role r
LEFT JOIN
admin_role_permission rp ON r.id = rp.role_id
LEFT JOIN
admin_permission p ON rp.permission_id = p.id
<where>
<if test="keyword != null and keyword != ''">
AND (r.name LIKE CONCAT('%', #{keyword}, '%')
OR r.code LIKE CONCAT('%', #{keyword}, '%')
OR r.description LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
ORDER BY r.id ASC
</select>
<!-- 获取角色详情 -->
<select id="selectRoleDetail" resultMap="roleMap">
SELECT
r.*,
p.id AS perm_id,
p.name AS perm_name,
p.code AS perm_code,
p.description AS perm_description
FROM
admin_role r
LEFT JOIN
admin_role_permission rp ON r.id = rp.role_id
LEFT JOIN
admin_permission p ON rp.permission_id = p.id
WHERE
r.id = #{id}
</select>
<!-- 获取管理员的角色列表 -->
<select id="selectRolesByAdminId" resultType="com.nuaa.video.admin.entity.AdminRole">
SELECT
r.*
FROM
admin_role r
INNER JOIN
admin_user_role ur ON r.id = ur.role_id
WHERE
ur.admin_id = #{adminId}
</select>
<!-- 结果映射 -->
<resultMap id="roleMap" type="com.nuaa.video.admin.entity.AdminRole">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="code" column="code"/>
<result property="description" column="description"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<!-- 权限集合 -->
<collection property="permissions" ofType="com.nuaa.video.admin.entity.AdminPermission">
<id property="id" column="perm_id"/>
<result property="name" column="perm_name"/>
<result property="code" column="perm_code"/>
<result property="description" column="perm_description"/>
</collection>
</resultMap>
</mapper>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminRolePermissionMapper">
<!-- 批量插入角色权限关联 -->
<insert id="batchInsert">
INSERT INTO admin_role_permission (role_id, permission_id, created_by, create_time)
VALUES
<foreach collection="permissionIds" item="permissionId" separator=",">
(#{roleId}, #{permissionId}, #{createdBy}, NOW())
</foreach>
</insert>
</mapper>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminSystemConfigMapper">
<!-- 根据配置键获取配置值 -->
<select id="getConfigValue" resultType="java.lang.String">
SELECT
config_value
FROM
admin_system_config
WHERE
config_key = #{configKey}
LIMIT 1
</select>
</mapper>
@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminUserMapper">
<!-- 根据用户名查询管理员信息 -->
<select id="selectByUsername" resultMap="adminUserMap">
SELECT
au.*,
ar.id AS role_id,
ar.name AS role_name,
ar.code AS role_code,
ar.description AS role_description,
ap.id AS perm_id,
ap.name AS perm_name,
ap.code AS perm_code,
ap.description AS perm_description
FROM
admin_user au
LEFT JOIN
admin_user_role aur ON au.id = aur.admin_id
LEFT JOIN
admin_role ar ON aur.role_id = ar.id
LEFT JOIN
admin_role_permission arp ON ar.id = arp.role_id
LEFT JOIN
admin_permission ap ON arp.permission_id = ap.id
WHERE
au.username = #{username}
</select>
<!-- 分页查询管理员列表 -->
<select id="selectUserList" resultMap="adminUserMap">
SELECT
au.*,
ar.id AS role_id,
ar.name AS role_name,
ar.code AS role_code
FROM
admin_user au
LEFT JOIN
admin_user_role aur ON au.id = aur.admin_id
LEFT JOIN
admin_role ar ON aur.role_id = ar.id
<where>
<if test="keyword != null and keyword != ''">
AND (au.username LIKE CONCAT('%', #{keyword}, '%')
OR au.real_name LIKE CONCAT('%', #{keyword}, '%')
OR au.email LIKE CONCAT('%', #{keyword}, '%')
OR au.phone LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
GROUP BY au.id
ORDER BY au.id ASC
</select>
<!-- 获取管理员详情 -->
<select id="selectUserDetail" resultMap="adminUserMap">
SELECT
au.*,
ar.id AS role_id,
ar.name AS role_name,
ar.code AS role_code,
ar.description AS role_description,
ap.id AS perm_id,
ap.name AS perm_name,
ap.code AS perm_code,
ap.description AS perm_description
FROM
admin_user au
LEFT JOIN
admin_user_role aur ON au.id = aur.admin_id
LEFT JOIN
admin_role ar ON aur.role_id = ar.id
LEFT JOIN
admin_role_permission arp ON ar.id = arp.role_id
LEFT JOIN
admin_permission ap ON arp.permission_id = ap.id
WHERE
au.id = #{id}
</select>
<!-- 结果映射 -->
<resultMap id="adminUserMap" type="com.nuaa.video.admin.entity.AdminUser">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="salt" column="salt"/>
<result property="realName" column="real_name"/>
<result property="avatar" column="avatar"/>
<result property="email" column="email"/>
<result property="phone" column="phone"/>
<result property="status" column="status"/>
<result property="lastLoginTime" column="last_login_time"/>
<result property="lastLoginIp" column="last_login_ip"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<!-- 角色集合 -->
<collection property="roles" ofType="com.nuaa.video.admin.entity.AdminRole">
<id property="id" column="role_id"/>
<result property="name" column="role_name"/>
<result property="code" column="role_code"/>
<result property="description" column="role_description"/>
<!-- 权限集合 -->
<collection property="permissions" ofType="com.nuaa.video.admin.entity.AdminPermission">
<id property="id" column="perm_id"/>
<result property="name" column="perm_name"/>
<result property="code" column="perm_code"/>
<result property="description" column="perm_description"/>
</collection>
</collection>
</resultMap>
</mapper>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuaa.video.admin.mapper.AdminUserRoleMapper">
<!-- 批量插入用户角色关联 -->
<insert id="batchInsert">
INSERT INTO admin_user_role (admin_id, role_id, created_by, create_time)
VALUES
<foreach collection="roleIds" item="roleId" separator=",">
(#{adminId}, #{roleId}, #{createdBy}, NOW())
</foreach>
</insert>
</mapper>
+8
View File
@@ -0,0 +1,8 @@
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/video-auth-*.jar app.jar
# 添加JVM内存优化配置
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar app.jar"]
+140
View File
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>video-platform</artifactId>
<groupId>com.nuaa.video</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>video-auth</artifactId>
<dependencies>
<!-- 引入公共模块 -->
<dependency>
<groupId>com.nuaa.video</groupId>
<artifactId>video-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Spring Security OAuth2 Authorization Server -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,16 @@
package com.nuaa.video.auth;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
@@ -0,0 +1,38 @@
package com.nuaa.video.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 创建配置好的ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 使用Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,85 @@
package com.nuaa.video.auth.config;
import cn.hutool.jwt.Claims;
import com.nuaa.video.auth.filter.JwtAuthenticationFilter;
import com.nuaa.video.auth.service.AuthUserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.View;
import java.io.IOException;
/**
* 安全配置
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final AuthUserService authUserService;
// 使用构造注入并添加@Lazy注解
public SecurityConfig(@Lazy AuthUserService authUserService) {
this.authUserService = authUserService;
}
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 认证管理器
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
/**
* 安全过滤器链
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, View error, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/auth/login",
"/auth/register",
"/auth/captcha",
"/actuator/**",
"/error"
).permitAll()
.anyRequest().authenticated()
)
//添加JWT认证过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -0,0 +1,49 @@
package com.nuaa.video.auth.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nuaa.video.auth.entity.AuthUser;
import com.nuaa.video.auth.service.AuthUserService;
import com.nuaa.video.common.model.vo.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 认证管理控制器 - 供管理后台调用
*/
@Slf4j
@RestController
@RequestMapping("/auth/admin")
@RequiredArgsConstructor
public class AdminAuthController {
private final AuthUserService authUserService;
/**
* 获取登录统计 - 管理接口
*/
@GetMapping("/login/stats")
public Result<Object> getLoginStats() {
return authUserService.getLoginStats();
}
/**
* 管理员重置用户密码 - 管理接口
*/
@PutMapping("/reset-password/{userId}")
public Result<Void> resetPassword(
@PathVariable Long userId,
@RequestParam String newPassword) {
return authUserService.adminResetPassword(userId, newPassword);
}
/**
* 锁定/解锁用户账号 - 管理接口
*/
@PutMapping("/lock/{userId}/{locked}")
public Result<Void> lockUser(
@PathVariable Long userId,
@PathVariable Boolean locked) {
return authUserService.lockUser(userId, locked);
}
}
@@ -0,0 +1,125 @@
package com.nuaa.video.auth.controller;
import com.nuaa.video.auth.dto.LoginDTO;
import com.nuaa.video.auth.dto.RegisterDTO;
import com.nuaa.video.auth.dto.TokenInfoDTO;
import com.nuaa.video.auth.entity.AuthUser;
import com.nuaa.video.auth.service.AuthUserService;
import com.nuaa.video.auth.service.CaptchaService;
import com.nuaa.video.common.model.vo.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 认证控制器
*/
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthUserService authUserService;
private final CaptchaService captchaService;
/**
* 用户登录
*/
@PostMapping("/login")
public Result<TokenInfoDTO> login(@RequestBody @Valid LoginDTO loginDTO) {
return authUserService.login(loginDTO);
}
/**
* 用户注册
*/
@PostMapping("/register")
public Result<String> register(@RequestBody @Valid RegisterDTO registerDTO) {
return authUserService.register(registerDTO);
}
/**
* 生成验证码
*/
@GetMapping("/captcha")
public Result<Map<String, Object>> captcha() {
return captchaService.generateCaptcha();
}
/**
* 刷新令牌
*/
@PostMapping("/refresh")
public Result<TokenInfoDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
return authUserService.refreshToken(refreshToken);
}
/**
* 用户登出
*/
@PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return authUserService.logout(userId);
}
/**
* 获取当前用户信息
*/
@GetMapping("/current")
public Result<AuthUser> currentUser(HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
if (userId == null) {
return Result.error("未获取到用户ID");
}
return authUserService.getCurrentUser(userId);
}
/**
* 从请求中获取用户ID
*/
private Long getUserIdFromRequest(HttpServletRequest request) {
// 首先尝试从请求属性中获取
Object userIdAttr = request.getAttribute("userId");
if (userIdAttr != null) {
return (Long) userIdAttr;
}
// 如果请求属性中没有,尝试从认证上下文获取
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() instanceof Long) {
return (Long) authentication.getDetails();
}
// 最后尝试从请求头获取(如果前端直接传递)
String userIdHeader = request.getHeader("userId");
if (userIdHeader != null && !userIdHeader.isEmpty()) {
try {
return Long.parseLong(userIdHeader);
} catch (NumberFormatException e) {
log.warn("Invalid userId header: {}", userIdHeader);
}
}
return null;
}
/**
* 根据ID获取用户名
*/
@GetMapping("/username/{userId}")
public String getUsernameById(@PathVariable Long userId) {
AuthUser user = authUserService.getById(userId);
return user != null ? user.getUsername() : null;
}
}
@@ -0,0 +1,36 @@
package com.nuaa.video.auth.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serializable;
/**
* 登录请求DTO
*/
@Data
public class LoginDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 验证码
*/
private String captcha;
/**
* 验证码KEY
*/
private String captchaKey;
}
@@ -0,0 +1,60 @@
package com.nuaa.video.auth.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.io.Serializable;
/**
* 注册请求DTO
*/
@Data
public class RegisterDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Pattern(regexp = "^[a-zA-Z0-9_]{4,16}$", message = "用户名必须为4-16位字母、数字或下划线")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^.{6,20}$", message = "密码长度必须为6-20位")
private String password;
/**
* 确认密码
*/
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 手机号
*/
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String captcha;
/**
* 验证码KEY
*/
@NotBlank(message = "验证码KEY不能为空")
private String captchaKey;
}

Some files were not shown because too many files have changed in this diff Show More