cron generate utils

该博客介绍了两个Cron表达式工具类的实现,用于构建和描述定时任务的Cron表达式。CronUtil类支持三种常用表达式,而CronUtil02类则根据TaskScheduleModel构建更复杂的表达式。通过示例展示了如何配置每日、每周和每月执行的任务,并生成对应的Cron表达式和详细描述。

I:
/**
 * @ClassName: CronUtil
 * @Description: Cron表达式工具类 目前支持三种常用的cron表达式
 * @author
 * @date
 *
 */
public class CronUtil {

    /**
     *
     * 方法摘要:构建Cron表达式
     *
     * @param rate  频率 0秒;1分;2小时;3日;4月
     * @param cycle 周期
     * @return String
     */
    public static String createLoopCronExpression(int rate, int cycle) {
        String cron = "";
        switch (rate) {
            case 0:// 每cycle秒执行一次
                cron = "0/" + cycle + " * * * * ?";
                break;
            case 1:// 每cycle分钟执行一次
                cron = "0 0/" + cycle + " * * * ?";
                break;
            case 2:// 每cycle小时执行一次
                cron = "0 0 0/" + cycle + " * * ?";
                break;
            case 3:// 每cycle天的0点执行一次
                cron = "0 0 0 1/" + cycle + " * ?";
                break;
            case 4:// 每cycle月的1号0点执行一次
                cron = "0 0 0 1 1/" + cycle + " ? ";
                break;
            case 5://  每天cycle点执行一次
                cron = "0 0 " + cycle+ "  * * ?";
                break;
            default:// 默认每cycle秒执行一次
                cron = "0/1 * * * * ?";
                break;
        }
        return cron;
    }
    /**
     *
     * 方法摘要:构建Cron描述
     *
     * @param rate  频率 0秒;1分;2小时;3日;4月
     * @param cycle 周期
     * @return String
     */
    public static String createLoopCronDescription(int rate, int cycle) {
        String desc = "";
        switch (rate) {
            case 0:// 每cycle秒执行一次
                desc = "每隔" + cycle + "秒触发一次任务";
                break;
            case 1:// 每cycle分钟执行一次
                desc = "每隔" + cycle + "分钟触发一次任务";
                break;
            case 2:// 每cycle小时执行一次
                desc = "每隔" + cycle + "小时触发一次任务";
                break;
            case 3:// 每cycle天的0点执行一次
                desc = "每隔" + cycle + "天的0点触发一次任务";
                break;
            case 4:// 每cycle月的1号0点执行一次
                desc = "每隔" + cycle + "月的1日0点触发一次任务";
                break;
            case 5:// 每天cycle点执行一次
                desc = "每天" + cycle + "点执行一次任务";
                break;
            default:// 默认每cycle秒执行一次
                desc = "每隔" + cycle + "秒触发一次任务";
                break;
        }
        return desc;
    }

    public static void main(String[] args) {

        System.out.println(CronUtil.createLoopCronExpression(4, 1));
        System.out.println(CronUtil.createLoopCronDescription(4, 1));

    }
}



=====================
II:


/** @ClassName: CronUtil
 * @Description: Cron表达式工具类
 * @author
 * @date
 *
 */
public class CronUtil02 {

