Auto.js 入门指南(十八)常见问题与解决方案

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

第十八章:常见问题与解决方案


一、理论讲解:Auto.js 常见问题产生的根源与分类

1.1 为什么 Auto.js 脚本会遇到各种问题?

Auto.js 脚本运行在 Android 系统之上,其复杂性导致了各种问题的出现。理解这些问题的根源,有助于我们更好地定位和解决它们:

  • Android 系统特性:Android 版本碎片化、OEM 厂商的定制、电池优化、权限管理、后台限制等。
  • 应用自身变化:目标应用的 UI 更新、防自动化机制、版本差异等。
  • 脚本开发问题:代码逻辑错误、性能瓶颈、资源管理不当、缺乏错误处理机制等。
  • 设备差异:不同手机型号、屏幕分辨率、硬件性能等。
  • 网络环境:网络不稳定、API 接口变化、数据格式不一致等。

1.2 常见问题分类

我们可以将 Auto.js 脚本开发中遇到的问题大致分为以下几类:

  • 权限相关问题:无障碍服务未开启、存储权限、悬浮窗权限、Root 权限等。
  • UI 自动化问题:控件找不到、点击无效、滑动失败、界面卡顿、弹窗阻碍等。
  • 脚本运行问题:脚本崩溃、ANR (Application Not Responding)、后台被杀、定时任务不准时、多线程死锁等。
  • 数据处理问题:文件读写失败、数据解析错误、网络请求失败、数据存储丢失等。
  • 兼容性问题:不同 Android 版本、不同设备、不同目标应用版本导致的问题。
  • 调试与日志问题:日志不全、无法定位错误、调试困难等。
  • 反自动化问题:被目标应用检测并限制、验证码识别失败等。

二、代码示例:常见问题场景与实战解决方案

2.1 权限问题:请求必要权限

很多 Auto.js 功能需要特定权限,如无障碍服务、悬浮窗、存储权限等。脚本启动时应检查并引导用户授予。

javascript 复制代码
// 场景:检查并请求无障碍服务、悬浮窗、存储权限

/**
 * 检查并请求所有必要权限
 */
function requestNecessaryPermissions() {
    log("检查并请求必要权限...");

    // 1. 请求无障碍服务权限 (必须)
    if (!auto.service) {
        toast("请开启无障碍服务以便运行脚本");
        app.startActivity({ packageName: "com.android.settings", className: "com.android.settings.Settings$AccessibilitySettingsActivity" });
        // 或者使用更直接的方式,但可能需要用户手动确认
        // app.startActivity("android.settings.ACCESSIBILITY_SETTINGS");
        log("等待用户开启无障碍服务...");
        // 可以添加一个循环等待,直到服务开启
        while (!auto.service) {
            sleep(1000);
        }
        log("无障碍服务已开启!");
    }

    // 2. 请求悬浮窗权限 (如果脚本需要悬浮窗)
    if (!floaty.checkPermission()) {
        toast("请授予悬浮窗权限");
        floaty.requestPermission();
        log("等待用户授予悬浮窗权限...");
        while (!floaty.checkPermission()) {
            sleep(1000);
        }
        log("悬浮窗权限已授予!");
    }

    // 3. 请求外部存储读写权限 (如果脚本需要读写文件,Android 6.0+ 需要动态申请)
    // 在 Auto.js 中,files 模块通常会自动处理,但明确请求更保险
    if (device.sdkInt >= 23 && !files.requestStoragePermission()) {
        toast("请授予存储权限");
        log("等待用户授予存储权限...");
        while (!files.requestStoragePermission()) {
            sleep(1000);
        }
        log("存储权限已授予!");
    }
    log("所有必要权限已检查并就绪!");
}

// 在脚本开始时调用
// requestNecessaryPermissions();

2.2 UI 自动化问题:控件找不到与动态 UI 适配

UI 自动化是最容易受应用版本影响的部分。需要更灵活的查找策略和错误处理。

javascript 复制代码
// 场景:查找一个可能变化的按钮,并进行点击

/**
 * 健壮的点击函数:尝试多种查找方式,并重试
 * @param {Array<string>} texts 按钮可能的文本列表
 * @param {string} id 按钮可能的ID
 * @param {string} desc 按钮可能的描述
 * @param {number} timeout 查找超时时间 (毫秒)
 * @returns {boolean} 是否成功点击
 */
