docker save -o hy_dev.tar 2d4567b582e85ded3f
docker load -i hy_dev.tar
docker tag xxx xxx
docker 导入导出镜像
docker 创建cron 定时任务
DockerFile
FROM debian:latest
RUN apt-get update && apt-get install -y cron
COPY cronjobs /etc/cron.d/cronjobs
RUN chmod 0644 /etc/cron.d/cronjobs
RUN touch /var/log/cron.log
CMD cron && tail -f /var/log/cron.log
cronjobs
* * * * * root echo 'cron is running' >> /var/log/cron.log 2>&1
##用文件会出错 deepseek说是少了换行导致的
//创建镜像
docker build -t cron-app .
docker构建php74+supervisor+cron
Dockerfile
FROM php:7.4.33-fpm
COPY sources.list /etc/apt/sources.list
# 安装编译 Redis 扩展所需的依赖
RUN apt-get update && apt-get install -y \
autoconf \
g++ \
make \
libssl-dev \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
# 补充运行时依赖库
libfreetype6 \
libjpeg62-turbo \
libpng16-16 \
#添加工具ps 用于查看进程
lsof \
procps \
vim \
supervisor \
&& apt-get install -y --no-install-recommends cron \
# 配置 GD 扩展(明确指定路径)
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install pdo_mysql zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 下载并解压 Redis 扩展源码(版本 5.3.7)
# 注意:需提前将源码包放在构建上下文的 `redis/` 目录下
COPY redis/redis-5.3.7.tgz /tmp/
RUN mkdir -p /usr/src/php/ext/redis \
&& tar xfz /tmp/redis-5.3.7.tgz -C /usr/src/php/ext/redis --strip-components=1 \
&& rm /tmp/redis-5.3.7.tgz
# 编译安装 Redis 扩展
RUN docker-php-ext-install redis
#
## 复制配置
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
#用文件会出错 deepseek说是少了换行导致的
RUN echo "* * * * * root echo 'cron is running' > /proc/1/fd/1 2>&1" >> /etc/cron.d/custom-cron
RUN echo "0 */2 * * * root /usr/bin/supervisorctl restart queue" >> /etc/cron.d/custom-cron
RUN echo "*/30 * * * * root /usr/bin/supervisorctl update" >> /etc/cron.d/custom-cron
RUN echo "3 8 * * * root cd /www/monitor_plat && /usr/local/bin/php think day" >> /etc/cron.d/custom-cron
# 设置权限
RUN chmod 0644 /etc/cron.d/custom-cron && \
touch /var/log/cron.log &&\
chmod 0777 /var/log/cron.log && \
chgrp crontab /etc/cron.d/custom-cron # 添加crontab组权限
# 启动 Supervisor supervisor 也是9000端口 主进程 supervisord 不建议重启
# /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf
# CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
# 复制docker-entrypoint.sh到镜像中,并赋予可执行权限
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
CMD ["/usr/local/bin/docker-entrypoint.sh"]
WORKDIR /www/monitor_plat
EXPOSE 9000
sources.list
deb http://mirrors.aliyun.com/debian/ bullseye main contrib non-free
deb http://mirrors.aliyun.com/debian-security/ bullseye-security main
deb http://mirrors.aliyun.com/debian/ bullseye-updates main contrib non-free
default.conf
server {
listen 80;
index index.php index.html;
server_name smart.u3733.com;
root /www/monitor_plat/public;
location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
}
location ~ \.php$ {
fastcgi_pass php74:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
docker-compose.yml
version: '3'
services:
php74:
image: my-php-fpm:7.4-supervisord
build:
context: .
dockerfile: Dockerfile
container_name: php74
ports:
- "9001:9000"
volumes:
- ./../:/www/monitor_plat
- ./php74/php.ini:/usr/local/etc/php/php.ini
- ./supervisord.conf:/etc/supervisor/conf.d/supervisord.conf
networks:
- smart-network
nginx:
image: registry.cn-hangzhou.aliyuncs.com/gcpu/lnmp:nginx_1.27.5
container_name: nginx
ports:
- "9090:80"
volumes:
- ./../:/www/monitor_plat
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
networks:
- smart-network
depends_on:
- php74
networks:
smart-network:
####
## docker-compose build --no-cache # 强制完全重建
## docker-compose up -d --force-recreate # 重启容器
supervisord 有两种模式,一种是端口的占用9000,一种是unix套接字
supervisord.conf
[inet_http_server]
port=0.0.0.0:9096
[unix_http_server]
file = /var/run/supervisor.sock ; (the path to the socket file)
chmod = 0700 ; sockef file mode (default 0700)
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[program:php-fpm]
daemonize = no ; 禁止后台守护进程模式
command=bash -c "fuser -k 9000/tcp || true; /usr/local/sbin/php-fpm" # 终止占用端口的进程
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:queue]
command=php /www/monitor_plat/think queue:listen --queue=default
directory=/www/monitor_plat
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
user=www-data
numprocs= 4 ; 启动8个进程
process_name=%(program_name)s_%(process_num)02d ; 动态生成进程名
docker-entrypoint.sh
#!/bin/sh
set -e
# 停止 cron 服务(如果正在运行)
pkill cron || true
# 创建运行时目录并设置适当权限
mkdir -p /www/monitor_plat/runtime
chmod -R 775 /www/monitor_plat/runtime
# 重启 cron 服务
service cron restart
# 后台输出 cron 日志
tail -F /var/log/cron.log &
# 启动主进程
exec /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf
eleme ui el-select 远程模糊搜索例子
Vue.component('MySuperGame', {
props: {
// 双向绑定值
value: {
type: [String, Number, Array],
default: ''
},
platform: {
type: [String, Number],
default: 0
},
// 初始默认值(用于回显)
initialValue: {
type: [String, Number, Array],
default: ''
},
// 是否多选
multiple: {
type: Boolean,
default: false
},
// 防抖时间(毫秒)
debounceTime: {
type: Number,
default: 300
},
// 是否启用缓存
enableCache: {
type: Boolean,
default: true
},
// 自定义结果格式化函数
formatter: {
type: Function,
default: items => items.map(item => ({
value: item.id+'',
label: item.id+'-'+item.title+'-' + item.subtitle,
gameInfo: item
}))
}
},
data() {
return {
localValue: this.value || this.initialValue,
options: [],
loading: false,
searchKeyword: '',
searchCache: new Map(),
cancelToken: null
}
},
watch: {
'platform': {
handler(newVal, oldVal) {
if (newVal > 0 && newVal != oldVal) {
this.localValue = ''; // 清空绑定值
this.options = []; // 清空绑定值
}
},
},
value(newVal) {
this.localValue = newVal
},
localValue(newVal) {
this.$emit('input', newVal)
this.$emit('change', newVal)
let gameNode=this.options.filter(item => {
if(item.value==newVal){
return true
}
})
if(gameNode[0]){
this.$emit('gamechange', gameNode[0].gameInfo)
}
}
},
created() {
console.log('platform',this.platform)
// 初始化加载默认值对应的选项
if (this.localValue) {
this.loadInitialValues()
}
},
methods: {
// 防抖搜索方法
handleSearch: _.debounce( async function (keyword) {
this.searchKeyword = keyword.trim()
if (!this.searchKeyword) {
this.options = []
return
}
this.loading = true
this.cancelPreviousRequest()
let response = await axios.post('/topic/card/getGameNodeSearch', {key: keyword, platform: this.platform,format:0})
.catch(error => {
if (!axios.isCancel(error)) {
console.error('Remote search failed:', error)
this.$message.error('搜索失败,请稍后重试')
}
})
.finally(() => (this.loading = false))
const items = this.formatter(response.data.data.gameList || [])
if (this.enableCache) {
this.searchCache.set(this.searchKeyword, items)
}
this.options = items
}, 300),
// 取消之前的请求
cancelPreviousRequest() {
if (this.cancelToken) {
this.cancelToken('取消重复请求')
}
},
// 加载初始值对应的选项
async loadInitialValues() {
try {
this.loading = true
const id = this.localValue
const response = await axios.post(`/topic/card/getGameNodeSearch`, {key: id, format: 0, platform: this.platform})
this.options = this.formatter(response.data.data.gameList || [])
} catch (error) {
console.error('初始化加载失败:', error)
} finally {
this.loading = false
}
},
// 高亮匹配文本
highlight(label) {
if (!this.searchKeyword) return label
const regex = new RegExp(`(${this.searchKeyword})`, 'gi')
return label.replace(regex, '<span class="highlight">$1</span>')
}
},
template: `
<el-select
v-model="localValue"
filterable
clearable
remote
reserve-keyword
placeholder="请输入游戏名或游戏ID"
:multiple="multiple"
:remote-method="handleSearch"
:loading="loading"
:loading-text="'加载中...'"
:no-match-text="'无匹配结果'"
:no-data-text="'暂无数据'"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
<span v-html="highlight(item.label)"></span>
</el-option>
</el-select>
`
})