    /**
     *
     *方法摘要:构建Cron表达式 
     *@param  taskScheduleModel
     *@return String
     */
    public static String createCronExpression(TaskScheduleModel taskScheduleModel){
        StringBuffer cronExp = new StringBuffer("");

        if(null == taskScheduleModel.getJobType()) {
            System.out.println("执行周期未配置" );//执行周期未配置 
        }

        if (null != taskScheduleModel.getSecond()
                && null != taskScheduleModel.getMinute()
                && null != taskScheduleModel.getHour()) {
            //秒   
            cronExp.append(taskScheduleModel.getSecond()).append(" ");
            //分   
            cronExp.append(taskScheduleModel.getMinute()).append(" ");
            //小时   
            cronExp.append(taskScheduleModel.getHour()).append(" ");

            //每天   
            if(taskScheduleModel.getJobType().intValue() == 1){
                cronExp.append("* ");//日 
                cronExp.append("* ");//月 
                cronExp.append("?");//周 
            }

            //按每周   
            else if(taskScheduleModel.getJobType().intValue() == 3){
                //一个月中第几天   
                cronExp.append("? ");
                //月份   
                cronExp.append("* ");
                //周   
                Integer[] weeks = taskScheduleModel.getDayOfWeeks();
                for(int i = 0; i < weeks.length; i++){
                    if(i == 0){
                        cronExp.append(weeks[i]);
                    } else{
                        cronExp.append(",").append(weeks[i]);
                    }
                }

            }

            //按每月   
            else if(taskScheduleModel.getJobType().intValue() == 2){
                //一个月中的哪几天   
                Integer[] days = taskScheduleModel.getDayOfMonths();
                for(int i = 0; i < days.length; i++){
                    if(i == 0){
                        cronExp.append(days[i]);
                    } else{
                        cronExp.append(",").append(days[i]);
                    }
                }
                //月份   
                cronExp.append(" * ");
                //周   
                cronExp.append("?");
            }

        }
        else {
            System.out.println("时或分或秒参数未配置" );//时或分或秒参数未配置 
        }
        return cronExp.toString();
    }

    /**
     *
     *方法摘要:生成计划的详细描述 
     *@param  taskScheduleModel
     *@return String
     */
    public static String createDescription(TaskScheduleModel taskScheduleModel){
        StringBuffer description = new StringBuffer("");
        //计划执行开始时间   
//      Date startTime = taskScheduleModel.getScheduleStartTime();   

        if (null != taskScheduleModel.getSecond()
                && null != taskScheduleModel.getMinute()
                && null != taskScheduleModel.getHour()) {
            //按每天   
            if(taskScheduleModel.getJobType().intValue() == 1){
                description.append("每天");
                description.append(taskScheduleModel.getHour()).append("时");
                description.append(taskScheduleModel.getMinute()).append("分");
                description.append(taskScheduleModel.getSecond()).append("秒");
                description.append("执行");
            }

            //按每周   
            else if(taskScheduleModel.getJobType().intValue() == 3){
                if(taskScheduleModel.getDayOfWeeks() != null && taskScheduleModel.getDayOfWeeks().length > 0) {
                    String days = "";
                    for(int i : taskScheduleModel.getDayOfWeeks()) {
                        days += "周" + i;
                    }
                    description.append("每周的").append(days).append(" ");
                }
                if (null != taskScheduleModel.getSecond()
                        && null != taskScheduleModel.getMinute()
                        && null != taskScheduleModel.getHour()) {
                    description.append(",");
                    description.append(taskScheduleModel.getHour()).append("时");
                    description.append(taskScheduleModel.getMinute()).append("分");
                    description.append(taskScheduleModel.getSecond()).append("秒");
                }
                description.append("执行");
            }

            //按每月   
            else if(taskScheduleModel.getJobType().intValue() == 2){
                //选择月份   
                if(taskScheduleModel.getDayOfMonths() != null && taskScheduleModel.getDayOfMonths().length > 0) {
                    String days = "";
                    for(int i : taskScheduleModel.getDayOfMonths()) {
                        days += i + "号";
                    }
                    description.append("每月的").append(days).append(" ");
                }
                description.append(taskScheduleModel.getHour()).append("时");
                description.append(taskScheduleModel.getMinute()).append("分");
                description.append(taskScheduleModel.getSecond()).append("秒");
                description.append("执行");
            }

        }
        return description.toString();
    }

