网站用户行为分析项目之会话切割(四)=> 代码重构

本文详细介绍了如何在Spark项目中重构输出代码,包括抽离输出逻辑、重构输出路径和文件类型,通过创建接口和实现不同格式输出,提高代码灵活性和可维护性。

0x00 文章内容

  1. 实现输出代码的重构
  2. 校验结果

0x01 实现输出代码的重构

1. 抽离输出代码

a. 因为SessionCutETL里的main方法写了比较多的代码,此时我们可以将第6步骤的输出代码进行抽离,全选,选中Refactor=>Extract=>Method
在这里插入图片描述
b. 我们这里选择第一个在这里插入图片描述
c. 填写方法名,点击OK
在这里插入图片描述
即可发现已经抽离出了方法。

2. 重构输出路径

a. 下面的代码中路径都是写死的,而且出现了共同的路径,我们可以进行统一

    val trackerLogOutputPath = "data/output/trackerLog"
    val trackerSessionOutputPath = "data/output/trackerSession"

修改如下:

    writeOutputData(sc,"data/output", parsedLogRDD, cookieLabeledSessionRDD)
    private def writeOutputData(sc: SparkContext, baseOutputPath: String, parsedLogRDD: RDD[TrackerLog], cookieLabeledSessionRDD: RDD[TrackerSession]) = {
    val trackerLogOutputPath = s"${baseOutputPath}/trackerLog"
    val trackerSessionOutputPath = s"${baseOutputPath}/trackerSession"

我们暂且这样重构先,此时需要重新执行一下代码,看一下改后是否能执行。

d. 执行验证,执行会报错,表示路径的文件已经存在,此时可以手动删除再执行,这里下一步是在代码中实现删除。

Exception in thread "main" org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory data/output/trackerLog already exists

e. 添加判断路径代码

    val fileSystem = FileSystem.get(sc.hadoopConfiguration)
    val path = new Path(trackerLogOutputPath)
    if (fileSystem.exists(path)) {
      fileSystem.delete(path, true)
    }

写完之后再抽离出去,取名deletePathIfExists方法。

f. 此时再重新执行代码,发现代码没有报错,也能得到想要的结果。

3. 重构输出文件类型

目前的情况是以Parquet的形式保存着,如果此时如果需求发生变化了,或者有其他格式的需求,如保存成TextFile格式。就要重新改代码了,如果需求又变了,或者写成其他组件,又要重新改,重新然后打包,重新然后上传,这样非常麻烦。试想,如果此时将需要修改的代码抽象成一个接口,就会大大方便了。

a. 本优化内容比较多,涉及两个类,代码量及过程比较多:
在这里插入图片描述
b. OutputComponent完整代码如下:

package com.shaonaiyi.session

import com.shaonaiyi.spark.session.{TrackerLog, TrackerSession}
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.parquet.avro.{AvroParquetOutputFormat, AvroWriteSupport}
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

/**
  * @Auther: shaonaiyi@163.com
  * @Date: 2019/12/30 21:09
  * @Description: 抽象输出文件组件
  */
trait OutputComponent {

  def writeOutputData(sc: SparkContext, baseOutputPath: String,
                      parsedLogRDD: RDD[TrackerLog],
                      cookieLabeledSessionRDD: RDD[TrackerSession]) = {
    deletePathIfExists(sc, baseOutputPath)
  }

  private def deletePathIfExists(sc: SparkContext, trackerLogOutputPath: String) = {
    val fileSystem = FileSystem.get(sc.hadoopConfiguration)
    val path = new Path(trackerLogOutputPath)
    if (fileSystem.exists(path)) {
      fileSystem.delete(path, true)
    }
  }

}

object OutputComponent {
  def fromOutputFileType(fileType: String) = {
    if (fileType.equals("parquet")) {
      new ParquetFileOutput
    } else {
      new TextFileOutput
    }
  }
}

/**
  * 写Parquet格式文件
  */
class ParquetFileOutput extends OutputComponent {
  override def writeOutputData(sc: SparkContext, baseOutputPath: String,
                               parsedLogRDD: RDD[TrackerLog],
                               cookieLabeledSessionRDD: RDD[TrackerSession]): Unit = {

    super.writeOutputData(sc, baseOutputPath, parsedLogRDD, cookieLabeledSessionRDD)

    //6、保存数据
    //6.1、保存TrackerLog,对应的是parsedLogRDD
    val trackerLogOutputPath = s"${baseOutputPath}/trackerLog"

    AvroWriteSupport.setSchema(sc.hadoopConfiguration, TrackerLog.SCHEMA$)
    parsedLogRDD.map((null, _)).saveAsNewAPIHadoopFile(trackerLogOutputPath,
      classOf[Void], classOf[TrackerLog], classOf[AvroParquetOutputFormat[TrackerLog]]
    )
    //6.2、保存TrackerSession,对应的是cookieLabeledSessionRDD
    val trackerSessionOutputPath = s"${baseOutputPath}/trackerSession"

    AvroWriteSupport.setSchema(sc.hadoopConfiguration, TrackerSession.SCHEMA$)
    cookieLabeledSessionRDD.map((null, _)).saveAsNewAPIHadoopFile(trackerSessionOutputPath,
      classOf[Void], classOf[TrackerSession], classOf[AvroParquetOutputFormat[TrackerSession]]
    )
  }

}

/**
  * 写TextFile格式文件
  */
class TextFileOutput extends OutputComponent {
  override def writeOutputData(sc: SparkContext, baseOutputPath: String,
                               parsedLogRDD: RDD[TrackerLog],
                               cookieLabeledSessionRDD: RDD[TrackerSession]): Unit = {

    super.writeOutputData(sc, baseOutputPath, parsedLogRDD, cookieLabeledSessionRDD)

    //6、保存数据
    //6.1、保存TrackerLog,对应的是parsedLogRDD
    val trackerLogOutputPath = s"${baseOutputPath}/trackerLog"

    parsedLogRDD.saveAsTextFile(trackerLogOutputPath)

    //6.2、保存TrackerSession,对应的是cookieLabeledSessionRDD
    val trackerSessionOutputPath = s"${baseOutputPath}/trackerSession"

    cookieLabeledSessionRDD.saveAsTextFile(trackerSessionOutputPath)

  }

}

编写思路:先写OutputComponent接口,然后写ParquetFileOutput类继承OutputComponent,最后写一个伴生类OutputComponent以方便调用,最后进行代码优化,将deletePathIfExists抽离出来。

代码讲解:

  1. OutputComponent是一个Trait类型,属于接口抽象类,目的将输出格式接口化,原本是只有一个Parquet格式的,现在再添加了一个TextFile格式,实现的功能其实就是与输出Parquet格式的代码相类似。ParquetFileOutputTextFileOutput均继承此类,所以需要实现writeOutputData方法,写好之后,需要再写一个伴生类来调用,并且判断输入的是哪种类型;除此之外,还简化了deletePathIfExists方法,统一用super进行了了调用。
  2. saveAsNewAPIHadoopFile需要的参数是key-value类型,所以需要转成key-value先。

c. SessionCutETL完整代码如下:

package com.shaonaiyi.session

import com.shaonaiyi.spark.session.{TrackerLog, TrackerSession}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @Auther: shaonaiyi@163.com
  * @Date: 2019/9/12 10:09
  * @Description: 会话切割的程序主入口
  */
object SessionCutETL {

  private val logTypeSet = Set("pageview", "click")

  def main(args: Array[String]): Unit = {

    var conf = new SparkConf()
    conf.setAppName("SessionCutETL")
    conf.setMaster("local")
    conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    var sc = new SparkContext(conf)

    //网站域名标签数据,此处只是演示,其实可以存放在数据库里
    val domainLabelMap = Map(
      "www.baidu.com" -> "level1",
      "www.taobao.com" -> "level2",
      "jd.com" -> "level3",
      "youku.com" -> "level4"
    )

    //广播
    val domainLabelMapB = sc.broadcast(domainLabelMap)

//    sc.setLogLevel("ERROR")

    // 1、加载日志源数据
    val rawRDD: RDD[String] = sc.textFile("data/rawdata/visit_log.txt")
//    rawRDD.collect().foreach(println)

    //2、解析rawRDD中每一行日志源数据
//    val parsedLogRDD: RDD[Option[TrackerLog]] = rawRDD.map( line => RawLogParserUtil.parse(line))
//    val parsedLogRDD1: RDD[TrackerLog] = rawRDD.flatMap( line => RawLogParserUtil.parse(line))
    val parsedLogRDD: RDD[TrackerLog] = rawRDD.flatMap( line => RawLogParserUtil.parse(line))
    .filter(trackerLog => logTypeSet.contains(trackerLog.getLogType.toString))
//    parsedLogRDD.collect().foreach(println)
//    parsedLogRDD1.collect().foreach(println)

    //3、按照cookie进行分组
   val cookieGroupRDD: RDD[(String, Iterable[TrackerLog])] = parsedLogRDD.groupBy(trackerLog => trackerLog.getCookie.toString)
//   cookieGroupRDD.collect().foreach(println)

    //4、按user进行分组
    val sessionRDD: RDD[(String, TrackerSession)] = cookieGroupRDD.flatMapValues { case iter =>

      //处理每个user的日志
      val processor = new OneUserTrackerLogsProcessor(iter.toArray)
        processor.buildSessions(domainLabelMapB.value)
    }

    //5、给会话的cookie打标签
    val cookieLabelRDD: RDD[(String, String)] = sc.textFile("data/cookie_label.txt").map { case line =>
      val temp = line.split("\\|")
      (temp(0), temp(1)) // (cookie, cookie_label)
    }

    val joinRDD: RDD[(String,(TrackerSession, Option[String]))] = sessionRDD.leftOuterJoin(cookieLabelRDD)

    val cookieLabeledSessionRDD: RDD[TrackerSession] = joinRDD.map {
      case (cookie, (session, cookieLabelOpt)) =>
        if (cookieLabelOpt.nonEmpty) {
          session.setCookieLabel(cookieLabelOpt.get)
        } else {
          session.setCookieLabel("-")
        }
        session
    }

    //text & parquet
    OutputComponent.fromOutputFileType("text").writeOutputData(sc,"data/output", parsedLogRDD, cookieLabeledSessionRDD)

    sc.stop()
  }

}

代码讲解:删除原先的输出格式的代码,用OutputComponent调用来实现。

0x02 校验结果

1. 生成不同的输出文件类型

a. 执行SessionCutETL类,查看文件的输出类型,此时生成了TextFile格式文件。
在这里插入图片描述
b. 修改此行代码的"text"为parquet,重新执行,查看结果,此时生成了Parquet格式文件。

    //text & parquet
    OutputComponent.fromOutputFileType("parquet").writeOutputData(sc,"data/output", parsedLogRDD, cookieLabeledSessionRDD)

在这里插入图片描述

0xFF 总结

  1. 代码重构是一项非常重要的技能,增强代码的可读性。
  2. 下一篇文章还会继续优化,请留意本博客,关注、评论、加点赞。
  3. 网站用户行为分析项目系列:
    网站用户行为分析项目之会话切割(一)
    网站用户行为分析项目之会话切割(二)
    网站用户行为分析项目之会话切割(三)
    网站用户行为分析项目之会话切割(四)
    网站用户行为分析项目之会话切割(五)
    网站用户行为分析项目之会话切割(六)

作者简介:邵奈一
全栈工程师、市场洞察者、专栏编辑
| 公众号 | 微信 | 微博 | 优快云 | 简书 |

福利:
邵奈一的技术博客导航
邵奈一 原创不易,如转载请标明出处。


<!DOCTYPE html> <html> <head> <title>终极版 JS 混淆解密工具</title> <style> /* 保留原有样式并优化布局 */ .container { max-width: 1400px; margin: 0 auto; padding: 20px; } .alert { padding: 10px 15px; margin: 10px 0; border-radius: 4px; display: none; } .alert.error { background: #fee; color: #d32f2f; } .alert.success { background: #efe; color: #2e7d32; } .alert.warning { background: #ffe; color: #f57c00; } .alert.show { display: block; } .config-panel { background: #f5f5f5; padding: 15px; border-radius: 4px; margin-bottom: 15px; } .config-group { display: flex; gap: 20px; flex-wrap: wrap; } .config-item { display: flex; align-items: center; gap: 5px; } .buttons { margin: 15px 0; display: flex; gap: 12px; } button { padding: 10px 20px; cursor: pointer; border: none; border-radius: 4px; background: #2196f3; color: white; transition: background 0.3s; } button:hover { background: #0b7dda; } button:disabled { background: #bbb; cursor: not-allowed; } .code-container { display: flex; gap: 20px; margin-top: 10px; } .code-box { flex: 1; display: flex; flex-direction: column; } textarea { width: 100%; height: 400px; padding: 12px; font-family: 'Consolas', monospace; border: 1px solid #ddd; border-radius: 4px; resize: vertical; } label { margin-bottom: 8px; font-weight: 500; } .progress { height: 4px; background: #eee; border-radius: 2px; margin: 10px 0; overflow: hidden; display: none; } .progress.show { display: block; } .progress-bar { height: 100%; background: #4caf50; width: 0%; transition: width 0.3s; } </style> </head> <body> <div class="container"> <div class="alert" id="alert"></div> <div class="config-panel"> <h4>混淆配置</h4> <div class="config-group"> <div class="config-item"> <input type="checkbox" id="mangleVars" checked> <label for="mangleVars">混淆变量名</label> </div> <div class="config-item"> <input type="checkbox" id="mangleFuncs" checked> <label for="mangleFuncs">混淆函数名</label> </div> <div class="config-item"> <input type="checkbox" id="mangleClasses" checked> <label for="mangleClasses">混淆类名</label> </div> <div class="config-item"> <input type="checkbox" id="flattenControl" checked> <label for="flattenControl">控制流扁平化</label> </div> <div class="config-item"> <input type="checkbox" id="antiDebug" checked> <label for="antiDebug">防调试保护</label> </div> </div> </div> <div class="buttons"> <button onclick="handleObfuscate()" id="obfBtn">混淆代码</button> <button onclick="handleDeobfuscate()" id="deobfBtn">解密代码</button> <button onclick="copyResult()">复制结果</button> </div> <div class="progress"> <div class="progress-bar" id="progressBar"></div> </div> <div class="code-container"> <div class="code-box"> <label for="inputCode">输入代码:</label> <textarea id="inputCode" placeholder="输入要处理的JS代码...">// 测试用例:模板字符串、try/catch、循环 const user = "World"; const str = `Hello ${user}! Current time: ${new Date().getHours()}`; try { let a = 10; if (a > 5) throw new Error("测试异常"); } catch (e) { let a = 20; // 应与try块中的a区分 console.log(`捕获异常: ${e.message}, a=${a}`); } // 循环结构测试 for (let i = 0; i < 3; i++) { console.log(`循环第${i}次`); } while (Math.random() > 0.5) { console.log("随机循环"); }</textarea> </div> <div class="code-box"> <label for="outputCode">处理结果:</label> <textarea id="outputCode" placeholder="处理结果将显示在这里..." spellcheck="false"></textarea> </div> </div> </div> <!-- 引入AST解析库 --> <script src="https://cdn.jsdelivr.net/npm/acorn@8.10.0/dist/acorn.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/walkes@0.2.0/walkes.min.js"></script> <!-- Web Worker 脚本 --> <script id="worker-script" type="javascript/worker"> // 导入所需库(Worker内通过importScripts加载) importScripts( 'https://cdn.jsdelivr.net/npm/acorn@8.10.0/dist/acorn.min.js', 'https://cdn.jsdelivr.net/npm/walkes@0.2.0/walkes.min.js', 'https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js' ); // 模块封装:字符串工具 const StringUtil = (() => { class _StringUtil { /** * 处理模板字符串,分离静态和动态部分 * @param {string} template 模板字符串内容(不含外层`) * @returns {Array} 格式: [{type: 'static', value: 'xxx'}, {type: 'dynamic', value: 'expr'}] */ static parseTemplate(template) { const parts = []; let currentStatic = ''; let inExpression = false; let braceDepth = 0; for (let i = 0; i < template.length; i++) { // 检测模板表达式开始 ${ if (!inExpression && template[i] === '$' && template[i+1] === '{') { if (currentStatic) { parts.push({ type: 'static', value: currentStatic }); currentStatic = ''; } inExpression = true; i++; // 跳过{ continue; } // 检测模板表达式结束 } if (inExpression) { if (template[i] === '{') braceDepth++; if (template[i] === '}') { if (braceDepth === 0) { inExpression = false; continue; } braceDepth--; } parts.push({ type: 'dynamic', value: template[i] }); continue; } currentStatic += template[i]; } if (currentStatic) { parts.push({ type: 'static', value: currentStatic }); } return parts; } /** * 转义字符串中的引号 * @param {string} str 原始字符串 * @param {string} quote 目标引号 * @returns {string} 转义后字符串 */ static escapeQuotes(str, quote) { const escapeChar = quote === '"' ? '\\"' : "\\'"; return str.replace(new RegExp(quote, 'g'), escapeChar); } /** * 兼容IE的replaceAll * @param {string} str 原始字符串 * @param {string} search 搜索值 * @param {string} replacement 替换值 * @returns {string} 替换后字符串 */ static replaceAll(str, search, replacement) { return String.prototype.replaceAll ? str.replaceAll(search, replacement) : str.split(search).join(replacement); } } return _StringUtil; })(); // 模块封装:加密工具 const CryptoUtil = (() => { class _CryptoUtil { /** * 生成会话级密钥(存储在sessionStorage) * @returns {string} 随机密钥 */ static generateSessionKey() { const key = Array.from(window.crypto.getRandomValues(new Uint8Array(16))) .map(b => b.toString(16).padStart(2, '0')) .join(''); sessionStorage.setItem('obf_session_key', key); return key; } /** * 获取会话密钥 * @returns {string} 密钥 */ static getSessionKey() { return sessionStorage.getItem('obf_session_key') || this.generateSessionKey(); } /** * AES-GCM加密 * @param {string} str 待加密字符串 * @returns {Promise<object>} 加密结果 */ static async encrypt(str) { try { const keyMaterial = await window.crypto.subtle.importKey( 'raw', new TextEncoder().encode(this.getSessionKey()), { name: 'AES-GCM' }, false, ['encrypt'] ); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const encrypted = await window.crypto.subtle.encrypt( { name: 'AES-GCM', iv }, keyMaterial, new TextEncoder().encode(str) ); return { data: btoa(String.fromCharCode(...new Uint8Array(encrypted))), iv: btoa(String.fromCharCode(...iv)), method: 'aes' }; } catch (e) { throw new Error(`加密失败: ${e.message}`); } } /** * AES-GCM解密 * @param {object} payload 加密数据 * @returns {Promise<string>} 解密结果 */ static async decrypt(payload) { try { const keyMaterial = await window.crypto.subtle.importKey( 'raw', new TextEncoder().encode(this.getSessionKey()), { name: 'AES-GCM' }, false, ['decrypt'] ); const iv = Uint8Array.from(atob(payload.iv), c => c.charCodeAt(0)); const encrypted = Uint8Array.from(atob(payload.data), c => c.charCodeAt(0)); const decrypted = await window.crypto.subtle.decrypt( { name: 'AES-GCM', iv }, keyMaterial, encrypted ); return new TextDecoder().decode(decrypted); } catch (e) { throw new Error(`解密失败: ${e.message}`); } } } return _CryptoUtil; })(); // 模块封装:作用域分析器 const ScopeAnalyzer = (() => { class _ScopeAnalyzer { constructor() { this.scopeStack = [new Map()]; // 初始全局作用域 this.varIndex = 0; } /** * 进入新作用域 */ enterScope() { this.scopeStack.push(new Map()); } /** * 退出当前作用域 */ exitScope() { if (this.scopeStack.length > 1) this.scopeStack.pop(); } /** * 生成安全变量名 * @returns {string} 变量名 */ generateVarName() { const keywords = new Set(['break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return', 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'let', 'void', 'while', 'with', 'yield']); const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let name; do { const char = chars[this.varIndex % chars.length]; const num = Math.floor(this.varIndex / chars.length); name = `_${char}${num > 0 ? num : ''}`; this.varIndex++; } while (keywords.has(name) || this.isNameInScope(name)); return name; } /** * 检查变量名是否在作用域中存在 * @param {string} name 变量名 * @returns {boolean} 是否存在 */ isNameInScope(name) { return this.scopeStack.some(scope => Array.from(scope.values()).includes(name) ); } /** * 获取变量混淆名 * @param {string} originalName 原始变量名 * @returns {string} 混淆名 */ getMangledName(originalName) { const currentScope = this.scopeStack[this.scopeStack.length - 1]; if (currentScope.has(originalName)) { return currentScope.get(originalName); } const mangled = this.generateVarName(); currentScope.set(originalName, mangled); return mangled; } /** * 获取当前作用域链的所有变量映射 * @returns {Map} 变量映射表 */ getAllMappings() { return this.scopeStack.reduce((acc, scope) => { scope.forEach((v, k) => acc.set(k, v)); return acc; }, new Map()); } } return _ScopeAnalyzer; })(); // 模块封装:混淆器 const Obfuscator = (() => { class _Obfuscator { /** * 处理模板字符串 * @param {string} code 代码 * @returns {Promise<string>} 处理后代码 */ static async processTemplates(code) { const ast = acorn.parse(code, { ecmaVersion: 2020, sourceType: 'module' }); let result = code; let offset = 0; walkes(ast, { TemplateLiteral(node) { // 提取模板字符串内容 const start = node.start + offset; const end = node.end + offset; const templateRaw = code.slice(start, end); const templateContent = templateRaw.slice(1, -1); // 移除外层` // 解析静态和动态部分 const parts = StringUtil.parseTemplate(templateContent); if (parts.length === 0) return; // 加密静态部分 const processedParts = parts.map(async part => { if (part.type === 'static') { const encrypted = await CryptoUtil.encrypt(part.value); return `await CryptoUtil.decrypt(${JSON.stringify(encrypted)})`; } return part.value; // 动态部分保留原样 }); // 生成新模板字符串 Promise.all(processedParts).then(processed => { const newTemplate = `\`\${[${processed.join(',')}].join('')}\``; // 替换原始模板 result = result.slice(0, start) + newTemplate + result.slice(end); offset += newTemplate.length - (end - start); }); } }); return result; } /** * 处理作用域(包含try/catch) * @param {string} code 代码 * @returns {string} 处理后代码 */ static processScopes(code) { const analyzer = new ScopeAnalyzer(); const ast = acorn.parse(code, { ecmaVersion: 2020 }); // 遍历AST标记作用域 walkes(ast, { // 函数作用域 FunctionDeclaration(node) { analyzer.enterScope(); node.params.forEach(param => this.processParam(param, analyzer)); }, FunctionExpression(node) { analyzer.enterScope(); node.params.forEach(param => this.processParam(param, analyzer)); }, ArrowFunctionExpression(node) { analyzer.enterScope(); node.params.forEach(param => this.processParam(param, analyzer)); }, // 块级作用域 BlockStatement() { analyzer.enterScope(); }, // try/catch作用域 TryStatement(node) { analyzer.enterScope(); // try块 if (node.handler) { analyzer.enterScope(); // catch块 this.processParam(node.handler.param, analyzer); } }, // 类作用域 ClassDeclaration() { analyzer.enterScope(); }, // 退出作用域 'FunctionDeclaration:exit'() { analyzer.exitScope(); }, 'FunctionExpression:exit'() { analyzer.exitScope(); }, 'ArrowFunctionExpression:exit'() { analyzer.exitScope(); }, 'BlockStatement:exit'() { analyzer.exitScope(); }, 'TryStatement:exit'(node) { analyzer.exitScope(); // 退出try块 if (node.handler) analyzer.exitScope(); // 退出catch块 }, 'ClassDeclaration:exit'() { analyzer.exitScope(); } }, this); // 替换变量名 return this.replaceIdentifiers(code, analyzer.getAllMappings()); } /** * 处理函数参数 * @param {object} param 参数节点 * @param {ScopeAnalyzer} analyzer 作用域分析器 */ static processParam(param, analyzer) { if (param.type === 'Identifier') { analyzer.getMangledName(param.name); } } /** * 替换标识符 * @param {string} code 代码 * @param {Map} mappings 变量映射表 * @returns {string} 处理后代码 */ static replaceIdentifiers(code, mappings) { let result = code; mappings.forEach((mangled, original) => { const regex = new RegExp(`\\b${original}\\b(?!\\.)`, 'g'); result = StringUtil.replaceAll(result, regex, mangled); }); return result; } /** * 控制流扁平化(支持循环和switch) * @param {string} code 代码 * @returns {string} 处理后代码 */ static flattenControlFlow(code) { // 处理for循环 code = code.replace(/for\s*\(\s*let\s+(\w+)\s*=\s*(\d+)\s*;\s*\1\s*<\s*(\d+)\s*;\s*\1\+\+\s*\)\s*\{/g, (match, varName, start, end) => { return `{let ${varName}=${start};const _loop=()=>{if(${varName}<${end}){`; } ).replace(/}\s*(\/\/.*)?$/gm, (match) => { return `${match}${varName}++;_loop();}};_loop();}`; }); // 处理while循环 code = code.replace(/while\s*\((.*?)\)\s*\{/g, `{const _loop=()=>{if($1){` ).replace(/}\s*(\/\/.*)?$/gm, (match) => { return `${match}_loop();}};_loop();}`; }); // 处理if-else if-else let caseIdx = 1; code = code.replace(/if\s*\((.*?)\)\s*\{/g, () => { const idx = caseIdx++; return `switch(true){case ${idx}:`; }).replace(/}\s*else\s+if\s*\((.*?)\)\s*\{/g, () => { const idx = caseIdx++; return `break;case ${idx}:`; }).replace(/}\s*else\s*\{/g, 'break;default:'); return code; } /** * 添加防调试保护 * @param {string} code 代码 * @returns {string} 处理后代码 */ static addAntiDebug(code) { const antiDebugCode = ` (()=>{ const check = ()=>{ const t=performance.now(); debugger; if(performance.now()-t>160)throw new Error("检测到调试行为"); }; setInterval(check, 100); })(); `; return antiDebugCode + code; } /** * 分块处理大文件 * @param {string} code 代码 * @param {object} config 配置 * @returns {Promise<string>} 混淆结果 */ static async processInChunks(code, config) { const chunkSize = 1024 * 10; // 10KB每块 const chunks = []; for (let i = 0; i < code.length; i += chunkSize) { chunks.push(code.slice(i, i + chunkSize)); } // 并行处理所有块 const processedChunks = await Promise.all(chunks.map(async (chunk, idx) => { self.postMessage({ type: 'progress', value: Math.floor((idx / chunks.length) * 80) }); let processed = chunk; processed = await this.processTemplates(processed); processed = this.processScopes(processed); if (config.flattenControl) processed = this.flattenControlFlow(processed); return processed; })); let result = processedChunks.join(''); if (config.antiDebug) result = this.addAntiDebug(result); self.postMessage({ type: 'progress', value: 100 }); return result; } /** * 执行混淆 * @param {string} code 代码 * @param {object} config 配置 * @returns {Promise<string>} 混淆结果 */ static async run(code, config) { try { return await this.processInChunks(code, config); } catch (e) { const category = e.message.includes('加密') ? 'security' : 'syntax'; throw { message: e.message, category }; } } } return _Obfuscator; })(); // 模块封装:解密器 const Deobfuscator = (() => { class _Deobfuscator { /** * 分块解密 * @param {string} code 代码 * @returns {Promise<string>} 解密结果 */ static async processInChunks(code) { const chunkSize = 1024 * 10; const chunks = []; for (let i = 0; i < code.length; i += chunkSize) { chunks.push(code.slice(i, i + chunkSize)); } const processedChunks = await Promise.all(chunks.map(async (chunk, idx) => { self.postMessage({ type: 'progress', value: Math.floor((idx / chunks.length) * 100) }); return await this.restoreStrings(chunk); })); return processedChunks.join(''); } /** * 还原加密字符串 * @param {string} code 代码 * @returns {Promise<string>} 处理后代码 */ static async restoreStrings(code) { const regex = /await CryptoUtil\.decrypt\((\{.*?\})\)/g; const matches = []; let match; while ((match = regex.exec(code)) !== null) { matches.push({ full: match[0], payload: JSON.parse(match[1]), index: match.index }); } // 倒序替换 matches.sort((a, b) => b.index - a.index).forEach(async (item) => { try { const decrypted = await CryptoUtil.decrypt(item.payload); code = code.slice(0, item.index) + decrypted + code.slice(item.index + item.full.length); } catch (e) { console.warn(`解密片段失败: ${e.message}`); } }); return code; } /** * 执行解密 * @param {string} code 代码 * @returns {Promise<string>} 解密结果 */ static async run(code) { try { return await this.processInChunks(code); } catch (e) { const category = e.message.includes('解密') ? 'security' : 'syntax'; throw { message: e.message, category }; } } } return _Deobfuscator; })(); // Worker消息处理 self.onmessage = async (e) => { const { type, code, config } = e.data; try { // 暴露加密工具供解密函数使用 self.CryptoUtil = CryptoUtil; let result; if (type === 'obfuscate') { result = await Obfuscator.run(code, config); } else { result = await Deobfuscator.run(code); } self.postMessage({ type: 'success', result }); } catch (e) { self.postMessage({ type: 'error', message: e.message, category: e.category || 'unknown' }); } }; </script> <script> // 主页面脚本 (() => { // 工具函数 const showAlert = (message, isError = true, category = 'unknown') => { const alertEl = document.getElementById('alert'); alertEl.textContent = message; alertEl.className = `alert ${ isError ? (category === 'security' ? 'error' : 'error') : 'success' } show`; setTimeout(() => alertEl.classList.remove('show'), 3000); }; const updateProgress = (percent) => { const progress = document.querySelector('.progress'); const bar = document.getElementById('progressBar'); progress.classList.add('show'); bar.style.width = `${Math.min(100, percent)}%`; if (percent >= 100) { setTimeout(() => progress.classList.remove('show'), 500); } }; // 创建Web Worker const createWorker = () => { const workerScript = document.getElementById('worker-script').textContent; const blob = new Blob([workerScript], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); return new Worker(url); }; // 混淆处理 window.handleObfuscate = () => { const input = document.getElementById('inputCode').value.trim(); if (!input) return showAlert('请输入要混淆的代码'); const obfBtn = document.getElementById('obfBtn'); obfBtn.disabled = true; updateProgress(0); const worker = createWorker(); const config = { mangleVars: document.getElementById('mangleVars').checked, mangleFuncs: document.getElementById('mangleFuncs').checked, mangleClasses: document.getElementById('mangleClasses').checked, flattenControl: document.getElementById('flattenControl').checked, antiDebug: document.getElementById('antiDebug').checked }; worker.onmessage = (e) => { if (e.data.type === 'progress') { updateProgress(e.data.value); } else if (e.data.type === 'success') { document.getElementById('outputCode').value = e.data.result; showAlert('混淆成功', false); obfBtn.disabled = false; } else if (e.data.type === 'error') { showAlert(`混淆失败: ${e.data.message}`, true, e.data.category); obfBtn.disabled = false; } }; worker.postMessage({ type: 'obfuscate', code: input, config }); }; // 解密处理 window.handleDeobfuscate = () => { const input = document.getElementById('inputCode').value.trim(); if (!input) return showAlert('请输入要解密的代码'); const deobfBtn = document.getElementById('deobfBtn'); deobfBtn.disabled = true; updateProgress(0); const worker = createWorker(); worker.onmessage = (e) => { if (e.data.type === 'progress') { updateProgress(e.data.value); } else if (e.data.type === 'success') { document.getElementById('outputCode').value = e.data.result; showAlert('解密成功', false); deobfBtn.disabled = false; } else if (e.data.type === 'error') { showAlert(`解密失败: ${e.data.message}`, true, e.data.category); deobfBtn.disabled = false; } }; worker.postMessage({ type: 'deobfuscate', code: input }); }; // 复制结果 window.copyResult = () => { const output = document.getElementById('outputCode'); if (!output.value) return showAlert('没有可复制的内容'); output.select(); document.execCommand('copy'); showAlert('复制成功', false); }; })(); </script> </body> </html>
最新发布
11-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邵奈一

教育是一生的事业。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值