function robustClick(texts, id, desc, timeout = 3000) {
    let startTime = Date.now();
    while (Date.now() - startTime < timeout) {
        let btn = null;
        // 1. 尝试按ID查找
        if (id) {
            btn = $(id).findOne(200);
            if (btn) {
                log(`通过ID找到控件: ${id}`);
            }
        }

        // 2. 如果未找到,尝试按文本列表查找
        if (!btn && texts && texts.length > 0) {
            for (let t of texts) {
                btn = text(t).findOne(200);
                if (btn) {
                    log(`通过文本找到控件: ${t}`);
                    break;
                }
            }
        }

        // 3. 如果未找到,尝试按描述查找
        if (!btn && desc) {
            btn = desc(desc).findOne(200);
            if (btn) {
                log(`通过描述找到控件: ${desc}`);
            }
        }

        if (btn) {
            try {
                if (btn.clickable()) {
                    btn.click();
                    log("控件点击成功!");
                    return true;
                } else if (btn.parent() && btn.parent().clickable()) { // 尝试点击父控件
                    btn.parent().click();
                    log("点击父控件成功!");
                    return true;
                } else { // 无法点击,尝试坐标点击
                    click(btn.bounds().centerX(), btn.bounds().centerY());
                    log("通过坐标点击成功!");
                    return true;
                }
            } catch (e) {
                log(`点击控件时发生异常: ${e}`, "ERROR");
            }
        }
        sleep(500); // 每次尝试之间稍作等待
    }
    log("未能在指定时间内找到并点击控件。", "WARN");
    return false;
}

// 示例使用
// robustClick(["确定", "OK"], "confirm_btn", "确认按钮");
// robustClick(["取消", "Cancel"], null, "取消按钮");

2.3 脚本崩溃/ANR 与后台被杀问题

  • ANR (Application Not Responding):通常是主线程长时间阻塞导致。解决方案是将耗时操作移至子线程。
  • 后台被杀:Android 系统为了省电,可能杀死后台进程。使用前台服务或定时任务可以提高脚本存活率。
javascript 复制代码
// 场景:耗时任务在子线程执行,防止ANR;使用前台服务保持后台运行

function longRunningTask() {
    log("开始执行耗时任务...");
    sleep(5000); // 模拟耗时5秒
    log("耗时任务完成。");
}

// 推荐:将耗时操作放到子线程
// threads.start(function(){
//     longRunningTask();
// });
// log("主线程继续执行,耗时任务在后台进行...");

// 使用前台服务 (保持脚本后台运行,避免被系统杀死)
// 需要在 Auto.js Pro 的设置中开启"保持服务运行"或"前台服务"选项
// service.foreground = true; // 设置为前台服务
// log("脚本已设置为前台服务运行。");
// setInterval(() => {
//     log("前台服务心跳...");
// }, 10000); // 每10秒打印一次心跳日志

// 更好的方式是使用 `engines.service` 模块
/*
// main.js (启动脚本)
if (!auto.service) {
    toast("请先启动无障碍服务");
    exit();
}
// 定义一个服务脚本 service.js
// engines.execScriptFile("service.js");

// service.js (服务脚本内容)
// setInterval(() => {
//     log("服务正在运行...");
//     // 在这里执行一些轻量级后台任务
// }, 5000);
// service.setOnDestroy(() => {
//     log("服务被销毁");
// });
*/

2.4 数据处理问题:JSON 解析与文件操作

javascript 复制代码
// 场景:安全地读写 JSON 配置文件

const CONFIG_FILE = "/sdcard/my_app_config.json";

/**
 * 安全读取 JSON 配置文件
 * @returns {object} 解析后的配置对象,或空对象
 */
function readJsonConfig() {
    if (!files.exists(CONFIG_FILE)) {
        log("配置文件不存在,创建默认配置。", "WARN");
        return {};
    }
    try {
        let content = files.read(CONFIG_FILE);
        return JSON.parse(content);
    } catch (e) {
        log(`读取或解析配置文件失败: ${e}`, "ERROR");
        return {};
    }
}

/**
 * 安全写入 JSON 配置文件
 * @param {object} configData 要写入的配置数据
 * @returns {boolean} 是否成功写入
 */