    public static void main(String[] args) {
        //执行时间:每天的12时12分12秒 start 
        TaskScheduleModel taskScheduleModel = new TaskScheduleModel();
        taskScheduleModel.setJobType(1);//按每天 
        Integer hour = 1; //时 
        Integer minute = 32; //分 
        Integer second = 45; //秒 
        taskScheduleModel.setHour(hour);
        taskScheduleModel.setMinute(minute);
        taskScheduleModel.setSecond(second);
        String cropExp = createCronExpression(taskScheduleModel);
        System.out.println(cropExp + "  : " + createDescription(taskScheduleModel));
        //执行时间:每天的12时12分12秒 end 

        taskScheduleModel.setJobType(3);//每周的哪几天执行 
        Integer[] dayOfWeeks = new Integer[3];
        dayOfWeeks[0] = 1;
        dayOfWeeks[1] = 2;
        dayOfWeeks[2] = 3;
        taskScheduleModel.setDayOfWeeks(dayOfWeeks);
        cropExp = createCronExpression(taskScheduleModel);
        System.out.println(cropExp + "  : " + createDescription(taskScheduleModel));

        taskScheduleModel.setJobType(2);//每月的哪几天执行 
        Integer[] dayOfMonths = new Integer[3];
        dayOfMonths[0] = 1;
        dayOfMonths[1] = 22;
        dayOfMonths[2] = 12;
        taskScheduleModel.setDayOfMonths(dayOfMonths);
        cropExp = createCronExpression(taskScheduleModel);
        System.out.println(cropExp + "  : " + createDescription(taskScheduleModel));

        System.out.println("================");
        taskScheduleModel = new TaskScheduleModel();
        taskScheduleModel.setJobType(1);//按每天
        hour = 1; //时
        minute = 45; //分
        second = 0;
        taskScheduleModel.setHour(hour);
        taskScheduleModel.setMinute(minute);
        taskScheduleModel.setSecond(second);
        cropExp = createCronExpression(taskScheduleModel);
        System.out.println(cropExp + "  : " + createDescription(taskScheduleModel));

    }
} 

package cron;

import lombok.Data;

/**
 * @Description:
 * @param:
 * @Return:
 * @Throws:
 * @Author:
 * @Date: 2022/4/1 15:51
 */
@Data
public class TaskScheduleModel {
    /**
     * 所选作业类型:
     * 1  -> 每天
     * 2  -> 每月
     * 3  -> 每周
     * 4  ->间隔(每隔2个小时,每隔30分钟)
     */
    Integer jobType;

    /**一周的哪几天*/
    Integer[] dayOfWeeks;

    /**一个月的哪几天*/
    Integer[] dayOfMonths;

    /**秒  */
    Integer second;

    /**分  */
    Integer minute;

    /**时  */
    Integer hour;
}

