前端打包

This commit is contained in:
2025-11-24 16:53:17 +08:00
parent df57d05817
commit b51faf731a
10 changed files with 218 additions and 385 deletions

View File

@@ -17,15 +17,16 @@ RUN mkdir -p /docker-entrypoint-initdb.d /opt/sql
# 复制所有SQL文件保持目录结构 # 复制所有SQL文件保持目录结构
COPY schoolNewsServ/.bin/mysql/sql/ /opt/sql/ COPY schoolNewsServ/.bin/mysql/sql/ /opt/sql/
# 复制并调整reInit.sh为Docker环境 # 复制并调整reInit.sh为Docker环境,设置执行权限
COPY schoolNewsServ/.bin/mysql/sql/reInit.sh /opt/sql/ COPY schoolNewsServ/.bin/mysql/sql/reInit.sh /opt/sql/
RUN sed -i 's/DB_HOST="localhost"/DB_HOST="localhost"/' /opt/sql/reInit.sh && \ RUN sed -i 's/DB_HOST="localhost"/DB_HOST="localhost"/' /opt/sql/reInit.sh && \
sed -i 's/DB_PORT="3306"/DB_PORT="3306"/' /opt/sql/reInit.sh && \ sed -i 's/DB_PORT="3306"/DB_PORT="3306"/' /opt/sql/reInit.sh && \
sed -i 's/DB_USER="root"/DB_USER="root"/' /opt/sql/reInit.sh && \ sed -i 's/DB_USER="root"/DB_USER="root"/' /opt/sql/reInit.sh && \
sed -i 's/DB_PASSWORD="123456"/DB_PASSWORD="${MYSQL_ROOT_PASSWORD}"/' /opt/sql/reInit.sh && \ sed -i 's/DB_PASSWORD="123456"/DB_PASSWORD="${MYSQL_ROOT_PASSWORD}"/' /opt/sql/reInit.sh && \
sed -i 's/DB_NAME="school_news"/DB_NAME="${MYSQL_DATABASE}"/' /opt/sql/reInit.sh && \ sed -i 's/DB_NAME="school_news"/DB_NAME="${MYSQL_DATABASE}"/' /opt/sql/reInit.sh && \
chmod +x /opt/sql/reInit.sh sed -i 's|LOG_FILE="$SCRIPT_DIR/reInit.log"|LOG_FILE="/tmp/reInit.log"|' /opt/sql/reInit.sh && \
chmod +x /opt/sql/reInit.sh && \
chmod +x /opt/sql/sensitiveData/importSensitiveWords.sh
# 创建Docker初始化适配脚本 # 创建Docker初始化适配脚本
RUN cat > /docker-entrypoint-initdb.d/01-init-database.sh <<'EOF' RUN cat > /docker-entrypoint-initdb.d/01-init-database.sh <<'EOF'
#!/bin/bash #!/bin/bash
@@ -54,10 +55,8 @@ export DB_PASSWORD="${MYSQL_ROOT_PASSWORD}"
export DB_NAME="${MYSQL_DATABASE}" export DB_NAME="${MYSQL_DATABASE}"
export MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" export MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
# 直接调用reInit.sh的初始化函数
echo "执行数据库初始化..."
# Source reInit.sh并调用其初始化函数 # Source reInit.sh并调用其初始化函数
echo "执行数据库初始化使用reInit.sh..."
source reInit.sh source reInit.sh
# 调用reInit.sh的核心函数跳过备份和删除 # 调用reInit.sh的核心函数跳过备份和删除
@@ -76,11 +75,9 @@ UPDATE tb_sys_config
SET config_value = '/app/crawler' SET config_value = '/app/crawler'
WHERE config_key = 'crawler.basePath'; WHERE config_key = 'crawler.basePath';
-- 如果配置不存在则插入 UPDATE _db_init_status
INSERT IGNORE INTO tb_sys_config (config_key, config_value, config_desc, created_at) SET status = 'success'
VALUES WHERE script_name = '01-init-database.sql';
('crawler.pythonPath', '/usr/bin/python3', 'Docker容器内Python路径', NOW()),
('crawler.basePath', '/app/crawler', 'Docker容器内爬虫脚本路径', NOW());
SELECT '✅ 数据库初始化完成!' AS message; SELECT '✅ 数据库初始化完成!' AS message;
SELECT '默认用户: admin, 密码: admin123' AS tip; SELECT '默认用户: admin, 密码: admin123' AS tip;