function writeJsonConfig(configData) {
    try {
        let content = JSON.stringify(configData, null, 4); // 格式化输出,方便阅读
        files.write(CONFIG_FILE, content);
        log("配置文件写入成功。");
        return true;
    } catch (e) {
        log(`写入配置文件失败: ${e}`, "ERROR");
        return false;
    }
}

// 示例使用
// let config = readJsonConfig();
// config.username = "new_user";
// config.lastRun = Date.now();
// writeJsonConfig(config);
// log("当前配置:" + JSON.stringify(readJsonConfig()));

2.5 网络请求失败与错误重试

javascript 复制代码
// 场景:带重试机制的网络请求

/**
 * 带重试机制的 HTTP GET 请求
 * @param {string} url 请求URL
 * @param {number} retries 重试次数
 * @param {number} delay 重试间隔 (毫秒)
 * @returns {object|null} 响应对象,或 null
 */
function reliableHttpGet(url, retries = 3, delay = 2000) {
    for (let i = 0; i < retries; i++) {
        log(`尝试GET请求:${url} (第 ${i + 1} 次尝试)`);
        try {
            let res = http.get(url, { timeout: 5000 }); // 设置超时
            if (res.statusCode == 200) {
                log("请求成功!");
                return res;
            } else {
                log(`请求失败,状态码:${res.statusCode}。` + (i < retries - 1 ? `等待 ${delay / 1000} 秒后重试...` : ""), "WARN");
            }
        } catch (e) {
            log(`请求异常:${e}。` + (i < retries - 1 ? `等待 ${delay / 1000} 秒后重试...` : ""), "ERROR");
        }
        if (i < retries - 1) {
            sleep(delay);
        }
    }
    log("多次重试后请求仍失败。", "FATAL");
    return null;
}

// 示例使用
// var response = reliableHttpGet("https://www.example.com/api/data", 5, 3000);
// if (response) {
//     log("成功获取到数据:" + response.body.string().substring(0, 100) + "...");
// } else {
//     log("未能获取数据。");
// }

2.6 反自动化检测与规避

很多应用会检测自动化行为。规避策略包括:

  • 模拟真实用户行为:引入随机延时、随机滑动距离、随机点击位置偏差等。
  • 使用图像识别:绕过控件 ID/文本的变化,直接根据图像特征判断。
  • 降低操作频率:避免在短时间内进行大量重复操作。
  • IP 代理:对于频繁的网络请求,更换 IP 地址。
  • 模拟设备信息:修改设备型号、IMEI 等信息(Auto.js Pro 部分支持)。
javascript 复制代码
// 场景:模拟更真实的用户点击

/**
 * 模拟真实用户点击:带随机偏移和随机延时
 * @param {UiObject} uiObject 目标控件
 */
function randomClick(uiObject) {
    if (!uiObject) return;
    let bounds = uiObject.bounds();
    let x = bounds.centerX() + random(-5, 5); // x轴随机偏移
    let y = bounds.centerY() + random(-5, 5); // y轴随机偏移
    click(x, y);
    sleep(random(500, 1500)); // 随机等待0.5到1.5秒
    log(`模拟随机点击 (${x}, ${y})`);
}

// 示例使用
// let btn = text("立即领取").findOne();
// if (btn) {
//     randomClick(btn);
// }

2.7 调试困难与日志技巧

  • 善用 log() / console.log():多在关键逻辑处打印日志,追踪变量值、函数执行流程。
  • 日志分级:使用不同级别(INFO, WARN, ERROR, DEBUG)的日志。
  • 文件日志:将日志输出到文件,方便长期存储和分析。
  • Auto.js Pro 调试器:利用其断点、单步执行、变量查看等功能。
  • Android Studio Logcat:配合 ADB 查看更底层的系统日志。
javascript 复制代码
// 场景:详细日志记录与错误追踪

const DEBUG_MODE = true; // 控制是否输出DEBUG日志
const LOG_FILE_PATH = "/sdcard/script_debug.log";

function debugLog(msg) {
    if (DEBUG_MODE) {
        files.append(LOG_FILE_PATH, `[DEBUG] ${new Date().toLocaleString()} : ${msg}\n`);
        console.log(`[DEBUG] ${msg}`);
    }
}

function infoLog(msg) {
    files.append(LOG_FILE_PATH, `[INFO] ${new Date().toLocaleString()} : ${msg}\n`);
    log(`[INFO] ${msg}`);
}