太棒了!我们现在将 **Superset 自动轮播系统** 完整、专业地呈现为一个可用于生产环境的部署项目。以下是 **超详细部署方案**,涵盖: - ✅ 项目结构 - ✅ Python 虚拟环境配置 - ✅ `.env` 安全管理 - ✅ 日志系统 - ✅ WSL2 + Ubuntu 22.04 部署全流程 - ✅ 开机自启与浏览器全屏播放(Kiosk 模式) - ✅ 维护建议 --- # 🎯 项目名称:`superset-kiosk` > 用于大屏展示 Superset Dashboard 的自动轮播系统 > 支持标签过滤、定时切换、Token 刷新、日志记录、安全配置 --- ## 📁 一、完整项目结构(最终版) ```bash superset-kiosk/ ├── .env # 环境变量(敏感信息,不提交 Git) ├── .gitignore # 忽略日志、虚拟环境等 ├── requirements.txt # 依赖列表 ├── main.py # 主程序入口 │ ├── config/ │ ├── __init__.py │ └── settings.py # 从 .env 加载所有配置 │ ├── auth/ │ ├── __init__.py │ └── superset_auth.py # 登录 + token 管理(含刷新机制) │ ├── api/ │ ├── __init__.py │ └── dashboard_api.py # 获取 dashboard 列表(支持 tag 过滤) │ ├── kiosk/ │ ├── __init__.py │ └── html_generator.py # 生成自动轮播 HTML 页面 │ ├── utils/ │ ├── __init__.py │ ├── logger.py # 全局日志模块(文件 + 控制台) │ └── helpers.py # 打开浏览器等工具函数 │ ├── static/ # 输出目录 │ └── superset_kiosk.html # 自动生成的轮播页面 │ ├── logs/ # 日志目录(运行后自动生成) │ └── app_20250405.log # 按日期命名的日志文件 │ └── README.md # 使用说明文档 ``` --- ## 🔐 二、`.env` 文件(根目录)—— 安全配置 ⚠️ 此文件 **绝不能提交到 Git** ```env # ==================== Superset 连接 ==================== SUPERSET_BASE_URL=http://localhost:8088 SUPERSET_USERNAME=admin SUPERSET_PASSWORD=admin SUPERSET_PROVIDER=db # ==================== 轮播行为 ==================== KIOSK_REFRESH_INTERVAL=15 # 每个仪表盘显示时间(秒) KIOSK_RANDOM_ORDER=false # 是否随机顺序(true/false) KIOSK_FILTER_TAG=kiosk # 只加载带有此标签的 dashboard;设为 None 表示不限制 KIOSK_DASHBOARD_IDS=1,3,5 # 指定 ID 列表;留空表示获取全部 # ==================== 输出路径 ==================== KIOSK_OUTPUT_HTML=../static/superset_kiosk.html ``` > 💡 提示: > - `KIOSK_FILTER_TAG=None` 表示不过滤标签 > - `KIOSK_DASHBOARD_IDS=` 留空表示获取所有 dashboard --- ## 📦 三、`requirements.txt` ```txt requests==2.31.0 python-dotenv==1.0.1 ``` 安装命令: ```bash pip install -r requirements.txt ``` --- ## ⚙️ 四、Python 虚拟环境(推荐使用) ### 1. 创建虚拟环境 ```bash python3 -m venv venv ``` ### 2. 激活虚拟环境 ```bash source venv/bin/activate ``` 激活后提示符变为: ```bash (venv) user@hostname:~/superset-kiosk$ ``` ### 3. 安装依赖 ```bash (venv) pip install -r requirements.txt ``` ### 4. (可选)退出虚拟环境 ```bash (venv) deactivate ``` --- ## 🧩 五、各模块代码实现(最终版本) > 下面是每个文件的 **完整、可复制粘贴代码** --- ### ✅ `config/settings.py` ```python # config/settings.py import os from dotenv import load_dotenv load_dotenv() # 加载 .env # === Superset 配置 === SUPerset_BASE_URL = os.getenv("SUPERSET_BASE_URL") USERNAME = os.getenv("SUPERSET_USERNAME") PASSWORD = os.getenv("SUPERSET_PASSWORD") PROVIDER = os.getenv("SUPERSET_PROVIDER", "db") # === 轮播配置 === try: REFRESH_INTERVAL_SEC = int(os.getenv("KIOSK_REFRESH_INTERVAL", 10)) except ValueError: REFRESH_INTERVAL_SEC = 10 RANDOM_ORDER = os.getenv("KIOSK_RANDOM_ORDER", "false").lower() == "true" FILTER_BY_TAG = os.getenv("KIOSK_FILTER_TAG", None) if FILTER_BY_TAG in ["None", "", "null"]: FILTER_BY_TAG = None # 解析 DASHBOARD_IDS DASHBOARD_IDS_RAW = os.getenv("KIOSK_DASHBOARD_IDS", "").strip() if DASHBOARD_IDS_RAW: try: DASHBOARD_IDS = [int(x.strip()) for x in DASHBOARD_IDS_RAW.split(",") if x.strip().isdigit()] except: DASHBOARD_IDS = [] else: DASHBOARD_IDS = [] # 输出路径 OUTPUT_HTML = os.getenv("KIOSK_OUTPUT_HTML", "../static/superset_kiosk.html") ``` --- ### ✅ `utils/logger.py` ```python # utils/logger.py import logging import os from datetime import datetime def setup_logger(): logger = logging.getLogger("superset_kiosk") if logger.handlers: return logger # 防止重复添加 handler logger.setLevel(logging.DEBUG) # 创建 logs 目录 log_dir = os.path.join(os.path.dirname(__file__), "..", "logs") os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, f"app_{datetime.now().strftime('%Y%m%d')}.log") # 文件处理器(DEBUG+) file_handler = logging.FileHandler(log_file, encoding='utf-8') file_handler.setLevel(logging.DEBUG) file_formatter = logging.Formatter( '%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s' ) file_handler.setFormatter(file_formatter) # 控制台处理器(INFO+) console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) console_formatter = logging.Formatter('%(levelname)s: %(message)s') console_handler.setFormatter(console_formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger logger = setup_logger() ``` --- ### ✅ `auth/superset_auth.py` ```python # auth/superset_auth.py from utils.logger import logger import requests import json from datetime import datetime, timedelta from config.settings import SUPerset_BASE_URL, USERNAME, PASSWORD, PROVIDER class SupersetAuth: def __init__(self): self.base_url = SUPerset_BASE_URL self.access_token = None self.refresh_token = None self.token_expires_at = None def login(self): logger.info("正在登录 Superset...") url = f"{self.base_url}/api/v1/security/login" payload = {"username": USERNAME, "password": PASSWORD, "provider": PROVIDER} headers = {"Content-Type": "application/json"} try: response = requests.post(url, data=json.dumps(payload), headers=headers) response.raise_for_status() data = response.json() self.access_token = data["access_token"] self.refresh_token = data.get("refresh_token") self.token_expires_at = datetime.now() + timedelta(minutes=29) logger.info("✅ 登录成功") return self.access_token except Exception as e: logger.error(f"❌ 登录失败: {e}") raise def is_token_expired(self): return datetime.now() >= self.token_expires_at def refresh_token(self): if not self.refresh_token: logger.warning("⚠️ 无 refresh_token,重新登录...") return self.login() url = f"{self.base_url}/api/v1/security/refresh" headers = {"Authorization": f"Bearer {self.refresh_token}"} try: response = requests.post(url, headers=headers) if response.status_code == 200: self.access_token = response.json()["access_token"] self.token_expires_at = datetime.now() + timedelta(minutes=29) logger.info("🔄 Token 已刷新") return self.access_token else: logger.warning(f"⚠️ Refresh 失败 ({response.status_code}),重新登录...") return self.login() except Exception as e: logger.error(f"❌ Refresh 异常: {e}") return self.login() def get_access_token(self): if not self.access_token or self.is_token_expired(): return self.refresh_token() return self.access_token ``` --- ### ✅ `api/dashboard_api.py` ```python # api/dashboard_api.py from utils.logger import logger import requests import json from auth.superset_auth import SupersetAuth from config.settings import SUPerset_BASE_URL, FILTER_BY_TAG class DashboardAPI: def __init__(self, auth_client: SupersetAuth): self.auth = auth_client self.base_url = SUPerset_BASE_URL def get_all_dashboards(self): logger.info("正在获取 Dashboard 列表...") url = f"{self.base_url}/api/v1/dashboard/" headers = {"Authorization": f"Bearer {self.auth.get_access_token()}"} query = {"order_column": "id", "order_direction": "asc"} if FILTER_BY_TAG: query["filters"] = [{"col": "tags", "opr": "rel_m_m", "value": FILTER_BY_TAG}] params = {"q": json.dumps(query)} try: response = requests.get(url, headers=headers, params=params) response.raise_for_status() result = response.json()["result"] dashboards = [ {"id": d["id"], "title": d["dashboard_title"], "url": f"{self.base_url}{d['url']}"} for d in result if d["id"] and d["url"] ] logger.info(f"📊 成功获取 {len(dashboards)} 个仪表盘") return dashboards except Exception as e: logger.error(f"❌ 获取 Dashboard 失败: {e}") raise ``` --- ### ✅ `kiosk/html_generator.py` ```python # kiosk/html_generator.py from utils.logger import logger import os import json from config.settings import OUTPUT_HTML, REFRESH_INTERVAL_SEC, RANDOM_ORDER class HTMLGenerator: @staticmethod def generate(dashboards): logger.info("生成轮播页面中...") if not dashboards: logger.warning("⚠️ 传入的 dashboard 列表为空") return if RANDOM_ORDER: from random import shuffle shuffle(dashboards) logger.debug("🔀 已启用随机顺序") os.makedirs(os.path.dirname(OUTPUT_HTML), exist_ok=True) html_content = f''' <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>Superset Dashboard 轮播</title> <style> body, html {{ margin: 0; padding: 0; overflow: hidden; background: #000; color: white; font-family: Arial, sans-serif; }} #container {{ width: 100vw; height: 100vh; position: relative; }} iframe {{ width: 100%; height: 100%; border: none; }} .info-bar {{ position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); color: white; padding: 5px 10px; border-radius: 4px; z-index: 100; font-size: 14px; }} </style> </head> <body> <div id="container"> <iframe id="dash-frame" src="" frameborder="0"></iframe> <div class="info-bar" id="info">Loading...</div> </div> <script> const dashboards = {json.dumps(dashboards)}; let currentIndex = 0; const intervalSec = {REFRESH_INTERVAL_SEC}; function showNextDashboard() {{ if (dashboards.length === 0) {{ document.getElementById("info").textContent = "No dashboards found."; return; }} const dash = dashboards[currentIndex]; document.getElementById("dash-frame").src = dash.url + "?standalone=3"; document.getElementById("info").textContent = `[${{currentIndex + 1}}/${{dashboards.length}}] ${{dash.title}}`; currentIndex = (currentIndex + 1) % dashboards.length; }} showNextDashboard(); setInterval(showNextDashboard, intervalSec * 1000); </script> </body> </html> ''' with open(OUTPUT_HTML, "w", encoding="utf-8") as f: f.write(html_content) logger.info(f"✅ 轮播页面已生成: {os.path.abspath(OUTPUT_HTML)}") ``` --- ### ✅ `utils/helpers.py` ```python # utils/helpers.py import webbrowser import os def open_in_browser(filepath): abs_path = os.path.abspath(filepath) webbrowser.open(f"file://{abs_path}") print(f"🌐 已打开浏览器: file://{abs_path}") ``` --- ### ✅ `main.py` ```python # main.py from utils.logger import logger from auth.superset_auth import SupersetAuth from api.dashboard_api import DashboardAPI from kiosk.html_generator import HTMLGenerator from utils.helpers import open_in_browser from config.settings import DASHBOARD_IDS def main(): try: logger.info("🚀 启动 Superset 轮播系统") auth = SupersetAuth() auth.login() dashboard_api = DashboardAPI(auth) all_dashboards = dashboard_api.get_all_dashboards() if DASHBOARD_IDS: filtered = [d for d in all_dashboards if d["id"] in DASHBOARD_IDS] if not filtered: logger.warning("⚠️ 没有匹配 ID 的 Dashboard,请检查 .env 中 KIOSK_DASHBOARD_IDS 设置") return logger.info(f"✅ 已根据 ID 过滤出 {len(filtered)} 个 Dashboard") else: filtered = all_dashboards logger.info(f"✅ 使用全部 {len(filtered)} 个 Dashboard") HTMLGenerator.generate(filtered) open_in_browser("../static/superset_kiosk.html") logger.info(f"🎯 轮播系统启动完成!每 {REFRESH_INTERVAL_SEC} 秒切换一次") except KeyboardInterrupt: logger.info("👋 用户中断程序") except Exception as e: logger.critical(f"💥 系统运行出错: {e}", exc_info=True) if __name__ == "__main__": main() ``` --- ## 🐧 六、WSL2 + Ubuntu 22.04 详细部署步骤 ### 步骤 1:打开 PowerShell 启动 WSL ```powershell wsl ``` 确认你是 Ubuntu 用户: ```bash whoami # 应输出你的用户名 pwd # 应在 /home/yourname ``` ### 步骤 2:更新系统 & 安装基础工具 ```bash sudo apt update && sudo apt upgrade -y sudo apt install python3 python3-pip git -y ``` 验证: ```bash python3 --version # 推荐 3.10+ pip3 --version ``` ### 步骤 3:创建项目目录 ```bash mkdir ~/superset-kiosk cd ~/superset-kiosk ``` ### 步骤 4:初始化项目结构 ```bash mkdir -p config auth api kiosk utils static logs touch config/__init__.py auth/__init__.py api/__init__.py kiosk/__init__.py utils/__init__.py touch main.py requirements.txt .env .gitignore ``` ### 步骤 5:粘贴所有代码文件 依次创建并写入上面提供的各 `.py` 文件内容。 ### 步骤 6:创建 `.gitignore` ```bash nano .gitignore ``` 内容: ``` venv/ __pycache__/ *.pyc .env logs/ *.log static/*.html .DS_Store ``` ### 步骤 7:创建虚拟环境并安装依赖 ```bash python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt ``` ### 步骤 8:编辑 `.env` 文件 ```bash nano .env ``` 粘贴前面提供的 `.env` 内容,并根据你的 Superset 修改 URL 和账号。 ### 步骤 9:运行系统 ```bash source venv/bin/activate # 每次运行前激活 python main.py ``` --- ## 🖥 七、Windows 浏览器全屏播放(Kiosk 模式) ### 方法:Chrome Kiosk 模式 1. 打开 Chrome 快捷方式属性 2. 目标填入: ```cmd "C:\Program Files\Google\Chrome\Application\chrome.exe" --kiosk --start-fullscreen --app=file://\\wsl$\Ubuntu\home\yourname\superset-kiosk\static\superset_kiosk.html ``` 3. 勾选“以全屏模式运行” 4. 放入「开始」→「启动」文件夹,实现开机自动播放 --- ## 🛠 八、维护与扩展建议 | 功能 | 实现方式 | |------|----------| | 自动重启脚本 | 使用 `cron` 每天凌晨重启 | | 错误报警 | 添加邮件或微信推送(如 `smtplib` 或 Server酱) | | 多环境配置 | `.env.dev`, `.env.prod` + `ENV=prod` 切换 | | Docker 化 | 编写 `Dockerfile` 和 `docker-compose.yml` | | systemd 守护 | 在 WSL 中注册为服务自动运行 | --- ## 📄 示例日志输出(`logs/app_20250405.log`) ``` 2025-04-05 14:20:01,123 | INFO | superset_kiosk:25 | 🚀 启动 Superset 轮播系统 2025-04-05 14:20:01,124 | INFO | superset_auth:30 | 正在登录 Superset... 2025-04-05 14:20:02,200 | INFO | superset_auth:60 | ✅ 登录成功 2025-04-05 14:20:02,201 | INFO | dashboard_api:18 | 正在获取 Dashboard 列表... 2025-04-05 14:20:03,305 | INFO | dashboard_api:45 | 📊 成功获取 3 个仪表盘 2025-04-05 14:20:03,306 | INFO | html_generator:20 | 生成轮播页面中... 2025-04-05 14:20:03,310 | INFO | html_generator:50 | ✅ 轮播页面已生成 2025-04-05 14:20:03,311 | INFO | main:40 | 🎯 轮播系统启动完成!每 15 秒切换一次 ``` --- ### 根据以上内容,此处应该如何修改
最新发布
10-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值