refactor(project): Reorganize frontend and backend directories
This commit is contained in:
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 "=========================================="
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
@@ -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>
|
||||
@@ -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 "下一步: 请克隆项目并执行部署脚本"
|
||||
@@ -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"]
|
||||
@@ -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:开启)');
|
||||
@@ -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', '');
|
||||
@@ -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');
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM eclipse-temurin:17-jre
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY target/video-admin-*.jar app.jar
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -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 "";
|
||||
}
|
||||
+220
@@ -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;
|
||||
}
|
||||
}
|
||||
+111
@@ -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);
|
||||
}
|
||||
}
|
||||
+119
@@ -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);
|
||||
}
|
||||
}
|
||||
+72
@@ -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;
|
||||
}
|
||||
+44
@@ -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);
|
||||
}
|
||||
+42
@@ -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);
|
||||
}
|
||||
+62
@@ -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);
|
||||
}
|
||||
+45
@@ -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("重置用户密码失败,用户服务不可用");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
+31
@@ -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);
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+57
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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("密码不正确");
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -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;
|
||||
}
|
||||
}
|
||||
+110
@@ -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);
|
||||
}
|
||||
+72
@@ -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("管理员账号初始化完成");
|
||||
}
|
||||
}
|
||||
+310
@@ -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();
|
||||
}
|
||||
}
|
||||
+279
@@ -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;
|
||||
}
|
||||
}
|
||||
+50
@@ -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 >= #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null and endTime != ''">
|
||||
AND operation_time <= #{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>
|
||||
@@ -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"]
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+49
@@ -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
Reference in New Issue
Block a user