function errorLog(msg, error) {
    let errMsg = `[ERROR] ${new Date().toLocaleString()} : ${msg}`;+
    if (error) {
        errMsg += `\nStackTrace: ${error.stack}`; // 记录堆栈信息
    }
    files.append(LOG_FILE_PATH, errMsg + "\n");
    log(errMsg, "e"); // 使用 'e' 级别在控制台显示红色
}

// 示例使用
// infoLog("脚本开始执行");
// let x = 10;
// debugLog("变量 x 的值为: " + x);
// try {
//     throw new Error("模拟一个错误");
// } catch (e) {
//     errorLog("捕获到错误!", e);
// }
// infoLog("脚本执行结束");

三、实战项目:脚本自愈与错误恢复模块

3.1 项目需求

开发一个能够自动处理常见异常并尝试恢复脚本运行的模块。当脚本在执行过程中遇到特定错误(如控件找不到、网络请求失败、意外弹窗)时,能自动进行错误识别、记录、尝试恢复,而不是直接崩溃退出。

3.2 项目结构

plaintext 复制代码
autojs-self-healing-demo/
├── main.js                     # 主入口脚本,使用自愈模块
├── modules/
│   ├── errorRecovery.js        # 核心错误恢复与处理逻辑
│   └── logger.js               # 日志模块 (复用)
├── logs/
│   └── app.log
└── README.md

3.3 logger.js 日志模块 (复用,与前面章节相同)

javascript 复制代码
// modules/logger.js
function log(msg, level = "INFO") {
    let line = `[${new Date().toLocaleTimeString()}][${level}] ${msg}`;
    files.append("../logs/app.log", line + "\n");
    console.log(line); // 同时输出到控制台
}

module.exports = { log };

3.4 errorRecovery.js 错误恢复模块

javascript 复制代码
// modules/errorRecovery.js
const logger = require('./logger.js');

const MAX_RETRY_COUNT = 3; // 最大重试次数

/**
 * 尝试处理常见的运行时异常并进行恢复
 * @param {Function} action 需要执行的业务逻辑函数
 * @param {Object} options 配置项
 * @param {Array<string>} [options.retryKeywords=[]] 遇到这些关键词的错误时进行重试
 * @param {Array<string>} [options.popupKeywords=[]] 意外弹窗的关键词,自动关闭
 * @param {Function} [options.onRecovery=null] 恢复操作前的回调函数
 * @param {number} [options.maxRetries=MAX_RETRY_COUNT] 最大重试次数
 * @param {number} [options.retryDelay=2000] 每次重试的间隔 (毫秒)
 * @returns {boolean} 业务逻辑是否最终成功执行
 */
function executeWithRecovery(action, options = {}) {
    const retryKeywords = options.retryKeywords || [];
    const popupKeywords = options.popupKeywords || [];
    const onRecovery = options.onRecovery || null;
    const maxRetries = options.maxRetries || MAX_RETRY_COUNT;
    const retryDelay = options.retryDelay || 2000;

    let currentRetry = 0;

    while (currentRetry <= maxRetries) {
        try {
            logger.log(`尝试执行业务逻辑 (第 ${currentRetry + 1} 次)...`);
            action(); // 执行核心业务逻辑
            logger.log("业务逻辑执行成功!");
            return true;
        } catch (e) {
            logger.log(`业务逻辑执行失败,异常信息:${e.message}`, "ERROR");
            logger.log(`堆栈信息:${e.stack}`, "ERROR");

            // 1. 尝试处理意外弹窗
            let popupHandled = false;
            for (let keyword of popupKeywords) {
                let uiObject = text(keyword).findOne(500) || desc(keyword).findOne(500);
                if (uiObject) {
                    try {
                        uiObject.click();
                        logger.log(`自动关闭意外弹窗:'${keyword}'`, "INFO");
                        popupHandled = true;
                        sleep(1000); // 等待弹窗消失
                        break;
                    } catch (clickErr) {
                        logger.log(`点击弹窗失败 '${keyword}': ${clickErr}`, "ERROR");
                    }
                }
            }
            if (popupHandled) {
                logger.log("弹窗已处理,准备重试。");
                sleep(retryDelay); // 弹窗处理后稍微等待
                currentRetry++;
                continue; // 处理完弹窗后立即重试当前操作
            }

            // 2. 检查是否需要重试 (基于错误信息关键词)
            let shouldRetry = false;
            for (let keyword of retryKeywords) {
                if (e.message.includes(keyword)) {
                    shouldRetry = true;
                    break;
                }
            }

            if (shouldRetry && currentRetry < maxRetries) {
                logger.log(`检测到可重试错误,进行第 ${currentRetry + 1} 次重试...`, "WARN");
                if (onRecovery) {
                    logger.log("执行恢复前回调...");
                    onRecovery(); // 执行自定义恢复操作,例如返回上一页、刷新界面
                }
                sleep(retryDelay); // 等待重试间隔
                currentRetry++;
            } else {
                logger.log("达到最大重试次数或非可重试错误,脚本终止。", "FATAL");
                return false; // 无法恢复,直接退出
            }
        }
    }
    logger.log("达到最大重试次数,业务逻辑未能成功执行。", "FATAL");
    return false;
}

