6 Commits

68 changed files with 903 additions and 535 deletions

3
.env
View File

@@ -7,6 +7,8 @@ APP_ENV=development
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
PHP_FPM_PORT=9100
XDEBUG_POST=9103
# 数据库配置
MYSQL_ROOT_HOST=%
@@ -21,4 +23,5 @@ REDIS_PORT=6399
# Nginx 配置
NGINX_PORT=8010
NGINX_SSL_PORT=8012

View File

@@ -7,6 +7,8 @@ APP_ENV=development
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
PHP_FPM_PORT=9105
XDEBUG_POST=9108
# 数据库配置
MYSQL_ROOT_HOST=%
@@ -21,4 +23,5 @@ REDIS_PORT=6499
# Nginx 暴漏端口
NGINX_PORT=8050
NGINX_SSL_PORT=8052

View File

@@ -7,6 +7,8 @@ APP_ENV=development
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
PHP_FPM_PORT=9100
XDEBUG_POST=9103
# 数据库配置
MYSQL_ROOT_HOST=%
@@ -21,3 +23,4 @@ REDIS_PORT=6399
# Nginx 配置
NGINX_PORT=8010
NGINX_SSL_PORT=8012

View File

@@ -2,23 +2,26 @@
PROJECT_NAME=newshop
# ThinkPHP 6.x 配置, 请根据实际情况修改
APP_ENV=production
APP_ENV=local
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
PHP_FPM_PORT=9100
XDEBUG_POST=9103
# 数据库配置
MYSQL_ROOT_HOST=%
MYSQL_DATABASE=shop_mallnew
MYSQL_DATABASE=shop_dev
MYSQL_USER=shop_mallnew
MYSQL_PASSWORD=shop_mallnew
MYSQL_PORT=3926
MYSQL_PORT=3316
# Redis 绑定端口及密码
# Redis 配置
REDIS_PASSWORD=luckyshop123!@#
REDIS_PORT=6829
REDIS_PORT=6399
# Nginx 暴漏端口
NGINX_PORT=8858
# Nginx 配置
NGINX_PORT=8010
NGINX_SSL_PORT=8012

View File

@@ -1,24 +0,0 @@
# 项目配置, 请根据实际情况修改
PROJECT_NAME=newshop
# ThinkPHP 6.x 配置, 请根据实际情况修改
APP_ENV=staging
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
# 数据库配置
MYSQL_ROOT_HOST=%
MYSQL_DATABASE=shop_mallnew
MYSQL_USER=shop_mallnew
MYSQL_PASSWORD=shop_mallnew
MYSQL_PORT=3826
# Redis 绑定端口及密码
REDIS_PASSWORD=luckyshop123!@#
REDIS_PORT=6809
# Nginx 暴漏端口
NGINX_PORT=8854

View File

@@ -1,24 +0,0 @@
# 项目配置, 请根据实际情况修改
PROJECT_NAME=newshop
# ThinkPHP 6.x 配置, 请根据实际情况修改
APP_ENV=test
# PHP/PHP-FPM 配置
PHP_VERSION=7.4
PHP_FPM_VERSION=7.4-fpm
# 数据库配置
MYSQL_ROOT_HOST=%
MYSQL_DATABASE=shop_mallnew
MYSQL_USER=shop_mallnew
MYSQL_PASSWORD=shop_mallnew
MYSQL_PORT=3346
# Redis 绑定端口及密码
REDIS_PASSWORD=luckyshop123!@#
REDIS_PORT=6799
# Nginx 暴漏端口
NGINX_PORT=8360

120
docker-compose.local.yml Normal file
View File