View File

@@ -1,12 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
校园新闻管理系统 - Docker环境日志配置 校园新闻管理系统 - Admin模块日志配置
--> -->
<configuration status="WARN" monitorInterval="30"> <configuration status="WARN" monitorInterval="30">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--变量配置-->
<Properties> <Properties>
<!-- 格式化输出:%date表示日期%thread表示线程名%-5level级别从左显示5个字符宽度 %msg日志消息%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="/app/logs" /> <!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="./logs" />
<!-- Admin模块日志文件名 -->
<property name="FILE_NAME" value="school-news-admin" /> <property name="FILE_NAME" value="school-news-admin" />
<!-- 设置系统属性 -->
<property name="file.encoding" value="UTF-8" /> <property name="file.encoding" value="UTF-8" />
<property name="console.encoding" value="UTF-8" /> <property name="console.encoding" value="UTF-8" />
<property name="stdout.encoding" value="UTF-8" /> <property name="stdout.encoding" value="UTF-8" />
@@ -14,60 +22,171 @@
</Properties> </Properties>
<appenders> <appenders>
<console name="Console" target="SYSTEM_OUT"> <console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/> <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <!--控制台输出debug及以上级别的信息-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</console> </console>
<!--文件会打印出所有信息这个log每次运行程序会自动清空由append属性决定适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/${FILE_NAME}-test.log" append="false"> <File name="Filelog" fileName="${FILE_PATH}/${FILE_NAME}-test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/> <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
</File> </File>
<!-- 这个会打印出所有的info及以下级别的信息每次大小超过size则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息onMatch其他的直接拒绝onMismatch-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/> <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
<Policies> <Policies>
<!--interval属性用来指定多久滚动一次默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/> <TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/> <SizeBasedTriggeringPolicy size="10MB"/>
</Policies> </Policies>
<!-- DefaultRolloverStrategy属性如不设置则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/> <DefaultRolloverStrategy max="15"/>
</RollingFile> </RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息每次大小超过size则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息onMatch其他的直接拒绝onMismatch-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/> <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
<Policies> <Policies>
<!--interval属性用来指定多久滚动一次默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/> <TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/> <SizeBasedTriggeringPolicy size="10MB"/>
</Policies> </Policies>
<!-- DefaultRolloverStrategy属性如不设置则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/> <DefaultRolloverStrategy max="15"/>
</RollingFile> </RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息每次大小超过size则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息onMatch其他的直接拒绝onMismatch-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/> <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
<Policies> <Policies>
<!--interval属性用来指定多久滚动一次默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/> <TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/> <SizeBasedTriggeringPolicy size="10MB"/>
</Policies> </Policies>
<!-- DefaultRolloverStrategy属性如不设置则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/> <DefaultRolloverStrategy max="15"/>
</RollingFile> </RollingFile>
<!-- 数据库日志Appender - 异步写入DEBUG级别及以上的日志到数据库 -->
<DatabaseAppender name="DatabaseAppender" ignoreExceptions="false"> <DatabaseAppender name="DatabaseAppender" ignoreExceptions="false">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</DatabaseAppender> </DatabaseAppender>
</appenders> </appenders>
<!--Logger节点用来单独指定日志的形式比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers只有定义了logger并引入的appenderappender才会生效-->
<loggers> <loggers>
<logger name="org.mybatis" level="info" additivity="false">
<!--过滤掉spring的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="debug" additivity="false">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
</logger> </logger>
<!--监控系统信息-->
<!--若是additivity设为false则 子Logger 只会在自己的appender里输出不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false"> <Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
</Logger> </Logger>
<Logger name="org.xyzh" level="info" additivity="false"> <!-- MyBatis Mapper 日志配置 - 打印SQL -->
<Logger name="org.xyzh.achievement.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.ai.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.system.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.news.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.study.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.crontab.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.message.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<!-- 项目包日志配置 - Auth模块 -->
<Logger name="org.xyzh.auth" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<!-- 项目包日志配置 - System模块 -->
<Logger name="org.xyzh.system" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<!-- 项目包日志配置 - News模块 -->
<Logger name="org.xyzh.news" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<!-- 项目包日志配置 - Common模块 -->
<Logger name="org.xyzh.common" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<!-- 项目包日志配置 - Achievement模块 -->
<Logger name="org.xyzh.achievement" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<Logger name="org.xyzh.crontab" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<Logger name="org.xyzh.message" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
<AppenderRef ref="DatabaseAppender"/>
</Logger>
<Logger name="org.xyzh.sensitive" level="debug" additivity="false">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
<AppenderRef ref="Filelog"/> <AppenderRef ref="Filelog"/>
<AppenderRef ref="RollingFileInfo"/> <AppenderRef ref="RollingFileInfo"/>
@@ -85,4 +204,5 @@
<appender-ref ref="DatabaseAppender"/> <appender-ref ref="DatabaseAppender"/>
</root> </root>
</loggers> </loggers>
</configuration> </configuration>