module.exports = { executeWithRecovery };

3.5 main.js 主入口脚本

javascript 复制代码
// main.js
const errorRecovery = require('./modules/errorRecovery.js');
const logger = require('./modules/logger.js');

logger.log("脚本启动:开始执行带自愈功能的业务逻辑...");

let step = 0;

/**
 * 模拟一个会抛出错误的业务逻辑
 */
function businessLogic() {
    step++;
    logger.log(`当前执行到业务逻辑第 ${step} 步。`);
    if (step === 1) {
        // 模拟一个控件找不到的错误
        throw new Error("控件'下一步'未找到!");
    } else if (step === 2) {
        // 模拟一个意外弹窗 (实际环境中会通过 UI 查找识别)
        logger.log("模拟出现一个意外弹窗:'系统提示:有新版本!'");
        // 这里我们手动模拟一个UI弹窗,实际中可能是APP界面的弹窗
        threads.start(function(){
            "ui";
            ui.layout(<frame><button id="updateBtn" text="立即更新" w="120dp" h="50dp"/><button id="cancelBtn" text="以后再说" w="120dp" h="50dp"/></frame>);
            ui.cancelBtn.on("click",()=>{ ui.finish(); });
            setTimeout(() => ui.finish(), 3000); // 3秒后自动消失
        });
        throw new Error("出现意外弹窗!"); // 抛出错误,让恢复模块处理
    } else if (step === 3) {
        // 模拟一个网络请求失败
        throw new Error("网络请求失败:连接超时!");
    } else if (step === 4) {
        logger.log("最终业务逻辑成功执行!");
        return true;
    }
    return false;
}

/**
 * 自定义恢复操作:例如返回、刷新等
 */
function customRecoveryAction() {
    logger.log("执行自定义恢复操作:返回上一页或刷新界面...");
    back(); // 模拟返回上一页
    sleep(1000);
    // 也可以是刷新当前界面或重新初始化某些状态
}

// 执行业务逻辑并启用错误恢复机制
let success = errorRecovery.executeWithRecovery(businessLogic, {
    retryKeywords: ["未找到", "失败", "超时"], // 遇到这些关键词的错误消息时重试
    popupKeywords: ["以后再说", "取消", "关闭", "我知道了", "立即更新"], // 遇到这些关键词的弹窗时自动点击
    onRecovery: customRecoveryAction, // 每次重试前的回调
    maxRetries: 5, // 允许更多的重试次数
    retryDelay: 1500 // 每次重试等待1.5秒
});

if (success) {
    logger.log("恭喜!脚本成功克服困难完成任务!", "INFO");
} else {
    logger.log("很遗憾,脚本在多次尝试后仍未能成功完成任务。", "FATAL");
}

logger.log("脚本执行完毕。");
exit();

3.6 运行效果与分析

  • 运行 main.js,脚本会模拟执行 businessLogic() 函数。
  • businessLogic() 内部,我们模拟了控件找不到、意外弹窗和网络请求失败三种错误情况。
  • errorRecovery.js 模块会捕获这些异常,根据配置的 retryKeywordspopupKeywords 进行智能判断。
    • 当遇到"控件未找到"或"网络请求失败"时,会根据 retryKeywords 尝试重试,并调用 customRecoveryAction 进行恢复。
    • 当模拟的"意外弹窗"出现时,即使 businessLogic 抛出错误,errorRecovery 也会优先识别并关闭弹窗,然后继续重试。
  • 日志会详细记录每次尝试、错误信息、恢复操作和最终结果。
  • 这个项目展示了如何构建一个通用的自愈模块,提高脚本的容错性和健壮性,减少因小问题导致的脚本中断。