@@ -0,0 +1,120 @@
# 特别说明本地local环境方便操作所以未使用的统一的docker-compose.yml文件只保留了local环境的配置
x-shared-env: &shared-api-env
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
MYSQL_ROOT_HOST: ${MYSQL_ROOT_HOST:-'%'} # 允许root从任何主机连接
MYSQL_DATABASE: ${MYSQL_DATABASE:-shop_mallnew}
MYSQL_USER: ${MYSQL_USER:-shop_mallnew}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-shop_mallnew}
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
REDIS_PORT: ${REDIS_PORT:-6379}
# 将服务归类到目录 A 中
services:
php-fpm:
build:
context: ./docker/php
dockerfile: Dockerfile
container_name: ${PROJECT_NAME}_php
restart: always
extra_hosts:
- "host.docker.internal:host-gateway" # 支持主机名解析
environment:
PHP_ENV: ${PHP_ENV:-development}
# 环境变量, APP_ENV 应用于 ThinkPHP 6.x 框架, .env.local 要想启用,需要在项目根目录下创建 .env.local 文件,并将 APP_ENV 设置为 local
# 同理,如果要启用开发环境,则将 APP_ENV 设置为 development如果要启用生产环境则将 APP_ENV 设置为 production
# 不然ThinkPHP 6.x 系列,会只加载 .env 文件,而不会加载 .env.local 文件,导致 .env.local 文件中的配置不会生效
APP_ENV: ${APP_ENV:-local}
APP_DEBUG: ${APP_DEBUG:-true}
ports:
- "${PHP_FPM_PORT:-9000}:9000" # PHP-FPM
volumes:
- ./:/var/www/all_source
- ./src:/var/www/html
# 更新下载源列表以加速apt-get
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
depends_on:
- db
healthcheck:
test: ["CMD", "bash", "-c", "curl -f http://localhost:9000/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
networks:
- sass-platform-net
labels:
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
nginx:
build:
context: ./docker/nginx
dockerfile: Dockerfile
container_name: ${PROJECT_NAME}_nginx
restart: always
ports:
- "${NGINX_PORT:-80}:80"
- "${NGINX_SSL_PORT:-443}:443"
volumes:
# 挂载项目代码到 Nginx 容器中
- ./src:/var/www/html:rw
# 更新下载源列表以加速apt-get
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
# 创建临时目录
- /var/www/server/nginx/proxy_temp_dir
- /var/www/server/nginx/proxy_cache_dir
depends_on:
- php-fpm
networks:
- sass-platform-net
labels:
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
db:
image: mysql:5.7.44
container_name: ${PROJECT_NAME}_mysql
environment:
<<: *shared-api-env
volumes:
- mysql_db_data:/var/lib/mysql
- ./docker/mysql/init:/docker-entrypoint-initdb.d
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf
ports:
- ${MYSQL_PORT:-3306}:3306
networks:
- sass-platform-net
restart: unless-stopped
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb_buffer_pool_size=256M
labels:
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
# Redis 服务(可选)
redis:
image: redis:8.2
container_name: ${PROJECT_NAME}_redis
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:-luckyshop123!@#}
REDISCLI_AUTH: ${REDIS_PASSWORD:-luckyshop123!@#}
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
- ./docker/redis/redis.conf:/etc/redis/redis.conf
networks:
- sass-platform-net
restart: unless-stopped
labels:
- "com.docker.compose.project.working_dir=${PROJECT_NAME}"
volumes:
mysql_db_data:
redis_data:
networks:
sass-platform-net:
driver: bridge

View File

@@ -25,13 +25,11 @@ services:
# 不然ThinkPHP 6.x 系列,会只加载 .env 文件,而不会加载 .env.local 文件,导致 .env.local 文件中的配置不会生效
APP_ENV: ${APP_ENV:-development}
APP_DEBUG: ${APP_DEBUG:-true}
# PHP应用根目录可选默认 /var/www/html
PHP_APP_ROOT: ${PHP_APP_ROOT:-/var/www/html}
# 用户ID映射可选用于解决挂载权限问题
USER_ID: ${USER_ID:-33}
GROUP_ID: ${GROUP_ID:-33}
ports:
- "${PHP_FPM_PORT:-9000}:9000" # PHP-FPM
volumes:
- ./src:/var/www/html:rw
- ./:/var/www/all_source
- ./src:/var/www/html
# 更新下载源列表以加速apt-get
- ./docker/debian/sources.list:/etc/apt/sources.list:ro
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
@@ -56,6 +54,7 @@ services:
restart: always
ports:
- "${NGINX_PORT:-80}:80"
- "${NGINX_SSL_PORT:-443}:443"
volumes:
# 挂载项目代码到 Nginx 容器中
- ./src:/var/www/html:rw
@@ -130,4 +129,5 @@ volumes:
networks:
sass-platform-net:
name: ${PROJECT_NAME}_${APP_ENV}_net
driver: bridge
driver: bridge
external: true

View File

@@ -4231,7 +4231,7 @@ CREATE TABLE `lucky_member_label` (
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
INDEX IDX_nc_member_label_label_id (`label_id`),
PRIMARY KEY (`label_id`) USING BTREE,
KEY `IDX_nc_member_label_label_id_key` (`label_id`) USING BTREE
KEY `IDX_nc_member_label_label_id` (`label_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='会员标签';
/*!40101 SET character_set_client = @saved_cs_client */;

View File

@@ -1,9 +1,10 @@
# 忽略所有文件
# 忽略目录下所有文件和子目录
*
# 只保留指定的 .gitkeep 文件
# 忽略所有子目录
*/
# 但不忽略 .gitkeep 文件
!.gitkeep
# 不忽略 .gitignore 文件自身
!.gitignore
!development/.gitkeep
!test/.gitkeep
!production/.gitkeep
!staging/.gitkeep
# 不忽略 development/.gitkeep 文件
!development/.gitkeep

View File

@@ -1,21 +1,29 @@
FROM nginx:alpine
# 删除默认配置
RUN rm /etc/nginx/conf.d/default.conf
# 将本地 nginx 配置复制到镜像中
COPY ./conf.c/ /etc/nginx/conf.c/
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./sites-enabled/ /etc/nginx/sites-enabled/
# 暴露端口
EXPOSE 80 443
# 添加在Dockerfile末尾CMD命令之前
COPY ./entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# 启动nginx
FROM nginx:alpine
# 删除默认配置
RUN rm /etc/nginx/conf.d/default.conf
#
# - ./.docker/nginx/conf.c:/etc/nginx/conf.c:ro
# - ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
# - ./.docker/nginx/sites-enabled:/etc/nginx/sites-enabled:ro
# 将本地 nginx 配置复制到镜像中并设置为只读
COPY ./conf.c /etc/nginx/conf.c
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./sites-enabled /etc/nginx/sites-enabled
# 设置只读权限(文件 0444目录及其内容 0555
RUN chmod 0444 /etc/nginx/conf.c \
&& chmod 0444 /etc/nginx/conf.d/default.conf \
&& chmod -R 0555 /etc/nginx/sites-enabled
# 设置工作目录
WORKDIR /var/www/html
# 创建日志目录
RUN mkdir -p /var/log/nginx
# 暴露端口
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,14 +0,0 @@
location /ws {
proxy_pass http://php-fpm:8080; # 注意:这里用的是 Docker 服务名或容器名
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
# 可选设置超时WebSocket 是长连接)
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}

View File

@@ -1,18 +0,0 @@
#!/bin/bash
set -e
echo "=== NGINX Docker 权限初始化 ==="
# 设置权限
chmod -R 0444 /etc/nginx/conf.c
chmod 0444 /etc/nginx/conf.d/default.conf
chmod -R 0755 /etc/nginx/sites-enabled
# 创建日志目录
mkdir -p /var/log/nginx
echo "=== NGINX Docker 权限初始化完成 ==="
# 执行原有的启动命令
exec "$@"

View File

@@ -20,9 +20,6 @@
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# --- SSL configuration end ---
# 启用 WebSocket 支持
include conf.c/enable-websocket.conf;
#PHP-INFO-START PHP引用配置可以注释或修改
include conf.c/enable-php-74.conf;
#PHP-INFO-END

View File

@@ -27,14 +27,9 @@ RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
iputils-ping \
&& apt-get clean \
iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# 安装 WebSocat 完成后,清理缓存
COPY ./websocat /usr/local/bin/websocat
RUN chmod +x /usr/local/bin/websocat
# 安装 PHP 扩展
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install \
@@ -59,15 +54,15 @@ RUN composer --version
# 修改 PHP 配置
RUN echo "memory_limit=256M" > /usr/local/etc/php/conf.d/memory-limit.ini \
&& echo "upload_max_filesize=150M" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "post_max_size=150M" >> /usr/local/etc/php/conf.d/uploads.ini
&& echo "upload_max_filesize=50M" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "post_max_size=50M" >> /usr/local/etc/php/conf.d/uploads.ini
# # 使用Composer安装项目依赖可选根据需要启用, 更多的时候,会出错,要在容器中执行操作)
# RUN composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
# RUN composer install --no-dev --optimize-autoloader --working-dir=/var/www/html
# 暴露端口9000 为 PHP-FPM 端口8080 为 WebSocket 端口
EXPOSE 9000 8080
# 暴露端口
EXPOSE 9000
############ 查看 cron 进程
## 查看 cron 进程

View File

@@ -1,343 +1,112 @@
#!/bin/bash
# 移除 set -e 以便更好的错误控制
set -e
echo "=== Web应用权限初始化 ==="
echo "=== ThinkPHP Docker权限初始化 ==="
# 定义应用根目录,优先使用环境变量,否则使用默认值
APP_ROOT="${PHP_APP_ROOT:-/var/www/html}"
# 定义应用根目录
APP_ROOT="/var/www/html"
echo "使用应用根目录: $APP_ROOT"
# 如果应用根目录不存在,则跳过权限设置
if [ ! -d "$APP_ROOT" ]; then
echo "❌ 应用根目录:'$APP_ROOT'不存在,跳过权限设置"
exit 1
# 获取正确的用户和组
if [ -n "$USER_ID" ] && [ -n "$GROUP_ID" ]; then
# 如果指定了用户ID修改www-data
usermod -u $USER_ID www-data
groupmod -g $GROUP_ID www-data
fi
# 创建统一的Web组并配置所有用户最高效的权限管理
configure_web_users() {
# 常见Web服务器用户列表
WEB_USERS=("www-data" "www" "apache" "nginx")
# 获取环境变量中的用户ID
TARGET_UID=${USER_ID:-33}
TARGET_GID=${GROUP_ID:-33}
echo "配置统一Web组权限目标UID:GID = $TARGET_UID:$TARGET_GID"
# 创建统一的Web组增强错误处理
WEB_GROUP="webaccess"
if ! getent group "$WEB_GROUP" &>/dev/null; then
echo "创建统一Web组: $WEB_GROUP"
# 尝试使用指定GID创建组
if groupadd -g $TARGET_GID "$WEB_GROUP" 2>/dev/null; then
echo "✅ 统一Web组创建成功GID: $TARGET_GID"
else
echo "⚠️ GID $TARGET_GID 已被占用尝试自动分配GID"
# 尝试不指定GID创建组
if groupadd "$WEB_GROUP" 2>/dev/null; then
ACTUAL_GID=$(getent group "$WEB_GROUP" | cut -d: -f3)
echo "✅ 统一Web组创建成功自动分配GID: $ACTUAL_GID"
else
echo "❌ 创建 $WEB_GROUP 组失败,尝试使用备用方案"
# 备用方案使用现有的www-data组
if getent group "www-data" &>/dev/null; then
WEB_GROUP="www-data"
echo "🔄 使用现有的www-data组作为统一组"
else
echo "❌ 备用方案也失败,权限配置可能不完整"
WEB_GROUP=""
fi
fi
fi
else
ACTUAL_GID=$(getent group "$WEB_GROUP" | cut -d: -f3)
echo "✅ 统一Web组 $WEB_GROUP 已存在GID: $ACTUAL_GID"
fi
# 最终验证组是否存在
if [ -z "$WEB_GROUP" ] || ! getent group "$WEB_GROUP" &>/dev/null; then
echo "❌ 无法创建或找到可用的Web组权限配置将受限"
return 1
fi
# 只将已存在的Web用户加入统一组增强错误处理
success_count=0
total_users=0
for web_user in "${WEB_USERS[@]}"; do
total_users=$((total_users + 1))
if id "$web_user" &>/dev/null; then
echo "📝 处理Web用户: $web_user"
# 获取用户当前组信息(安全的变量处理)
current_groups=$(id -Gn "$web_user" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')
echo " 当前所属组: ${current_groups:-}"
# 尝试将用户加入统一组(使用-a参数保留现有组只添加新组
if usermod -a -G "$WEB_GROUP" "$web_user" 2>/dev/null; then
echo " ✅ 成功将 $web_user 添加到统一组 $WEB_GROUP"
success_count=$((success_count + 1))
else
echo " ⚠️ 无法将 $web_user 添加到统一组,尝试设置主组"
# 备用方案:设置主组
if usermod -g "$WEB_GROUP" "$web_user" 2>/dev/null; then
echo " ✅ 成功将 $web_user 主组设置为 $WEB_GROUP"
success_count=$((success_count + 1))
else
echo " ❌ 无法配置 $web_user 的组权限"
fi
fi
else
echo "⚭ Web用户 $web_user 不存在,跳过"
fi
done
echo "📊 用户配置汇总: $success_count/$total_users 个Web用户配置成功"
# 至少要有一个用户配置成功
if [ $success_count -eq 0 ]; then
echo "⚠️ 没有Web用户被成功配置但继续执行"
fi
echo "统一Web组配置完成"
}
# 错误处理:如果配置失败,不要终止整个脚本
configure_web_users || echo "⚠️ Web用户配置出现问题但继续执行权限设置"
echo "当前用户: $(whoami)"
echo "UID: $(id -u), GID: $(id -g)"
# 修复所有目录权限使用统一Web组最高效的权限管理
if [ -d "$APP_ROOT" ]; then
# 重新获取最终的WEB_GROUP可能已被修改
FINAL_WEB_GROUP=""
# 修复目录所有权和权限
fix_directory_permissions() {
local dir=$1
echo "修复PHP目录权限: $dir"
# 首选使用创建的webaccess组
if getent group "webaccess" &>/dev/null; then
FINAL_WEB_GROUP="webaccess"
echo "🎯 使用创建的统一Web组: $FINAL_WEB_GROUP"
# 备选使用www-data组
elif getent group "www-data" &>/dev/null; then
FINAL_WEB_GROUP="www-data"
echo "🔄 回退到www-data组: $FINAL_WEB_GROUP"
# 最后:使用当前用户的组
else
CURRENT_USER=$(whoami)
CURRENT_GROUP=$(id -gn "$CURRENT_USER")
FINAL_WEB_GROUP="$CURRENT_GROUP"
echo "🔧 使用当前用户组: $FINAL_WEB_GROUP"
fi
# 确保目录存在
mkdir -p "$dir"
# 最终验证
if [ -z "$FINAL_WEB_GROUP" ]; then
echo "❌ 无法确定有效的Web组跳过权限设置"
echo "=== 启动应用 ==="
exec "$@"
fi
# 设置所有权
chown -R www-data:www-data "$dir"
WEB_GROUP="$FINAL_WEB_GROUP"
WEB_GROUP_GID=$(getent group "$WEB_GROUP" | cut -d: -f3)
echo "✅ 最终使用Web组: $WEB_GROUP (GID: $WEB_GROUP_GID)"
echo "🔒 统一组权限模式所有Web用户通过组继承权限"
# 设置权限
chmod -R 775 "$dir"
# 设置所有权为统一Web组增强错误处理
echo "📁 设置应用目录所有权为统一Web组"
CURRENT_USER=$(whoami)
# 设置setgid权限
chmod g+s "$dir"
if chown -R $CURRENT_USER:$WEB_GROUP "$APP_ROOT" 2>/dev/null; then
echo "✅ 所有权设置成功: $CURRENT_USER:$WEB_GROUP"
else
echo "⚠️ 所有权设置失败,尝试只设置组权限"
chgrp -R "$WEB_GROUP" "$APP_ROOT" 2>/dev/null || echo "❌ 组权限设置也失败"
fi
# 设置目录权限为775组权限为rwx所有组内用户都有完整权限
echo "🔐 设置目录权限775文件权限664"
# 使用更安全的权限设置方式,避免权限被拒绝
dir_count=0
file_count=0
# 设置目录权限(兼容性更好的方式)
if command -v find >/dev/null 2>&1; then
dir_count=$(find "$APP_ROOT" -type d -exec chmod 775 {} \; 2>/dev/null | wc -l)
file_count=$(find "$APP_ROOT" -type f -exec chmod 664 {} \; 2>/dev/null | wc -l)
find "$APP_ROOT" -type d -exec chmod g+s {} \; 2>/dev/null
else
# 备用方案:使用简单的循环
echo "find命令不可用跳过批量权限设置"
dir_count=0
file_count=0
fi
echo "📊 权限设置完成: $dir_count个目录, $file_count个文件"
echo "✅ 统一组权限设置完成所有Web用户通过组获得权限"
# 设置ACL如果支持只需设置统一组
# 尝试设置ACL如果支持
if command -v setfacl >/dev/null 2>&1; then
echo "🔒 设置ACL权限只需设置统一Web组"
acl_success=0
# 只为统一Web组设置ACL权限限制处理深度
if setfacl -R -m g:$WEB_GROUP:rwx "$APP_ROOT" 2>/dev/null; then
echo " ✅ 设置组ACL权限成功"
acl_success=$((acl_success + 1))
else
echo " ❌ 设置组ACL权限失败"
fi
# 设置默认ACL权限新创建的文件自动继承权限
if setfacl -dR -m g:$WEB_GROUP:rwx "$APP_ROOT" 2>/dev/null; then
echo " ✅ 设置默认ACL权限成功"
acl_success=$((acl_success + 1))
else
echo " ❌ 设置默认ACL权限失败"
fi
if [ $acl_success -eq 2 ]; then
echo "🎉 统一组ACL设置完成所有组内用户自动获得权限"
elif [ $acl_success -eq 1 ]; then
echo "⚠️ ACL部分设置成功建议检查文件系统ACL支持"
else
echo "❌ ACL设置完全失败文件系统可能不支持ACL"
fi
else
echo " ACL不支持依赖传统权限模式"
echo "✅ 775权限已足够所有组内用户都有rwx权限"
setfacl -dR -m u:www-data:rwx "$dir"
fi
find "$dir" -type d -exec chmod 775 {} \;
find "$dir" -type f -exec chmod 775 {} \;
find "$dir" -type d -exec chmod g+s {} \;
find "$dir" -type f -exec chmod g+s {} \;
# 设置umask
umask 0002
echo "$dir 权限设置完成, 目录权限: $(stat -c '%a %n' "$dir"), setgid权限: $(stat -c '%a %n' "$dir" | grep 's')"
}
echo "✅ 应用目录权限修复完成"
# 验证文件权限是否足够(测试统一组权限效果)
echo "=== 验证统一组权限效果 ==="
# 查找测试文件的更可靠方法
test_file=""
# 方法1: 查找index.html
if [ -f "$APP_ROOT/index.html" ]; then
test_file="$APP_ROOT/index.html"
fi
# 方法2: 查找任意HTML文件更安全的方式
if [ -z "$test_file" ]; then
first_html=$(find "$APP_ROOT" -maxdepth 2 -name "*.html" -type f 2>/dev/null | head -1)
if [ -n "$first_html" ] && [ -f "$first_html" ]; then
test_file="$first_html"
fi
fi
# 方法3: 查找index.php
if [ -z "$test_file" ] && [ -f "$APP_ROOT/index.php" ]; then
test_file="$APP_ROOT/index.php"
fi
# 方法4: 创建专用测试文件(最安全的选择)
if [ -z "$test_file" ]; then
test_file="$APP_ROOT/.permission_test.html"
echo "创建专用权限测试文件"
cat > "$test_file" << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Permission Test</title></head>
<body><h1>Web Server Permission Test File</h1></body>
</html>
EOF
# 设置正确的权限
chown $(whoami):"$WEB_GROUP" "$test_file" 2>/dev/null || true
chmod 664 "$test_file"
fi
# 执行权限测试
if [ -f "$test_file" ]; then
echo "使用测试文件: $test_file"
echo "文件权限: $(stat -c '%a %n' "$test_file")"
echo "文件所有者: $(stat -c '%U:%G' "$test_file")"
# 测试所有Web用户的权限通过组权限
for test_user in "www-data" "www" "apache" "nginx"; do
if id "$test_user" &>/dev/null; then
echo "🔍 测试 $test_user 用户权限(通过组权限):"
# 显示用户组信息(安全的变量处理)
user_groups=$(id -Gn "$test_user" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')
echo " 📋 所属组: ${user_groups:-}"
# 测试读权限(安全:只读不修改)
if su -s /bin/sh -c "cat '$test_file' >/dev/null 2>&1" "$test_user" 2>/dev/null; then
echo " ✅ 读权限: 通过组权限可读"
else
echo " ❌ 读权限: 不可读"
fi
# 测试写权限(使用临时文件,避免污染原文件)
temp_test_file="${test_file}.write_test_${test_user}"
if su -s /bin/sh -c "echo 'permission_test' > '$temp_test_file' 2>/dev/null" "$test_user" 2>/dev/null; then
echo " ✅ 写权限: 通过组权限可写"
rm -f "$temp_test_file" 2>/dev/null
else
echo " ❌ 写权限: 不可写"
fi
# 测试目录创建权限(使用临时目录)
temp_test_dir="${APP_ROOT}/.perm_test_${test_user}"
if su -s /bin/sh -c "mkdir -p '$temp_test_dir' 2>/dev/null" "$test_user" 2>/dev/null; then
echo " ✅ 创建目录: 通过组权限可创建"
rm -rf "$temp_test_dir" 2>/dev/null
else
echo " ❌ 创建目录: 不可创建"
fi
echo " 🔗 权限来源: 统一Web组 ($WEB_GROUP) 775权限"
break # 只测试第一个可用的用户即可验证效果
fi
done
# 清理专用测试文件(如果是创建的)
if echo "$test_file" | grep -q "\.permission_test\.html$"; then
rm -f "$test_file" 2>/dev/null
echo "🧹 已清理临时测试文件"
fi
# 处理所有需要权限的目录
directories=("runtime" "upload" "runtime/log" "runtime/cache" "runtime/temp")
for dir in "${directories[@]}"; do
fix_directory_permissions "$APP_ROOT/$dir"
done
# 验证权限
echo "=== 权限验证 ==="
echo "当前用户: $(whoami)"
echo "当前UID: $(id -u), GID: $(id -g)"
echo "当前umask: $(umask)"
# 验证www-data用户是否可以在runtime和upload目录下新建子目录
# 方法1使用sudo
if command -v sudo >/dev/null 2>&1; then
echo "使用sudo测试..."
if sudo -u www-data mkdir -p $APP_ROOT/runtime/log/test_dir 2>/dev/null; then
echo "✅ sudo: runtime目录创建子目录成功 [使用www-data用户]"
rm -rf $APP_ROOT/runtime/log/test_dir
else
echo "❌ 无法找到或创建测试文件,跳过权限验证"
fi
# 显示统一组和用户状态
echo "=== 统一Web组状态检查 ==="
if getent group "$WEB_GROUP" &>/dev/null; then
echo "✅ 统一Web组 '$WEB_GROUP' 存在"
echo "组信息: $(getent group "$WEB_GROUP" 2>/dev/null || echo '获取失败')"
# 检查哪些用户在统一组中
echo "统一组成员检查:"
for web_user in "www-data" "www" "apache" "nginx"; do
if id "$web_user" &>/dev/null; then
if id -Gn "$web_user" | grep -q "$WEB_GROUP"; then
echo "$web_user 在统一组 '$WEB_GROUP' 中"
else
echo "$web_user 不在统一组 '$WEB_GROUP' 中"
fi
else
echo "$web_user 用户不存在"
fi
done
else
echo "❌ 统一Web组 '$WEB_GROUP' 不存在"
echo "❌ sudo: runtime目录创建子目录失败 [使用www-data用户]"
fi
fi
# 方法2使用su
echo "使用su测试..."
if su -s /bin/sh -c "mkdir -p $APP_ROOT/runtime/log/test_dir" www-data 2>/dev/null; then
echo "✅ su: runtime目录创建子目录成功 [使用www-data用户]"
rm -rf $APP_ROOT/runtime/log/test_dir
else
echo "❌ su: runtime目录创建子目录失败 [使用www-data用户]"
fi
# 方法3使用runuser
if command -v runuser >/dev/null 2>&1; then
echo "使用runuser测试..."
if runuser -u www-data -- mkdir -p $APP_ROOT/runtime/log/test_dir 2>/dev/null; then
echo "✅ runuser: runtime目录创建子目录成功 [使用www-data用户]"
rm -rf $APP_ROOT/runtime/log/test_dir
else
echo "❌ runuser: runtime目录创建子目录失败 [使用www-data用户]"
fi
fi
# 检查www-data用户和组
echo "检查www-data用户..."
id www-data
groups www-data
# 检查目录的实际权限
echo "检查目录权限..."
ls -ld $APP_ROOT/runtime/log
ls -ld $APP_ROOT/runtime/cache
ls -ld $APP_ROOT/runtime/temp
ls -ld $APP_ROOT/upload
echo "=== 启动应用 ==="
# 执行原有的启动命令

View File

@@ -6,6 +6,13 @@ logfile_backups=10
loglevel=info
pidfile=/var/run/supervisord.pid
[program:chmod]
command=/bin/bash -c "while true; do chmod -R 775 /var/www/html/runtime/ /var/www/html/upload/ 2>/dev/null || true; sleep 30; done"
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
[program:php-fpm]
command=php-fpm
autostart=true
@@ -35,19 +42,4 @@ stderr_logfile=/var/log/supervisor/think-cron-error.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=10
startsecs=3
stopwaitsecs=10
[program:websocket-server]
command=/bin/bash -c "if [ -f /var/www/html/ws_server.php ]; then php ./ws_server.php; else echo 'ws_server.php not found, skipping websocket server'; fi"
workdir=/var/www/html
autostart=true
autorestart=false
startretries=0
stdout_logfile=/var/log/supervisor/websocket-server.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=10
stderr_logfile=/var/log/supervisor/websocket-server-error.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=10
startsecs=3
stopwaitsecs=10
stopwaitsecs=10

Binary file not shown.

View File

@@ -1,9 +1,10 @@
# 忽略所有文件
# 忽略目录下所有文件和子目录
*
# 只保留指定的 .gitkeep 文件
# 忽略所有子目录
*/
# 但不忽略 .gitkeep 文件
!.gitkeep
# 不忽略 .gitignore 文件自身
!.gitignore
!development/.gitkeep
!test/.gitkeep
!production/.gitkeep
!staging/.gitkeep
# 不忽略 development/.gitkeep 文件
!development/.gitkeep

View File

@@ -10,7 +10,7 @@ default_lang = zh-cn
[DATABASE]
TYPE = mysql
HOSTNAME = db
DATABASE = shop_mallnew
DATABASE = shop_dev
USERNAME = shop_mallnew
PASSWORD = shop_mallnew
HOSTPORT = 3306

View File

@@ -1,24 +0,0 @@
APP_DEBUG = true
APP_TRACE = true
[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
[LANG]
default_lang = zh-cn
[DATABASE]
TYPE = mysql
HOSTNAME = db
DATABASE = shop_mallnew
USERNAME = shop_mallnew
PASSWORD = shop_mallnew
HOSTPORT = 3306
CHARSET = utf8mb4
DEBUG = true
[REDIS]
HOST = redis
PORT = 6379
PASSWORD = 'luckyshop123!@#'
EXPIRY = 604800

View File

@@ -120,6 +120,9 @@ class Config extends BaseApi
$config_model = new ConfigModel();
$default_img = $config_model->getDefaultImg($this->site_id, 'shop')[ 'data' ][ 'value' ];
// 企业微信配置
$wxwork_config = $config_model->getWxworkConfig($this->site_id, 'shop')[ 'data' ][ 'value' ];
// 版权信息
$copyright = $config_model->getCopyright($this->site_id, 'shop')[ 'data' ][ 'value' ];
$map_config = $config_model->getMapConfig($this->site_id, 'shop')[ 'data' ][ 'value' ];
@@ -146,7 +149,8 @@ class Config extends BaseApi
'servicer' => $servicer_info,
'shop_info'=>$shop_info,
'store_config' => $this->store_data[ 'config' ],
'map_config' => $map_config
'map_config' => $map_config,
'wxwork_config' => $wxwork_config
];
if (!empty($this->store_data[ 'store_info' ])) {
$res[ 'store_info' ] = $this->store_data[ 'store_info' ];

View File

@@ -847,6 +847,63 @@ class Config extends BaseModel
// return $data;
// }
/**
* 设置企业微信配置
* @param $data
* @param int $site_id
* @param string $app_module
* @return array
*/
public function setWxworkConfig($data, $site_id = 1, $app_module = 'shop')
{
// 只要提供了Secret就自动生成时间戳、随机字符串和签名
if (!empty($data['secret'])) {
$data['timestamp'] = time();
// 生成16位随机字符串
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$nonceStr = '';
for ($i = 0; $i < 16; $i++) {
$nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
$data['nonceStr'] = $nonceStr;
// 生成SHA1签名
$string = 'secret=' . $data['secret'] . '&timestamp=' . $data['timestamp'] . '&nonceStr=' . $data['nonceStr'];
$data['signature'] = sha1($string);
}
$config = new ConfigModel();
$res = $config->setConfig($data, '企业微信配置', 1, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'WXWORK_CONFIG' ] ]);
return $res;
}
/**
* 获取企业微信配置
* @param int $site_id
* @param string $app_module
* @return array
*/
public function getWxworkConfig($site_id = 1, $app_module = 'shop')
{
$config = new ConfigModel();
$res = $config->getConfig([ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'WXWORK_CONFIG' ] ]);
if (empty($res[ 'data' ][ 'value' ])) {
$res[ 'data' ][ 'value' ] = [
'corp_id' => '',
'agent_id' => '',
'secret' => '',
'contact_id' => '',
'contact_url' => '',
'timestamp' => '',
'nonceStr' => '',
'signature' => '',
'enabled' => false
];
}
return $res;
}
/**
* 设置商品编码设置
* @param $data

View File

@@ -210,6 +210,30 @@ class Config extends BaseShop
}
/**
* 企业微信配置
*/
public function wxwork()
{
$config_model = new ConfigModel();
if (request()->isJson()) {
$data = [
'corp_id' => input('corp_id', ''),
'agent_id' => input('agent_id', ''),
'secret' => input('secret', ''),
'contact_id' => input('contact_id', ''),
'contact_url' => input('contact_url', ''),
'enabled' => input('enabled', 0)
];
$this->addLog('修改企业微信配置');
return $config_model->setWxworkConfig($data, $this->site_id, $this->app_module);
} else {
$config_result = $config_model->getWxworkConfig($this->site_id, $this->app_module);
$this->assign('wxwork_config', $config_result[ 'data' ][ 'value' ]);
return $this->fetch('config/wxwork');
}
}
/**
* 域名跳转配置
*/

View File

@@ -0,0 +1,514 @@
<style type="text/css">
.inline-block{display: inline-block;}
.wxwork-form .layui-form-item{margin-bottom: 20px;}
.wxwork-form .layui-input-block{margin-left: 120px;}
.wxwork-help{color: #999; font-size: 12px; margin-top: 5px;}
.wxwork-required{color: #ff5722; margin-right: 4px;}
.wxwork-guide{background: #f8f9fa; border-left: 3px solid #007bff; padding: 15px; margin-top: 10px; border-radius: 4px;}
.wxwork-guide h4{margin: 0 0 10px 0; color: #007bff;}
.wxwork-guide ol{margin: 0; padding-left: 20px;}
.wxwork-guide li{margin-bottom: 8px;}
.wxwork-code{background: #f1f3f4; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 12px;}
.wxwork-field-required .layui-form-label{color: #333;}
.wxwork-link{color: #007bff; text-decoration: none;}
.wxwork-link:hover{text-decoration: underline;}
.wxwork-guide-toggle{margin-top: 30px;}
.wxwork-guide-toggle .guide-toggle-btn{width: 100%; padding: 12px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; color: #333;}
.wxwork-guide-toggle .guide-toggle-btn:hover{background: #e9ecef; border-color: #007bff;}
.wxwork-guide-toggle .guide-toggle-btn.active{background: #007bff; color: white; border-color: #007bff;}
.wxwork-guide-toggle .guide-content{margin-top: 15px; background: #f8f9fa; border-left: 3px solid #007bff; padding: 15px; border-radius: 4px; display: none;}
.wxwork-guide-toggle .guide-content.show{display: block;}
.guide-toggle-icon{transition: transform 0.3s;}
.guide-toggle-icon.rotate{transform: rotate(180deg);}
.password-wrapper{position: relative;}
.password-toggle{position: absolute; right: 10px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #999; font-size: 16px; user-select: none;}
.password-toggle:hover{color: #007bff;}
</style>
<div class="layui-form form-wrap wxwork-form">
<div class="layui-form-item">
<label class="layui-form-label">启用状态:</label>
<div class="layui-input-block">
<input type="checkbox" name="enabled" value="1" lay-skin="switch" {if condition="$wxwork_config.enabled == 1" }checked {/if} lay-title="启用|关闭"/>
</div>
<div class="wxwork-help">开启后,企业微信相关功能将可用</div>
</div>
<div class="layui-form-item wxwork-field-required">
<label class="layui-form-label"><span class="wxwork-required">*</span>企业ID</label>
<div class="layui-input-block">
<input type="text" name="corp_id" required autocomplete="off" value="{$wxwork_config.corp_id ?? ''}" class="layui-input len-long" placeholder="wwxxxxxxxxxxxx">
</div>
<div class="wxwork-help">
<p><strong>获取方式</strong>:企业微信管理后台 → 我的企业 → 企业信息 → 企业ID</p>
<p><strong>格式</strong>:通常以 "ww" 开头的18位字符串</p>
</div>
</div>
<div class="layui-form-item wxwork-field-required">
<label class="layui-form-label"><span class="wxwork-required">*</span>应用ID</label>
<div class="layui-input-block">
<input type="text" name="agent_id" required autocomplete="off" value="{$wxwork_config.agent_id ?? ''}" class="layui-input len-long" placeholder="1000001">
</div>
<div class="wxwork-help">
<p><strong>获取方式</strong>:应用管理 → 选择应用 → 查看应用详情 → AgentId</p>
<p><strong>格式</strong>:数字字符串,如 1000001</p>
</div>
</div>
<div class="layui-form-item wxwork-field-required">
<label class="layui-form-label"><span class="wxwork-required">*</span>应用Secret</label>
<div class="layui-input-block">
<div class="password-wrapper">
<input type="password" name="secret" id="secret-input" required autocomplete="off" value="{$wxwork_config.secret ?? ''}" class="layui-input len-long" placeholder="请输入应用Secret">
<span class="password-toggle" onclick="togglePassword()">👁️</span>
</div>
</div>
<div class="wxwork-help" style="color: #ff5722;">
<p><strong>获取方式</strong>:企业微信管理后台 → 应用管理 → 选择应用 → 查看Secret</p>
<p><strong>说明</strong><strong style="color: #ff5722;">系统必需</strong>:用于自动生成签名,请妥善保管</p>
<p><strong>安全提示</strong>Secret仅在创建时可见请立即复制保存</p>
</div>
</div>
<div class="layui-form-item wxwork-field-required">
<label class="layui-form-label"><span class="wxwork-required">*</span>客服ID</label>
<div class="layui-input-block">
<input type="text" name="contact_id" required autocomplete="off" value="{$wxwork_config.contact_id ?? ''}" class="layui-input len-long" placeholder="kfc_xxxxx">
</div>
<div class="wxwork-help" style="color: #ff5722;">
<p><strong>获取方式</strong>:企业微信管理后台 → 应用管理 → 客服 → 联系我</p>
<p><strong>说明</strong><strong style="color: #ff5722;">系统必需功能</strong>,用于企业微信客服对接</p>
<p><strong>格式</strong>:通常以 "kfc_" 开头的字符串</p>
</div>
</div>
<div class="layui-form-item wxwork-field-required">
<label class="layui-form-label"><span class="wxwork-required">*</span>活码链接:</label>
<div class="layui-input-block">
<input type="text" name="contact_url" required autocomplete="off" value="{$wxwork_config.contact_url ?? ''}" class="layui-input len-long" placeholder="https://work.weixin.qq.com/kf/...">
</div>
<div class="wxwork-help" style="color: #ff5722;">
<p><strong>获取方式</strong>:企业微信管理后台 → 客服 → 联系我 → 生成活码</p>
<p><strong>说明</strong><strong style="color: #ff5722;">系统必需功能</strong>,客服活码的完整链接地址</p>
<p><strong>格式</strong>完整的HTTPS链接如 https://work.weixin.qq.com/kf/xxxx</p>
</div>
</div>
<!-- 隐藏的自动生成字段 -->
<input type="hidden" name="timestamp" value="{$wxwork_config.timestamp ?? ''}">
<input type="hidden" name="nonceStr" value="{$wxwork_config.nonceStr ?? ''}">
<input type="hidden" name="signature" value="{$wxwork_config.signature ?? ''}">
<!-- 系统状态显示区域 -->
<div class="layui-form-item">
<div class="layui-input-block">
<div class="wxwork-status-panel" style="border: 1px solid #e6e6e6; border-radius: 4px; padding: 15px; background: #fafafa; display: flex; flex-direction: column;">
<div style="margin-bottom: 10px;">
<span><strong>配置完整性:</strong></span>
<span id="config-completeness" style="color: #999;">检查中...</span>
</div>
<div style="margin-bottom: 10px;">
<span><strong>签名生成状态:</strong></span>
<span id="signature-status" style="color: #999;">点击保存配置,将自动生成</span>
</div>
<div style="margin-bottom: 10px;">
<span><strong>时间戳:</strong></span>
<span id="timestamp-value" style="color: #666;">{if condition="$wxwork_config.timestamp"}{$wxwork_config.timestamp}{else /}未生成{/if}</span>
</div>
<div style="margin-bottom: 10px;">
<span><strong>随机字符串:</strong></span>
<span id="nonce-str-value" style="color: #666;">{if condition="$wxwork_config.nonceStr"}{$wxwork_config.nonceStr}{else /}未生成{/if}</span>
</div>
<div style="margin-bottom: 10px;">
<span><strong>签名:</strong></span>
<span id="signature-value" style="color: #52c41a; font-family: monospace; font-size: 12px; word-break: break-all;">{if condition="$wxwork_config.signature"}{$wxwork_config.signature}{else /}未生成{/if}</span>
</div>
<div>
<span><strong>最后更新时间:</strong></span>
<span id="last-update-time" style="color: #666;">{if condition="$wxwork_config.timestamp"}<?php echo date('Y-m-d H:i:s', $wxwork_config['timestamp']); ?>{else /}从未生成{/if}</span>
</div>
</div>
</div>
<div class="wxwork-help">
<p><strong>说明</strong>:系统将在保存配置时自动生成时间戳、随机字符串和签名</p>
<p><strong>生成规则</strong>基于Secret使用SHA1算法生成用于企业微信API验证</p>
</div>
</div>
<div class="form-row">
<button class="layui-btn" lay-submit lay-filter="save">保存配置</button>
<button type="button" class="layui-btn layui-btn-primary" onclick="window.location.reload()">重置</button>
<button type="button" class="layui-btn layui-btn-normal" onclick="window.open('https://work.weixin.qq.com/', '_blank')">企业微信管理后台</button>
</div>
</div>
<!-- 获取指南 -->
<div class="wxwork-guide-toggle">
<div class="guide-toggle-btn" onclick="toggleGuide()">
<span>📋 企业微信配置获取指南</span>
<span class="guide-toggle-icon"></span>
</div>
<div class="guide-content">
<p style="color: #ff5722; font-weight: bold; margin-bottom: 15px;">⚠️ 注意:客服功能为系统必需功能,必须正确配置相关参数</p>
<ol>
<li><strong>登录企业微信管理后台</strong><a href="https://work.weixin.qq.com/" target="_blank" class="wxwork-link">https://work.weixin.qq.com/</a></li>
<li><strong>获取企业ID</strong>进入「我的企业」→「企业信息」→「企业ID」</li>
<li><strong>创建应用</strong>:进入「应用管理」→「应用」→「创建应用」→「自建」</li>
<li><strong>获取应用ID</strong>:创建完成后进入应用详情页,查看 <span class="wxwork-code">AgentId</span></li>
<li><strong>获取Secret</strong>:在应用详情页查看 <span class="wxwork-code">Secret</span>(用于生成签名)</li>
<li><strong>配置可信域名</strong>在应用详情页设置可信域名用于JS接口调用</li>
<li><strong>开启客服功能</strong><strong style="color: #ff5722;">(必需)</strong>进入「应用管理」→「客服」→「联系我」→ 开启服务</li>
<li><strong>获取客服参数</strong>在客服页面获取客服ID和活码链接</li>
</ol>
<h5 style="margin-top: 15px; color: #007bff;">🔧 参数获取方式说明</h5>
<table style="width: 100%; border-collapse: collapse; margin-top: 10px;">
<tr style="background: #f8f9fa;">
<th style="border: 1px solid #dee2e6; padding: 8px; text-align: left;">参数名称</th>
<th style="border: 1px solid #dee2e6; padding: 8px; text-align: left;">获取方式</th>
<th style="border: 1px solid #dee2e6; padding: 8px; text-align: left;">说明</th>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业ID</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业微信管理后台 → 我的企业 → 企业信息 → 企业ID</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业唯一标识</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">应用ID</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业微信管理后台 → 应用管理 → 选择应用 → 查看应用详情 → AgentId</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">应用的AgentId</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">应用Secret</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业微信管理后台 → 应用管理 → 选择应用 → 查看Secret</td>
<td style="border: 1px solid #dee2e6; padding: 8px; color: #ff5722;"><strong>必需</strong>:用于生成签名的密钥</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">客服ID</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业微信管理后台 → 应用管理 → 客服 → 联系我</td>
<td style="border: 1px solid #dee2e6; padding: 8px; color: #ff5722;"><strong>必需</strong>:客服功能的唯一标识</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">活码链接</td>
<td style="border: 1px solid #dee2e6; padding: 8px;">企业微信管理后台 → 客服 → 联系我 → 生成活码</td>
<td style="border: 1px solid #dee2e6; padding: 8px; color: #ff5722;"><strong>必需</strong>:客服活码的完整链接</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">时间戳</td>
<td style="border: 1px solid #dee2e6; padding: 8px;"><span class="wxwork-code">本系统自动生成</span></td>
<td style="border: 1px solid #dee2e6; padding: 8px;">Unix时间戳无需手动填写</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">随机字符串</td>
<td style="border: 1px solid #dee2e6; padding: 8px;"><span class="wxwork-code">本系统自动生成</span></td>
<td style="border: 1px solid #dee2e6; padding: 8px;">16位随机字符无需手动填写</td>
</tr>
<tr>
<td style="border: 1px solid #dee2e6; padding: 8px;">签名</td>
<td style="border: 1px solid #dee2e6; padding: 8px;"><span class="wxwork-code">本系统自动生成</span></td>
<td style="border: 1px solid #dee2e6; padding: 8px;">SHA1签名无需手动填写</td>
</tr>
</table>
</div>
</div>
<script>
layui.use(['form', 'layer'], function() {
var form = layui.form,
layer = layui.layer,
repeat_flag = false; //防重复标识
form.render();
// 启用状态切换时的验证
$('input[name="enabled"]').on('change', function() {
if($(this).is(':checked')) {
var corpId = $('input[name="corp_id"]').val().trim();
var agentId = $('input[name="agent_id"]').val().trim();
var secret = $('input[name="secret"]').val().trim();
var contactId = $('input[name="contact_id"]').val().trim();
var contactUrl = $('input[name="contact_url"]').val().trim();
if(!corpId) {
layer.msg('请先填写企业ID必填项', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="corp_id"]').focus();
return;
}
if(!agentId) {
layer.msg('请先填写应用ID必填项', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="agent_id"]').focus();
return;
}
if(!secret) {
layer.msg('请先填写应用Secret系统必需', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="secret"]').focus();
return;
}
if(!contactId) {
layer.msg('请先填写客服ID系统必需功能', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="contact_id"]').focus();
return;
}
if(!contactUrl) {
layer.msg('请先填写活码链接(系统必需功能)', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="contact_url"]').focus();
return;
}
// 企业ID格式验证
if(!/^ww[a-zA-Z0-9]{16}$/.test(corpId)) {
layer.msg('企业ID格式不正确应为ww开头的18位字符', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="corp_id"]').focus();
return;
}
// 应用ID格式验证
if(!/^\d+$/.test(agentId)) {
layer.msg('应用ID格式不正确应为纯数字', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="agent_id"]').focus();
return;
}
// Secret格式验证
if(secret.length < 10) {
layer.msg('Secret格式不正确长度至少10位字符', {icon: 2});
$(this).prop('checked', false);
form.render();
$('input[name="secret"]').focus();
return;
}
layer.msg('配置验证通过!企业微信功能已启用,系统将自动生成签名', {icon: 1});
}
});
// 更新配置完整性状态
function updateConfigStatus() {
var corpId = $('input[name="corp_id"]').val().trim();
var agentId = $('input[name="agent_id"]').val().trim();
var secret = $('input[name="secret"]').val().trim();
var contactId = $('input[name="contact_id"]').val().trim();
var contactUrl = $('input[name="contact_url"]').val().trim();
var timestamp = $('input[name="timestamp"]').val();
var nonceStr = $('input[name="nonceStr"]').val();
var signature = $('input[name="signature"]').val();
// 更新配置完整性
var requiredFields = [corpId, agentId, secret, contactId, contactUrl];
var filledCount = requiredFields.filter(function(field) { return field.length > 0; }).length;
var completeness = Math.round((filledCount / requiredFields.length) * 100);
var completenessElement = $('#config-completeness');
if (completeness === 100) {
completenessElement.text('完整 (100%)').css('color', '#52c41a');
} else {
completenessElement.text('不完整 (' + completeness + '%)').css('color', '#ff7875');
}
// 更新签名状态和相关信息
var signatureElement = $('#signature-status');
var timestampElement = $('#timestamp-value');
var nonceStrElement = $('#nonce-str-value');
var signatureValueElement = $('#signature-value');
if (signature && timestamp) {
// 显示已存在的签名信息
signatureElement.text('已生成').css('color', '#52c41a');
timestampElement.text(timestamp).css('color', '#52c41a');
nonceStrElement.text(nonceStr || '未生成').css('color', '#52c41a');
signatureValueElement.text(signature).css('color', '#52c41a');
} else {
// 显示生成提示
if (completeness === 100) {
signatureElement.text('配置完整,点击保存将生成').css('color', '#1890ff');
} else {
signatureElement.text('请完善必填配置信息').css('color', '#ff7875');
}
timestampElement.text('未生成').css('color', '#999');
nonceStrElement.text('未生成').css('color', '#999');
signatureValueElement.text('未生成').css('color', '#999');
}
}
// 监听所有输入框变化
$('input[name="corp_id"], input[name="agent_id"], input[name="secret"], input[name="contact_id"], input[name="contact_url"]').on('input blur', function() {
updateConfigStatus();
});
// 初始化状态
updateConfigStatus();
// 表单提交前的必填项验证
form.on('submit(save)', function(data) {
var corpId = data.field.corp_id.trim();
var agentId = data.field.agent_id.trim();
var secret = data.field.secret.trim();
var contactId = data.field.contact_id.trim();
var contactUrl = data.field.contact_url.trim();
// 验证企业ID
if(!corpId) {
layer.msg('企业ID为必填项', {icon: 2});
$('input[name="corp_id"]').focus();
repeat_flag = false;
return false;
}
// 验证应用ID
if(!agentId) {
layer.msg('应用ID为必填项', {icon: 2});
$('input[name="agent_id"]').focus();
repeat_flag = false;
return false;
}
// 验证Secret必需
if(!secret) {
layer.msg('应用Secret为系统必需参数必须填写', {icon: 2});
$('input[name="secret"]').focus();
repeat_flag = false;
return false;
}
// 验证客服ID必需
if(!contactId) {
layer.msg('客服ID为系统必需功能必须填写', {icon: 2});
$('input[name="contact_id"]').focus();
repeat_flag = false;
return false;
}
// 验证活码链接(必需)
if(!contactUrl) {
layer.msg('活码链接为系统必需功能,必须填写', {icon: 2});
$('input[name="contact_url"]').focus();
repeat_flag = false;
return false;
}
// 企业ID格式验证
if(!/^ww[a-zA-Z0-9]{16}$/.test(corpId)) {
layer.msg('企业ID格式不正确应为ww开头的18位字符', {icon: 2});
$('input[name="corp_id"]').focus();
repeat_flag = false;
return false;
}
// 应用ID格式验证
if(!/^\d+$/.test(agentId)) {
layer.msg('应用ID格式不正确应为纯数字', {icon: 2});
$('input[name="agent_id"]').focus();
repeat_flag = false;
return false;
}
// Secret格式验证
if(secret.length < 10) {
layer.msg('Secret格式不正确长度至少10位字符', {icon: 2});
$('input[name="secret"]').focus();
repeat_flag = false;
return false;
}
// 客服ID格式验证
if(!/^kfc_[a-zA-Z0-9]+$/.test(contactId)) {
layer.msg('客服ID格式不正确应以kfc_开头', {icon: 2});
$('input[name="contact_id"]').focus();
repeat_flag = false;
return false;
}
// 活码链接格式验证
if(!/^https:\/\/work\.weixin\.qq\.com\/kf\//.test(contactUrl)) {
layer.msg('活码链接格式不正确,应为 https://work.weixin.qq.com/kf/... 格式', {icon: 2});
$('input[name="contact_url"]').focus();
repeat_flag = false;
return false;
}
// 继续执行原有的AJAX提交逻辑
if (repeat_flag) return false;
repeat_flag = true;
// 开关值处理
data.field.enabled = data.field.enabled ? 1 : 0;
$.ajax({
url: ns.url("shop/config/wxwork"),
data: data.field,
dataType: 'JSON',
type: 'POST',
success: function(res){
if(res.code == 0){
layer.msg('企业微信配置保存成功!系统已自动生成签名和时间戳', {icon: 1});
// 刷新页面以显示更新后的状态
setTimeout(function() {
window.location.reload();
}, 1500);
}else{
layer.msg(res.message, {icon: 2});
}
repeat_flag = false;
},
error: function(){
layer.msg('服务器错误', {icon: 2});
repeat_flag = false;
}
});
return false; // 阻止默认提交
});
});
// 切换密码显示/隐藏
function togglePassword() {
var secretInput = $('#secret-input');
var toggleBtn = $('.password-toggle');
if (secretInput.attr('type') === 'password') {
secretInput.attr('type', 'text');
toggleBtn.text('🙈');
} else {
secretInput.attr('type', 'password');
toggleBtn.text('👁️');
}
}
// 切换指南显示/隐藏
function toggleGuide() {
var btn = $('.guide-toggle-btn');
var content = $('.guide-content');
var icon = $('.guide-toggle-icon');
if (content.hasClass('show')) {
content.removeClass('show');
btn.removeClass('active');
icon.removeClass('rotate');
} else {
content.addClass('show');
btn.addClass('active');
icon.addClass('rotate');
}
}
</script>

View File

@@ -1,5 +1,9 @@
<!-- 搜索框 -->
<div class="single-filter-box">
<div class="single-filter-box top">
<div class="layui-form">
<div class="layui-input-inline">
<label class="layui-form-label">发送状态:</label>
@@ -42,6 +46,7 @@
</div>
</script>
<script>
layui.use(['form', 'laytpl'], function() {
var table,
@@ -154,7 +159,7 @@
</tr>
<tr>
<td>通知内容</td>
<td colspan="3">{{d.content}}{{d.var_parse}}</td>
<td colspan="3">{{d.content}}</td>
</tr>
<tr>
<td>返回结果</td>

View File

@@ -68,11 +68,12 @@ if ($appEnv) {
if (is_file($envFile)) {
$app->env->load($envFile);
}
}
// 3. 为了兼容性,如果存在.env.local也加载优先级最高
if (is_file(__DIR__ . '/.env.local')) {
$app->env->load(__DIR__ . '/.env.local');
} else {
echo "⚠️ 警告未设置APP_ENV环境变量默认使用local环境。\n";
// 3. 为了兼容性,如果存在.env.local也加载优先级最高
if (is_file(__DIR__ . '/.env.local')) {
$app->env->load(__DIR__ . '/.env.local');
}
}
// 执行HTTP应用并响应

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
[[{"1613":0,"1616":1,"1621":2}],[{"1557":0,"1611":1,"1612":2,"1614":3,"1615":4,"1617":5,"1618":6,"1619":7,"1620":8,"1623":9,"1626":10,"1648":11,"57990":12,"57991":13,"57992":14,"57993":15,"57994":16,"57995":17,"57996":18,"57997":19}],[{"3768":0,"3769":1}],[{"3784":0,"3785":1,"3786":2,"3787":3,"3788":4,"3789":5}],[{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"825":22,"826":23,"827":24,"828":25,"839":26,"840":27,"841":28,"845":29,"846":30,"851":31,"858":32},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"829":18,"831":19,"832":20,"833":21,"834":22,"836":23,"838":24,"842":25,"843":26,"844":27,"849":28,"850":29,"855":30,"7620":31,"7621":32,"7622":33,"7623":34,"7624":35,"7625":36,"8400":37,"8401":38,"8406":39,"8407":40,"8411":41,"8412":42,"8417":43,"57895":44,"57896":45,"57897":46,"57898":47,"57899":48,"57900":49,"57901":50,"57902":51,"57903":52,"57904":53,"57905":54,"58088":55}],[{"1456":0,"1457":1,"1458":2,"1459":3,"1460":4,"1461":5,"1462":6,"1463":7,"1464":8,"1467":9,"1469":10,"1479":11,"1613":12,"1616":13,"1621":14}],[{"1456":0,"1457":1,"1458":2,"1459":3,"1460":4,"1461":5,"1462":6,"1463":7,"1464":8,"1467":9,"1469":10,"1479":11,"1613":12,"1616":13,"1621":14,"2034":15}],[{"1468":0}],[{"1471":0,"1557":1,"1611":2,"1612":3,"1614":4,"1615":5,"1617":6,"1618":7,"1619":8,"1620":9,"1623":10,"1626":11,"1648":12,"64286":13,"57990":14,"57991":15,"57992":16,"57993":17,"57994":18,"57995":19,"57996":20,"57997":21}],[{"1471":0,"1557":1,"1611":2,"1612":3,"1614":4,"1615":5,"1617":6,"1618":7,"1619":8,"1620":9,"1623":10,"1626":11,"1648":12,"2027":13,"2028":14,"2029":15,"2030":16,"2031":17,"2032":18,"2033":19,"2035":20,"64286":21,"57990":22,"57991":23,"57992":24,"57993":25,"57994":26,"57995":27,"57996":28,"57997":29}],[{"3768":0,"3769":1,"3772":2}],[{"3761":0,"3764":1,"3765":2,"3766":3,"3767":4,"3771":5,"3784":6,"3785":7,"3786":8,"3787":9,"3788":10,"3789":11}],[{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34}],[{"65057":0,"65059":1},{"768":0,"769":1},{"807":0},{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"786":18,"787":19,"788":20,"829":21,"830":22,"831":23,"832":24,"833":25,"834":26,"836":27,"838":28,"842":29,"843":30,"844":31,"849":32,"850":33,"855":34,"1155":35,"1156":36,"1157":37,"1158":38,"1159":39,"7620":40,"7621":41,"7622":42,"7623":43,"7624":44,"7625":45,"8400":46,"8401":47,"8406":48,"8407":49,"8411":50,"8412":51,"8417":52,"57895":53,"57896":54,"57897":55,"57898":56,"57899":57,"57900":58,"57901":59,"57902":60,"57903":61,"57904":62,"57905":63,"58088":64},{"808":0}],[{"45":0,"65":1,"66":2,"67":3,"68":4,"70":5,"71":6,"72":7,"74":8,"75":9,"76":10,"79":11,"80":12,"81":13,"82":14,"83":15,"84":16,"85":17,"86":18,"87":19,"88":20,"89":21,"90":22,"101":23,"102":24,"107":25,"110":26,"111":27,"114":28,"118":29,"119":30,"120":31,"121":32,"171":33,"187":34,"192":35,"193":36,"194":37,"195":38,"196":39,"198":40,"199":41,"208":42,"210":43,"211":44,"212":45,"213":46,"214":47,"217":48,"218":49,"219":50,"220":51,"221":52,"222":53,"223":54,"232":55,"233":56,"234":57,"235":58,"240":59,"241":60,"242":61,"243":62,"244":63,"245":64,"246":65,"253":66,"255":67,"256":68,"258":69,"260":70,"262":71,"268":72,"270":73,"272":74,"283":75,"286":76,"313":77,"317":78,"320":79,"328":80,"340":81,"341":82,"344":83,"345":84,"350":85,"352":86,"356":87,"366":88,"376":89,"381":90,"8208":91,"8220":92,"8222":93,"42788":94,"42816":95,"42817":96}],[{"61185":0,"61186":1,"61187":2,"61188":3,"61189":4,"61191":5,"61192":6,"61193":7,"61194":8,"61195":9,"61197":10,"61198":11,"61199":12,"61200":13,"61201":14,"61203":15,"61204":16,"61205":17,"61206":18,"61207":19}]]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"rtlSUB":{"007CA":{"1":"0E28E","3":"0E28F","2":"0E290"},"007CB":{"1":"0E291","3":"0E292","2":"0E293"},"007CC":{"1":"0E294","3":"0E295","2":"0E296"},"007CD":{"1":"0E297","3":"0E298","2":"0E299"},"007CE":{"1":"0E29A","3":"0E29B","2":"0E29C"},"007CF":{"1":"0E29D","3":"0E29E","2":"0E29F"},"007D0":{"1":"0E2A0","3":"0E2A1","2":"0E2A2"},"007D1":{"1":"0E2A3","3":"0E2A4","2":"0E2A5"},"007D2":{"1":"0E2A6","3":"0E2A7","2":"0E2A8"},"007D3":{"1":"0E2A9","3":"0E2AA","2":"0E2AB"},"007D4":{"1":"0E2AC","3":"0E2AD","2":"0E2AE"},"007D5":{"1":"0E2AF","3":"0E2B0","2":"0E2B1"},"007D6":{"1":"0E2B2","3":"0E2B3","2":"0E2B4"},"007D7":{"1":"0E2B5","3":"0E2B6","2":"0E2B7"},"007D8":{"1":"0E2B8","3":"0E2B9","2":"0E2BA"},"007D9":{"1":"0E2BB","3":"0E2BC","2":"0E2BD"},"007DA":{"1":"0E2BE","3":"0E2BF","2":"0E2C0"},"007DB":{"1":"0E2C1","3":"0E2C2","2":"0E2C3"},"007DC":{"1":"0E2C4","3":"0E2C5","2":"0E2C6"},"007DD":{"1":"0E2C7","3":"0E2C8","2":"0E2C9"},"007DE":{"1":"0E2CA","3":"0E2CB","2":"0E2CC"},"007DF":{"1":"0E2CD","3":"0E2CE","2":"0E2CF"},"007E0":{"1":"0E2D0","3":"0E2D1","2":"0E2D2"},"007E1":{"1":"0E2D3","3":"0E2D4","2":"0E2D5"},"007E2":{"1":"0E2D6","3":"0E2D7","2":"0E2D8"},"007E3":{"1":"0E2D9","3":"0E2DA","2":"0E2DB"},"007E4":{"1":"0E2DC","3":"0E2DD","2":"0E2DE"},"007E5":{"1":"0E2DF","3":"0E2E0","2":"0E2E1"},"007E6":{"1":"0E2E2","3":"0E2E3","2":"0E2E4"},"007E7":{"1":"0E2E5","3":"0E2E6","2":"0E2E7"}},"finals":"0E28E 0E291 0E294 0E297 0E29A 0E29D 0E2A0 0E2A3 0E2A6 0E2A9 0E2AC 0E2AF 0E2B2 0E2B5 0E2B8 0E2BB 0E2BE 0E2C1 0E2C4 0E2C7 0E2CA 0E2CD 0E2D0 0E2D3 0E2D6 0E2D9 0E2DC 0E2DF 0E2E2 0E2E5 ","rphf":[],"half":[],"pref":[],"blwf":[],"pstf":[]}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"rangeid":122,"prevcid":126,"prevwidth":754,"interval":false,"range":{"32":[286,360,414,754,572,855,702,247],"40":{"0":351,"1":351,"interval":true},"42":[450,754,286,325,286,303],"48":{"0":572,"1":572,"interval":true,"2":572,"3":572,"4":572,"5":572,"6":572,"7":572,"8":572,"9":572},"58":{"0":303,"1":303,"interval":true},"60":{"0":754,"1":754,"interval":true,"2":754},"63":[478,900,615,617,628,693,568,518,697,677],"73":{"0":265,"1":265,"interval":true},"75":[590,501,776,673,708,542,708,625,571,549,659,615,890,616,549,616,351,303,351,754],"95":{"0":450,"1":450,"interval":true},"97":[551,571,495,571,554,316,571,570],"105":{"0":250,"1":250,"interval":true},"107":[521,250,876,570,550],"112":{"0":571,"1":571,"interval":true},"114":[370,469,353,570,532,736],"120":{"0":532,"1":532,"interval":true},"122":[472,572,303,572,754]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
[[{"1613":0,"1616":1,"1621":2}],[{"1557":0,"1611":1,"1612":2,"1614":3,"1615":4,"1617":5,"1618":6,"1619":7,"1620":8,"1623":9,"1626":10,"1648":11,"57974":12,"57975":13,"57976":14,"57977":15,"57978":16,"57979":17,"57980":18,"57981":19}],[{"3768":0,"3769":1}],[{"3784":0,"3785":1,"3786":2,"3787":3,"3788":4,"3789":5}],[{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"829":18,"831":19,"832":20,"833":21,"834":22,"836":23,"838":24,"842":25,"843":26,"844":27,"849":28,"850":29,"855":30,"7620":31,"7621":32,"7622":33,"7623":34,"7624":35,"7625":36,"8400":37,"8401":38,"8406":39,"8407":40,"8411":41,"8412":42,"8417":43,"57879":44,"57880":45,"57881":46,"57882":47,"57883":48,"57884":49,"57885":50,"57886":51,"57887":52,"57888":53,"57926":54,"58072":55}],[{"1456":0,"1457":1,"1458":2,"1459":3,"1460":4,"1461":5,"1462":6,"1463":7,"1464":8,"1467":9,"1469":10,"1479":11,"1613":12,"1616":13,"1621":14}],[{"1456":0,"1457":1,"1458":2,"1459":3,"1460":4,"1461":5,"1462":6,"1463":7,"1464":8,"1467":9,"1469":10,"1479":11,"1613":12,"1616":13,"1621":14,"2034":15}],[{"1468":0}],[{"1465":0,"1471":1,"1557":2,"1611":3,"1612":4,"1614":5,"1615":6,"1617":7,"1618":8,"1619":9,"1620":10,"1623":11,"1626":12,"1648":13,"64286":14,"57974":15,"57975":16,"57976":17,"57977":18,"57978":19,"57979":20,"57980":21,"57981":22}],[{"1465":0,"1471":1,"1557":2,"1611":3,"1612":4,"1614":5,"1615":6,"1617":7,"1618":8,"1619":9,"1620":10,"1623":11,"1626":12,"1648":13,"2027":14,"2028":15,"2029":16,"2030":17,"2031":18,"2032":19,"2033":20,"2035":21,"64286":22,"57974":23,"57975":24,"57976":25,"57977":26,"57978":27,"57979":28,"57980":29,"57981":30}],[{"3768":0,"3769":1,"3772":2}],[{"3761":0,"3764":1,"3765":2,"3766":3,"3767":4,"3771":5,"3784":6,"3785":7,"3786":8,"3787":9,"3788":10,"3789":11}],[{"65057":0,"65059":1},{"768":0,"769":1},{"807":0},{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"786":18,"787":19,"788":20,"829":21,"830":22,"831":23,"832":24,"833":25,"834":26,"835":27,"836":28,"838":29,"842":30,"843":31,"844":32,"849":33,"850":34,"855":35,"1155":36,"1156":37,"1157":38,"1158":39,"1159":40,"7620":41,"7621":42,"7622":43,"7623":44,"7624":45,"7625":46,"8400":47,"8401":48,"8406":49,"8407":50,"8411":51,"8412":52,"8417":53,"57879":54,"57880":55,"57881":56,"57882":57,"57883":58,"57884":59,"57885":60,"57886":61,"57887":62,"57888":63,"57926":64,"58072":65},{"808":0}],[{"45":0,"65":1,"66":2,"67":3,"68":4,"70":5,"71":6,"75":7,"76":8,"79":9,"80":10,"81":11,"82":12,"83":13,"84":14,"85":15,"86":16,"87":17,"88":18,"89":19,"90":20,"97":21,"102":22,"107":23,"114":24,"118":25,"119":26,"121":27,"192":28,"193":29,"194":30,"195":31,"196":32,"198":33,"199":34,"208":35,"210":36,"211":37,"212":38,"213":39,"214":40,"216":41,"217":42,"218":43,"219":44,"220":45,"221":46,"224":47,"225":48,"226":49,"227":50,"228":51,"229":52,"253":53,"255":54,"256":55,"258":56,"260":57,"262":58,"264":59,"266":60,"268":61,"270":62,"272":63,"286":64,"313":65,"317":66,"320":67,"321":68,"340":69,"341":70,"344":71,"345":72,"350":73,"352":74,"356":75,"366":76,"376":77,"381":78,"699":79,"8208":80,"8216":81,"8218":82,"8220":83,"8222":84,"42788":85,"42816":86,"42817":87}],[{"61185":0,"61186":1,"61187":2,"61188":3,"61189":4,"61191":5,"61192":6,"61193":7,"61194":8,"61195":9,"61197":10,"61198":11,"61199":12,"61200":13,"61201":14,"61203":15,"61204":16,"61205":17,"61206":18,"61207":19}]]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"rtlSUB":{"007CA":{"1":"0E27E","3":"0E27F","2":"0E280"},"007CB":{"1":"0E281","3":"0E282","2":"0E283"},"007CC":{"1":"0E284","3":"0E285","2":"0E286"},"007CD":{"1":"0E287","3":"0E288","2":"0E289"},"007CE":{"1":"0E28A","3":"0E28B","2":"0E28C"},"007CF":{"1":"0E28D","3":"0E28E","2":"0E28F"},"007D0":{"1":"0E290","3":"0E291","2":"0E292"},"007D1":{"1":"0E293","3":"0E294","2":"0E295"},"007D2":{"1":"0E296","3":"0E297","2":"0E298"},"007D3":{"1":"0E299","3":"0E29A","2":"0E29B"},"007D4":{"1":"0E29C","3":"0E29D","2":"0E29E"},"007D5":{"1":"0E29F","3":"0E2A0","2":"0E2A1"},"007D6":{"1":"0E2A2","3":"0E2A3","2":"0E2A4"},"007D7":{"1":"0E2A5","3":"0E2A6","2":"0E2A7"},"007D8":{"1":"0E2A8","3":"0E2A9","2":"0E2AA"},"007D9":{"1":"0E2AB","3":"0E2AC","2":"0E2AD"},"007DA":{"1":"0E2AE","3":"0E2AF","2":"0E2B0"},"007DB":{"1":"0E2B1","3":"0E2B2","2":"0E2B3"},"007DC":{"1":"0E2B4","3":"0E2B5","2":"0E2B6"},"007DD":{"1":"0E2B7","3":"0E2B8","2":"0E2B9"},"007DE":{"1":"0E2BA","3":"0E2BB","2":"0E2BC"},"007DF":{"1":"0E2BD","3":"0E2BE","2":"0E2BF"},"007E0":{"1":"0E2C0","3":"0E2C1","2":"0E2C2"},"007E1":{"1":"0E2C3","3":"0E2C4","2":"0E2C5"},"007E2":{"1":"0E2C6","3":"0E2C7","2":"0E2C8"},"007E3":{"1":"0E2C9","3":"0E2CA","2":"0E2CB"},"007E4":{"1":"0E2CC","3":"0E2CD","2":"0E2CE"},"007E5":{"1":"0E2CF","3":"0E2D0","2":"0E2D1"},"007E6":{"1":"0E2D2","3":"0E2D3","2":"0E2D4"},"007E7":{"1":"0E2D5","3":"0E2D6","2":"0E2D7"}},"finals":"0E27E 0E281 0E284 0E287 0E28A 0E28D 0E290 0E293 0E296 0E299 0E29C 0E29F 0E2A2 0E2A5 0E2A8 0E2AB 0E2AE 0E2B1 0E2B4 0E2B7 0E2BA 0E2BD 0E2C0 0E2C3 0E2C6 0E2C9 0E2CC 0E2CF 0E2D2 0E2D5 ","rphf":[],"half":[],"pref":[],"blwf":[],"pstf":[]}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"rangeid":114,"prevcid":126,"prevwidth":754,"interval":false,"range":{"32":[313,410,469,754,626,901,785,275],"40":{"0":411,"1":411,"interval":true},"42":[470,754,342,374,342,329],"48":{"0":626,"1":626,"interval":true,"2":626,"3":626,"4":626,"5":626,"6":626,"7":626,"8":626,"9":626},"58":{"0":360,"1":360,"interval":true},"60":{"0":754,"1":754,"interval":true,"2":754},"63":[522,900,696,686,660,747],"69":{"0":615,"1":615,"interval":true},"71":[738,753],"73":{"0":334,"1":334,"interval":true},"75":[697,573,896,753,765,659,765,693,648,614,730,696,993,694,651,652,411,329,411,754],"95":{"0":450,"1":450,"interval":true},"97":[607,644,533,644,610,391,644,641],"105":{"0":308,"1":308,"interval":true},"107":[598,308,938,641,618],"112":{"0":644,"1":644,"interval":true},"114":[444,536,430,641,586,831,580,586,523,641,329,641,754]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
[[{"3784":0,"3785":1,"3786":2,"3787":3,"3788":4,"3789":5}],[{"3768":0,"3769":1}],[{"792":0,"793":1,"796":2,"797":3,"798":4,"799":5,"805":6,"809":7,"810":8,"812":9,"813":10,"825":11,"826":12,"827":13,"840":14,"851":15,"858":16},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"829":18,"831":19,"832":20,"833":21,"834":22,"836":23,"838":24,"842":25,"843":26,"844":27,"849":28,"850":29,"855":30,"7620":31,"7621":32,"7622":33,"7623":34,"7624":35,"7625":36,"8400":37,"8401":38,"8406":39,"8407":40,"8411":41,"8412":42,"8417":43,"57811":44,"57812":45,"57813":46,"57814":47,"57815":48,"57816":49,"57817":50,"57818":51,"57819":52,"57820":53,"57821":54,"57863":55}],[{"1456":0,"1457":1,"1458":2,"1459":3,"1460":4,"1461":5,"1462":6,"1463":7,"1464":8,"1467":9,"1469":10,"1479":11}],[{"1468":0}],[{"1471":0,"64286":1}],[{"3761":0,"3764":1,"3765":2,"3766":3,"3767":4,"3771":5,"3784":6,"3785":7,"3786":8,"3787":9,"3788":10,"3789":11}],[{"3768":0,"3769":1,"3772":2}],[{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34}],[{"808":0},{"65057":0,"65059":1},{"768":0,"769":1},{"807":0,"7698":1},{"790":0,"791":1,"792":2,"793":3,"796":4,"797":5,"798":6,"799":7,"800":8,"803":9,"804":10,"805":11,"806":12,"809":13,"810":14,"811":15,"812":16,"813":17,"814":18,"815":19,"816":20,"817":21,"818":22,"819":23,"825":24,"826":25,"827":26,"828":27,"839":28,"840":29,"841":30,"845":31,"846":32,"851":33,"858":34},{"768":0,"769":1,"770":2,"771":3,"772":4,"773":5,"774":6,"775":7,"776":8,"777":9,"778":10,"779":11,"780":12,"781":13,"782":14,"783":15,"784":16,"785":17,"786":18,"787":19,"788":20,"829":21,"830":22,"831":23,"832":24,"833":25,"834":26,"835":27,"836":28,"838":29,"842":30,"843":31,"844":32,"849":33,"850":34,"855":35,"1155":36,"1156":37,"1157":38,"1158":39,"1159":40,"7620":41,"7621":42,"7622":43,"7623":44,"7624":45,"7625":46,"8400":47,"8401":48,"8406":49,"8407":50,"8411":51,"8412":52,"8417":53,"57811":54,"57812":55,"57813":56,"57814":57,"57815":58,"57816":59,"57817":60,"57818":61,"57819":62,"57820":63,"57821":64,"57863":65}],[{"45":0,"65":1,"66":2,"67":3,"70":4,"75":5,"76":6,"79":7,"80":8,"81":9,"83":10,"84":11,"86":12,"87":13,"88":14,"89":15,"90":16,"102":17,"114":18,"118":19,"119":20,"121":21,"192":22,"193":23,"194":24,"195":25,"196":26,"198":27,"199":28,"208":29,"210":30,"211":31,"212":32,"213":33,"214":34,"216":35,"221":36,"222":37,"253":38,"255":39,"256":40,"258":41,"260":42,"262":43,"264":44,"266":45,"268":46,"313":47,"317":48,"321":49,"341":50,"345":51,"350":52,"352":53,"356":54,"376":55,"381":56,"699":57,"8208":58,"8216":59,"8218":60,"8220":61,"8222":62,"42788":63,"42816":64}],[{"61185":0,"61186":1,"61187":2,"61188":3,"61189":4,"61191":5,"61192":6,"61193":7,"61194":8,"61195":9,"61197":10,"61198":11,"61199":12,"61200":13,"61201":14,"61203":15,"61204":16,"61205":17,"61206":18,"61207":19}]]

View File

@@ -1 +0,0 @@
[[{"1506":0}],[{"105":0,"106":1,"303":2,"585":3,"616":4,"669":5,"768":6,"769":7,"770":8,"771":9,"772":10,"773":11,"774":12,"775":13,"776":14,"777":15,"778":16,"779":17,"780":18,"781":19,"782":20,"783":21,"784":22,"785":23,"786":24,"787":25,"788":26,"829":27,"830":28,"831":29,"832":30,"833":31,"834":32,"835":33,"836":34,"838":35,"842":36,"843":37,"844":38,"849":39,"855":40,"1011":41,"1110":42,"1112":43,"1155":44,"1156":45,"1157":46,"1158":47,"7725":48,"7883":49,"8400":50,"8401":51,"8406":52,"8407":53,"8520":54,"8521":55}],[{"741":0,"742":1,"743":2,"744":3,"745":4},{"741":0,"742":1,"743":2,"744":3,"745":4},{"741":0,"742":1,"743":2,"744":3,"745":4},{"741":0,"742":1,"743":2,"744":3,"745":4},{"741":0,"742":1,"743":2,"744":3,"745":4},{"741":0},{"742":0},{"743":0},{"744":0},{"745":0}],[{"1497":0,"1522":1}],[{"1073":0}],[{"330":0}],[{"102":0}],[{"1488":0}],[{"1381":0,"1396":1,"1406":2}],[{"115":0}],[{"383":0}],[{"33":0,"63":1,"70":2,"82":3,"83":4,"84":5,"97":6,"99":7}],[{"1471":0}],[{"1471":0}],[{"910":0,"933":1,"939":2,"946":3,"952":4,"960":5,"966":6}],[{"910":0,"933":1,"939":2,"946":3,"952":4,"960":5,"966":6}],[{"73":0,"74":1,"97":2,"108":3,"330":4}],[{"73":0,"74":1,"97":2,"108":3,"121":4,"330":5}],[{"1506":0}],[{"768":0,"769":1,"770":2,"771":3,"774":4,"775":5,"776":6,"778":7,"779":8,"780":9,"783":10,"785":11}],[{"105":0,"106":1,"303":2,"585":3,"616":4,"669":5,"1011":6,"1110":7,"1112":8,"7725":9,"7883":10,"8520":11,"8521":12}],[{"742":0,"743":1,"744":2,"745":3}],[{"741":0,"742":1,"743":2,"744":3,"745":4}],[{"741":0,"742":1,"743":2,"744":3,"745":4}],[{"741":0,"742":1,"743":2,"744":3,"745":4}],[{"741":0,"742":1,"743":2,"744":3,"745":4}],[{"741":0,"742":1,"743":2,"744":3,"745":4}],[{"161":0,"191":1,"11800":2}]]

View File

@@ -1 +0,0 @@
{"rangeid":122,"prevcid":126,"prevwidth":754,"interval":false,"range":{"32":[286,360,414,754,572,855,702,247],"40":{"0":351,"1":351,"interval":true},"42":[450,754,286,325,286,303],"48":{"0":572,"1":572,"interval":true,"2":572,"3":572,"4":572,"5":572,"6":572,"7":572,"8":572,"9":572},"58":{"0":303,"1":303,"interval":true},"60":{"0":754,"1":754,"interval":true,"2":754},"63":[478,900,615,617,628,693,568,518,697,677],"73":{"0":265,"1":265,"interval":true},"75":[590,501,776,673,708,542,708,625,571,549,659,615,890,616,549,616,351,303,351,754],"95":{"0":450,"1":450,"interval":true},"97":[551,571,495,571,554,316,571,570],"105":{"0":250,"1":250,"interval":true},"107":[521,250,876,570,550],"112":{"0":571,"1":571,"interval":true},"114":[370,469,353,570,532,736],"120":{"0":532,"1":532,"interval":true},"122":[472,572,303,572,754]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"rangeid":1,"prevcid":127,"prevwidth":500,"interval":true,"range":{"1":{"0":500,"1":500,"interval":true,"2":500,"3":500,"4":500,"5":500,"6":500,"7":500,"8":500,"9":500,"10":500,"11":500,"12":500,"13":500,"14":500,"15":500,"16":500,"17":500,"18":500,"19":500,"20":500,"21":500,"22":500,"23":500,"24":500,"25":500,"26":500,"27":500,"28":500,"29":500,"30":500,"31":500,"32":500,"33":500,"34":500,"35":500,"36":500,"37":500,"38":500,"39":500,"40":500,"41":500,"42":500,"43":500,"44":500,"45":500,"46":500,"47":500,"48":500,"49":500,"50":500,"51":500,"52":500,"53":500,"54":500,"55":500,"56":500,"57":500,"58":500,"59":500,"60":500,"61":500,"62":500,"63":500,"64":500,"65":500,"66":500,"67":500,"68":500,"69":500,"70":500,"71":500,"72":500,"73":500,"74":500,"75":500,"76":500,"77":500,"78":500,"79":500,"80":500,"81":500,"82":500,"83":500,"84":500,"85":500,"86":500,"87":500,"88":500,"89":500,"90":500,"91":500,"92":500,"93":500,"94":500,"95":500,"96":500,"97":500,"98":500,"99":500,"100":500,"101":500,"102":500,"103":500,"104":500,"105":500,"106":500,"107":500,"108":500,"109":500,"110":500,"111":500,"112":500,"113":500,"114":500,"115":500,"116":500,"117":500,"118":500,"119":500,"120":500,"121":500,"122":500,"123":500,"124":500,"125":500,"126":500}}}

View File

@@ -1 +0,0 @@
{"name":"Sun-ExtA","type":"TTF","desc":{"CapHeight":684,"XHeight":453,"FontBBox":"[-973 -301 2074 1078]","Flags":4,"Ascent":930,"Descent":-141,"Leading":0,"ItalicAngle":0,"StemV":87,"MissingWidth":1000},"unitsPerEm":256,"up":-86,"ut":47,"strp":254,"strs":47,"ttffile":"D:\\2024project\\myweb\\saas.cn\\vendor\\mpdf\\mpdf\\src\\Config\/..\/..\/ttfonts\/Sun-ExtA.ttf","TTCfontID":0,"originalsize":22993540,"sip":false,"smp":false,"BMPselected":false,"fontkey":"sun-exta","panose":" 0 0 2 1 6 0 3 1 1 1 1 1","haskerninfo":false,"haskernGPOS":false,"hassmallcapsGSUB":false,"fontmetrics":"win","useOTL":0,"rtlPUAstr":"","GSUBScriptLang":[],"GSUBFeatures":[],"GSUBLookups":[],"GPOSScriptLang":[],"GPOSFeatures":[],"GPOSLookups":[],"kerninfo":[]}