View File

@@ -13,7 +13,7 @@ services:
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456} MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456}
TZ: Asia/Shanghai TZ: Asia/Shanghai
ports: ports:
- "${MYSQL_PORT:-3306}:3306" - "${MYSQL_PORT:-3307}:3306"
volumes: volumes:
# 数据持久化(命名卷) # 数据持久化(命名卷)
- mysql-data:/var/lib/mysql - mysql-data:/var/lib/mysql
@@ -26,14 +26,16 @@ services:
- --default-authentication-plugin=mysql_native_password - --default-authentication-plugin=mysql_native_password
- --max_connections=1000 - --max_connections=1000
- --max_allowed_packet=64M - --max_allowed_packet=64M
- --local-infile=1
networks: networks:
- school-news-network - school-news-network
healthcheck: healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD:-123456}"] # 只有当 MySQL 可访问且敏感词表中至少有一条 deny 记录时,才认为 healthy
test: ["CMD-SHELL", "mysql -uroot -p${MYSQL_ROOT_PASSWORD:-123456} -D ${MYSQL_DATABASE:-school_news} -e \"SELECT 'ok' FROM tb_sensitive_word WHERE type='deny' LIMIT 1;\" 2>/dev/null | grep -q ok"]
interval: 10s interval: 10s
timeout: 5s timeout: 10s
retries: 5 retries: 10
start_period: 30s start_period: 60s
# Redis缓存 # Redis缓存
redis: redis:
@@ -143,8 +145,10 @@ services:
ports: ports:
- "${NGINX_PORT:-80}:80" - "${NGINX_PORT:-80}:80"
volumes: volumes:
# Nginx配置文件(命名卷) # 仅挂载自定义 Nginx配置文件
- nginx-config:/etc/nginx:ro - ./volumes/nginx/config/nginx.conf:/etc/nginx/nginx.conf
# 仅挂载站点配置目录conf.d保留镜像内的 mime.types 等其他文件
- nginx-conf-d:/etc/nginx/conf.d:ro
# 日志目录(命名卷) # 日志目录(命名卷)
- nginx-logs:/var/log/nginx - nginx-logs:/var/log/nginx
networks: networks:
@@ -233,12 +237,19 @@ volumes:
device: ./volumes/web/logs device: ./volumes/web/logs
# ===== Nginx ===== # ===== Nginx =====
nginx-config: nginx-conf-file:
driver: local driver: local
driver_opts: driver_opts:
type: none type: none
o: bind o: bind
device: ./volumes/nginx/config device: ./volumes/nginx/config/nginx.conf
nginx-conf-d:
driver: local
driver_opts:
type: none
o: bind
device: ./volumes/nginx/config/conf.d
nginx-logs: nginx-logs:
driver: local driver: local