四、常见问题与解决方案总结

问题类型 常见具体问题 解决方案
权限问题 无障碍服务未开启、悬浮窗权限未授权 脚本启动时主动 requestNecessaryPermissions(),引导用户开启。
UI 自动化问题 控件找不到、点击无效、UI 变化失效 id() 优先;使用 text()/desc()/className() 结合;局部查找;waitForExists();健壮点击函数 (robustClick);图像识别辅助。
脚本运行问题 脚本卡顿/ANR、后台被杀、定时任务不准时 耗时操作移至子线程;使用前台服务 (service.foreground = true);合理设置定时器 (setInterval);检查电池优化设置。
数据处理问题 文件读写失败、JSON 解析错误、数据丢失 使用 try-catch 捕获异常;检查文件路径和权限;安全地读写 JSON (readJsonConfig/writeJsonConfig);使用数据库。
网络请求问题 请求超时、请求失败、数据格式错误 设置超时时间;实现重试机制 (reliableHttpGet);检查网络连接;处理 HTTP 状态码;验证 JSON/XML 响应格式。
兼容性问题 不同 Android 版本、手机型号、应用版本适配 使用 device.sdkInt 进行版本判断;测试不同设备;使用更通用的 UI 查找方法;定期更新脚本。
反自动化检测 被目标应用识别、验证码、风控系统 模拟真实用户行为 (randomClick);引入随机延时;使用图像识别/OCR;IP 代理;降低操作频率。
调试与日志问题 难以定位错误、日志信息不足 善用 log()/console.log();日志分级 (debugLog/infoLog/errorLog);写入文件日志;利用 Auto.js Pro 调试器。
内存泄漏 脚本长时间运行导致内存占用过高 及时释放图片 (recycle())、线程、定时器等资源;避免创建大量临时对象。
多线程安全 数据竞争、死锁 对共享资源使用 threads.lock() 或其他同步机制;避免在子线程直接操作 UI。

五、性能优化建议 (与问题解决关联)

性能优化和问题解决是相辅相成的。许多常见问题的解决方案本身就是性能优化的体现。

  • UI 查找优化:精准定位控件,减少遍历,避免无效查找,是解决"控件找不到"和"脚本卡顿"的关键。
  • 异步处理:将耗时操作放到子线程,是避免 ANR 和提升响应速度的有效手段。
  • 资源管理:及时释放内存(特别是图片资源),是防止 OOM 的重要保障。
  • 错误处理与重试:健壮的错误处理机制和重试策略,提高了脚本的容错性,减少因网络波动或UI变化导致的失败。
  • 日志精简:控制日志输出,既能提高性能,又能让关键错误更易于发现。
  • 模拟真实操作:这不仅是规避反自动化检测的手段,也通过引入适当延时,避免了因操作过快而导致的问题。

通过综合运用这些策略,我们可以编写出更加稳定、高效和健壮的 Auto.js 自动化脚本。


感谢大家阅读到这里,本系列的 Auto.js 学习已经结束,希望对大家有所帮助。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
Xf3n1an1 小时前
html语法
前端·html
张拭心1 小时前
亚马逊 AI IDE Kiro “狙击”Cursor?实测心得
前端·ai编程
烛阴1 小时前
为什么你的Python项目总是混乱?层级包构建全解析
前端·python
CYRUS_STUDIO1 小时前
深入 Android syscall 实现:内联汇编系统调用 + NDK 汇编构建
android·操作系统·汇编语言
@大迁世界2 小时前
React 及其生态新闻 — 2025年6月
前端·javascript·react.js·前端框架·ecmascript
红尘散仙2 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust
死也不注释2 小时前
【第一章编辑器开发基础第一节绘制编辑器元素_6滑动条控件(6/7)】
android·编辑器
新酱爱学习2 小时前
前端海报生成的几种方式:从 Canvas 到 Skyline
前端·javascript·微信小程序
袁煦丞3 小时前
把纸堆变数据流!Paperless-ngx让文件管理像打游戏一样爽:cpolar内网穿透实验室第539个成功挑战
前端·程序员·远程工作
慧慧吖@3 小时前
关于两种网络攻击方式XSS和CSRF
前端·xss·csrf