View File

@@ -19,7 +19,7 @@ CREATE TABLE IF NOT EXISTS _db_init_status (
id INT PRIMARY KEY AUTO_INCREMENT, id INT PRIMARY KEY AUTO_INCREMENT,
script_name VARCHAR(255) NOT NULL UNIQUE, script_name VARCHAR(255) NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(50) DEFAULT 'success' status VARCHAR(50) DEFAULT 'init'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 记录初始化标记 -- 记录初始化标记

View File

@@ -1,231 +0,0 @@
#!/bin/bash
##############################################
# 数据库初始化SQL准备脚本
# 功能将SQL文件复制到Docker初始化目录
# 基于schoolNewsServ/.bin/mysql/sql/reInit.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"
}
# 路径定义
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SQL_SOURCE_DIR="../../schoolNewsServ/.bin/mysql/sql"
INIT_DB_DIR="$SCRIPT_DIR"
echo "========================================"
echo "数据库初始化SQL准备"
echo "========================================"
log_info "源SQL目录: $SQL_SOURCE_DIR"
log_info "目标目录: $INIT_DB_DIR"
echo "========================================"
echo ""
# 检查源目录
if [ ! -d "$SQL_SOURCE_DIR" ]; then
log_error "源SQL目录不存在: $SQL_SOURCE_DIR"
exit 1
fi
# 备份现有SQL文件
if ls "$INIT_DB_DIR"/*.sql 1> /dev/null 2>&1; then
log_warn "发现现有SQL文件正在备份..."
BACKUP_DIR="$INIT_DB_DIR/backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
mv "$INIT_DB_DIR"/*.sql "$BACKUP_DIR/" 2>/dev/null || true
log_info "已备份到: $BACKUP_DIR"
echo ""
fi
# 创建02-create-tables.sql
log_info "创建表结构脚本: 02-create-tables.sql"
cat > "$INIT_DB_DIR/02-create-tables.sql" << 'EOF'
-- ========================================
-- 校园新闻管理系统 - 表结构创建脚本
-- 自动生成时间: $(date '+%Y-%m-%d %H:%M:%S')
-- 基于: schoolNewsServ/.bin/mysql/sql/
-- ========================================
USE school_news;
-- 检查是否已执行过此脚本
SET @executed = (SELECT COUNT(*) FROM _db_init_status WHERE script_name = '02-create-tables.sql');
-- 如果已执行过,记录并退出
SELECT CASE
WHEN @executed > 0 THEN '表结构已存在,跳过创建'
ELSE '开始创建表结构...'
END AS message;
EOF
# 合并所有createTable*.sql文件
log_info "合并表结构文件..."
TABLE_FILES=(
"createTableUser.sql"
"createTablePermission.sql"
"createTablePermissionControl.sql"
"createTableResource.sql"
"createTableCourse.sql"
"createTableLearning.sql"
"createTableUserCenter.sql"
"createTableAI.sql"
"createTableSystem.sql"
"createTableAchievement.sql"
"createTableCrontab.sql"
"createTableMessage.sql"
"createTableSensitive.sql"
)
for file in "${TABLE_FILES[@]}"; do
if [ -f "$SQL_SOURCE_DIR/$file" ]; then
echo "" >> "$INIT_DB_DIR/02-create-tables.sql"
echo "-- ========================================" >> "$INIT_DB_DIR/02-create-tables.sql"
echo "-- $file" >> "$INIT_DB_DIR/02-create-tables.sql"
echo "-- ========================================" >> "$INIT_DB_DIR/02-create-tables.sql"
cat "$SQL_SOURCE_DIR/$file" >> "$INIT_DB_DIR/02-create-tables.sql"
log_info "$file"
else
log_warn "$file (文件不存在)"
fi
done
# 添加执行状态记录
cat >> "$INIT_DB_DIR/02-create-tables.sql" << 'EOF'
-- 记录执行状态
INSERT IGNORE INTO _db_init_status (script_name)
VALUES ('02-create-tables.sql');
SELECT '表结构创建完成' AS message;
EOF
echo ""
# 创建03-init-data.sql
log_info "创建初始数据脚本: 03-init-data.sql"
cat > "$INIT_DB_DIR/03-init-data.sql" << 'EOF'
-- ========================================
-- 校园新闻管理系统 - 初始数据导入脚本
-- 自动生成时间: $(date '+%Y-%m-%d %H:%M:%S')
-- 基于: schoolNewsServ/.bin/mysql/sql/
-- ========================================
USE school_news;
-- 检查是否已执行过此脚本
SET @executed = (SELECT COUNT(*) FROM _db_init_status WHERE script_name = '03-init-data.sql');
SELECT CASE
WHEN @executed > 0 THEN '初始数据已存在,跳过导入'
ELSE '开始导入初始数据...'
END AS message;
EOF
# 合并初始数据文件
log_info "合并初始数据文件..."
DATA_FILES=(
"initMenuData.sql"
"initAllData.sql"
"initCrontabMetaData.sql"
)
for file in "${DATA_FILES[@]}"; do
if [ -f "$SQL_SOURCE_DIR/$file" ]; then
echo "" >> "$INIT_DB_DIR/03-init-data.sql"
echo "-- ========================================" >> "$INIT_DB_DIR/03-init-data.sql"
echo "-- $file" >> "$INIT_DB_DIR/03-init-data.sql"
echo "-- ========================================" >> "$INIT_DB_DIR/03-init-data.sql"
cat "$SQL_SOURCE_DIR/$file" >> "$INIT_DB_DIR/03-init-data.sql"
log_info "$file"
else
log_warn "$file (文件不存在)"
fi
done
# 添加爬虫配置初始化
cat >> "$INIT_DB_DIR/03-init-data.sql" << 'EOF'
-- ========================================
-- Docker环境爬虫配置
-- ========================================
INSERT IGNORE INTO tb_sys_config (config_key, config_value, config_desc, created_at)
VALUES
('crawler.pythonPath', '/usr/bin/python3', 'Docker容器内Python路径', NOW()),
('crawler.basePath', '/app/crawler', 'Docker容器内爬虫脚本路径', NOW());
-- 记录执行状态
INSERT IGNORE INTO _db_init_status (script_name)
VALUES ('03-init-data.sql');
SELECT '初始数据导入完成' AS message;
SELECT '默认用户: admin, 密码: admin123' AS tip;
EOF
echo ""
# 生成统计信息
log_info "生成文件统计..."
cat > "$INIT_DB_DIR/00-init-summary.txt" << EOF
========================================
数据库初始化脚本准备完成
========================================
生成时间: $(date '+%Y-%m-%d %H:%M:%S')
源目录: $SQL_SOURCE_DIR
生成的文件:
01-init-database.sql - 数据库创建(已存在)
02-create-tables.sql - 表结构创建
03-init-data.sql - 初始数据导入
执行顺序:
1. 创建数据库和初始化标记表
2. 创建所有业务表
3. 导入初始数据(用户、菜单、系统配置等)
默认账户:
用户名: admin
密码: admin123
角色: 管理员
注意事项:
1. 脚本具有幂等性,可以重复执行
2. 使用 _db_init_status 表跟踪执行状态
3. Docker容器启动时自动执行这些脚本
4. 如需重新初始化,删除数据卷: docker-compose down -v
========================================
EOF
echo ""
log_info "========================================"
log_info "准备完成!"
log_info "========================================"
log_info "生成的文件:"
ls -lh "$INIT_DB_DIR"/*.sql 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
echo ""
log_info "下一步:"
echo " 1. 检查生成的SQL文件"
echo " 2. 运行Docker构建: ./build.sh"
echo " 3. 启动服务: cd docker && docker-compose up -d"
log_info "========================================"

View File

@@ -32,6 +32,7 @@ mkdir -p "${VOLUMES_DIR}/nginx/logs"
# 复制配置文件模板 # 复制配置文件模板
echo "复制配置文件模板..." echo "复制配置文件模板..."
[ -f redis/redis.conf ] && cp redis/redis.conf "${VOLUMES_DIR}/redis/config/" || echo " ⚠️ redis.conf 不存在" [ -f redis/redis.conf ] && cp redis/redis.conf "${VOLUMES_DIR}/redis/config/" || echo " ⚠️ redis.conf 不存在"
[ -f mysql/my.cnf ] && cp mysql/my.cnf "${VOLUMES_DIR}/mysql/" || echo " ⚠️ my.cnf 不存在"
[ -f config/application.yml ] && cp config/application.yml "${VOLUMES_DIR}/serv/config/" || echo " ⚠️ application.yml 不存在" [ -f config/application.yml ] && cp config/application.yml "${VOLUMES_DIR}/serv/config/" || echo " ⚠️ application.yml 不存在"
[ -f config/log4j2-spring.xml ] && cp config/log4j2-spring.xml "${VOLUMES_DIR}/serv/config/" || echo " ⚠️ log4j2-spring.xml 不存在" [ -f config/log4j2-spring.xml ] && cp config/log4j2-spring.xml "${VOLUMES_DIR}/serv/config/" || echo " ⚠️ log4j2-spring.xml 不存在"
[ -f config/web-app-config.js ] && cp config/web-app-config.js "${VOLUMES_DIR}/web/config/app-config.js" || echo " ⚠️ web-app-config.js 不存在" [ -f config/web-app-config.js ] && cp config/web-app-config.js "${VOLUMES_DIR}/web/config/app-config.js" || echo " ⚠️ web-app-config.js 不存在"

View File

@@ -1,23 +1,20 @@
# ====================================
# Nginx主配置文件
# 可通过docker-compose.yml挂载自定义配置
# ====================================
user nginx; user nginx;
worker_processes auto; worker_processes auto;
error_log /var/log/nginx/error.log notice; error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid; pid /run/nginx.pid;
events { events {
worker_connections 1024; worker_connections 1024;
} }
http { http {
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" ' '$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'; '"$http_user_agent" "$http_x_forwarded_for"';
@@ -25,20 +22,11 @@ http {
access_log /var/log/nginx/access.log main; access_log /var/log/nginx/access.log main;
sendfile on; sendfile on;
tcp_nopush on; #tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65; keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩 #gzip on;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 包含站点配置
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
} }

View File

@@ -93,70 +93,34 @@ echo ""
log_info "开始导入敏感词..." log_info "开始导入敏感词..."
START_TIME=$(date +%s) START_TIME=$(date +%s)
# 创建临时SQL文件 # 预处理转义单引号生成临时词表文件仅一列word
TEMP_SQL=$(mktemp) TEMP_WORDS=$(mktemp)
trap "rm -f ${TEMP_SQL}" EXIT trap "rm -f ${TEMP_WORDS}" EXIT
sed "s/'/''/g" "${DICT_FILE}" > "${TEMP_WORDS}"
# 生成SQL语句 # 使用 LOAD DATA LOCAL INFILE 批量导入,极大提升导入性能
log_info "生成SQL语句..." log_info "使用 LOAD DATA LOCAL INFILE 批量导入敏感词..."
cat > "${TEMP_SQL}" <<EOF if mysql --local-infile=1 -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" <<EOF
-- 敏感词批量导入
USE ${DB_NAME};
-- 设置字符集
SET NAMES utf8mb4; SET NAMES utf8mb4;
-- 清除现有的 deny 类型敏感词 -- 清除现有的 deny 类型敏感词
DELETE FROM tb_sensitive_word WHERE type = 'deny'; DELETE FROM tb_sensitive_word WHERE type = 'deny';
-- 批量入敏感词 -- 从预处理文件批量入敏感词LOCAL 由客户端读取,避免 secure-file-priv 限制)
INSERT INTO tb_sensitive_word (word, type) VALUES LOAD DATA LOCAL INFILE '${TEMP_WORDS}'
INTO TABLE tb_sensitive_word
CHARACTER SET utf8mb4
LINES TERMINATED BY '\n'
(word)
SET type = 'deny';
EOF EOF
then
# 读取敏感词并生成INSERT语句
COUNTER=0
while IFS= read -r word || [ -n "$word" ]; do
# 跳过空行
[ -z "$word" ] && continue
# 转义单引号
word=$(echo "$word" | sed "s/'/''/g")
COUNTER=$((COUNTER + 1))
# 添加到SQL最后一个不加逗号
if [ $COUNTER -eq ${TOTAL_WORDS} ]; then
echo "('${word}', 'deny');" >> "${TEMP_SQL}"
else
echo "('${word}', 'deny')," >> "${TEMP_SQL}"
fi
# 进度提示每1000个
if [ $((COUNTER % 1000)) -eq 0 ]; then
log_info "已处理 ${COUNTER}/${TOTAL_WORDS} 个敏感词..."
fi
done < "${DICT_FILE}"
# 添加查询语句
cat >> "${TEMP_SQL}" <<EOF
-- 验证导入结果
SELECT COUNT(*) AS '导入数量' FROM tb_sensitive_word WHERE type = 'deny';
EOF
log_info "SQL语句生成完成${COUNTER}个敏感词)"
echo ""
# 执行SQL
log_info "执行数据库导入..."
if mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" < "${TEMP_SQL}"; then
END_TIME=$(date +%s) END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME)) DURATION=$((END_TIME - START_TIME))
echo "" echo ""
echo "==================================================" echo "=================================================="
log_info "导入完成!" log_info "导入完成!"
log_info "成功导入: ${COUNTER} 个敏感词" log_info "成功导入: ${TOTAL_WORDS} 个敏感词(基于文件统计)"
log_info "耗时: ${DURATION}" log_info "耗时: ${DURATION}"
echo "==================================================" echo "=================================================="
else else
@@ -172,10 +136,10 @@ IMPORTED_COUNT=$(mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS
echo "" echo ""
log_info "数据库中当前有 ${IMPORTED_COUNT} 个 deny 类型敏感词" log_info "数据库中当前有 ${IMPORTED_COUNT} 个 deny 类型敏感词"
if [ "${IMPORTED_COUNT}" -eq "${COUNTER}" ]; then if [ "${IMPORTED_COUNT}" -eq "${TOTAL_WORDS}" ]; then
log_info "✅ 验证通过:导入数量与预期一致" log_info "✅ 验证通过:导入数量与预期一致"
else else
log_warn "⚠️ 导入数量不匹配:预期 ${COUNTER},实际 ${IMPORTED_COUNT}" log_warn "⚠️ 导入数量不匹配:预期 ${TOTAL_WORDS},实际 ${IMPORTED_COUNT}"
fi fi
echo "" echo ""

View File

@@ -1,6 +1,7 @@
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios"; import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios";
import { ElLoading, ElMessage } from "element-plus"; import { ElLoading, ElMessage } from "element-plus";
import type { ResultDomain } from "@/types"; import type { ResultDomain } from "@/types";
import { API_BASE_URL } from "@/config";
/** /**
* 扩展AxiosRequestConfig以支持自定义配置 * 扩展AxiosRequestConfig以支持自定义配置
@@ -67,9 +68,13 @@ export const TokenManager = {
/** /**
* 创建axios实例 * 创建axios实例
*
* 说明:
* - 统一使用配置模块提供的 API_BASE_URL 作为基础路径
* - API_BASE_URL 在开发环境来自 devConfig在生产环境来自 window.APP_RUNTIME_CONFIG
*/ */
const request = axios.create({ const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || "/api", baseURL: API_BASE_URL,
timeout: 30000, timeout: 30000,
headers: { headers: {
'Content-Type': 'application/json;charset=UTF-8', 'Content-Type': 'application/json;charset=UTF-8',

View File

@@ -9,6 +9,9 @@ import type { SysMenu } from '@/types';
import { MenuType } from '@/types/enums'; import { MenuType } from '@/types/enums';
import { routes } from '@/router'; import { routes } from '@/router';
// 预注册所有视图组件,构建时由 Vite 解析并生成按需加载的 chunk
const VIEW_MODULES = import.meta.glob('../views/**/*.vue');
/** /**
* 布局组件映射 * 布局组件映射
*/ */
@@ -281,69 +284,44 @@ function findFirstMenuWithUrl(menus: SysMenu[]): SysMenu | null {
* @returns 组件异步加载函数 * @returns 组件异步加载函数
*/ */
function getComponent(componentName: string) { function getComponent(componentName: string) {
// 检查是否是布局组件 // 1. 若是布局组件,直接返回预定义映射
if (LAYOUT_MAP[componentName]) { if (LAYOUT_MAP[componentName]) {
return LAYOUT_MAP[componentName]; return LAYOUT_MAP[componentName];
} }
// 处理页面组件路径 // 2. 将后台给的 component 字段转换为 ../views/**.vue 形式的 key
let componentPath = componentName; let componentPath = componentName;
// 如果不是以@/开头的完整路径,则添加@/views/前缀 // 如果不是以 @/ 开头,则认为是相对 views 根目录的路径,例如 "user/home/HomeView"
if (!componentPath.startsWith('@/')) { if (!componentPath.startsWith('@/')) {
// 确保路径以/开头
if (!componentPath.startsWith('/')) { if (!componentPath.startsWith('/')) {
componentPath = '/' + componentPath; componentPath = '/' + componentPath;
} }
// 添加@/views前缀 componentPath = '@/views' + componentPath; // => '@/views/user/home/HomeView'
componentPath = '@/views' + componentPath;
} }
// 将@/别名转换为相对路径因为Vite动态导入可能无法正确解析别名 // 将别名 @/ 转为相对于当前文件的路径,必须与 import.meta.glob 中的模式一致
if (componentPath.startsWith('@/')) { componentPath = componentPath.replace(/^@\//, '../'); // => '../views/user/home/HomeView'
componentPath = componentPath.replace('@/', '../');
}
// 如果没有.vue扩展名添加它 // 补全 .vue 后缀
if (!componentPath.endsWith('.vue')) { if (!componentPath.endsWith('.vue')) {
componentPath += '.vue'; componentPath += '.vue';
} }
// 3. 从 VIEW_MODULES 中查找对应的 loader
const loader = VIEW_MODULES[componentPath];
// 动态导入组件 if (!loader) {
return () => { console.error('[路由生成] 未找到组件模块', {
return import(/* @vite-ignore */ componentPath)
.then(module => {
return module;
})
.catch(error => {
console.error('[路由生成] 组件加载失败:', {
原始组件名: componentName, 原始组件名: componentName,
最终路径: componentPath, 期望路径: componentPath,
错误: error 可用模块: Object.keys(VIEW_MODULES)
}); });
// 返回404组件 // 找不到时退回到 404 组件
return import('@/views/public/error/404.vue').catch(() => return () => import('@/views/public/error/404.vue');
Promise.resolve({
template: `<div class="component-error">
<h3>组件加载失败</h3>
<p>无法加载组件: ${componentPath}</p>
<p>原始组件名: ${componentName}</p>
<p>错误: ${error instanceof Error ? error.message : String(error)}</p>
</div>`,
style: `
.component-error {
padding: 20px;
text-align: center;
color: #f56565;
background: #fed7d7;
border-radius: 4px;
} }
`
}) return loader as () => Promise<any>;
);
});
};
} }
/** /**