实现一个优雅的 jsBridge 方案

实现一个优雅的 jsBridge 方案

在 iOS 项目中,有时需要实现 JavaScript 和 Native 代码之间的通信。本文介绍一种优雅的 jsBridge 实现方案,支持互相调用和回调机制,并附带详细的代码和注释。

步骤 1: 定义桥接协议

首先,定义一个通用的消息格式,用于传递方法名和参数,以及回调标识符。例如:

{
  "method": "showAlert",
  "params": {
    "title": "Hello",
    "message": "This is a message from JavaScript"
  },
  "callbackId": "callback_1"
}

步骤 2: 设置 WebView

我们需要设置 WKWebView 并配置消息处理器,以处理来自 JavaScript 的消息。

import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    var callbacks: [String: (Any?) -> Void] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 设置 WKWebView 配置
        let contentController = WKUserContentController()
        // 在 WKWebView 中注册一个名为 jsBridge 的消息处理器,以便接收和处理从 JavaScript 发送到 Native 代码的消息。
        contentController.add(self, name: "jsBridge")
        
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        // 初始化 WKWebView
        webView = WKWebView(frame: self.view.bounds, configuration: config)
        self.view.addSubview(webView)
        
        // 加载网页
        if let url = URL(string: "https://your-web-page-url.com") {
            webView.load(URLRequest(url: url))
        }
    }
    
    // 处理从 JavaScript 发送的消息
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let messageBody = message.body as? [String: Any],
              let method = messageBody["method"] as? String,
              let params = messageBody["params"] as? [String: Any],
              let callbackId = messageBody["callbackId"] as? String else {
            return
        }
        
        handleJSMethod(method, params: params, callbackId: callbackId)
    }
    
    // 处理具体的 JavaScript 调用的方法
    func handleJSMethod(_ method: String, params: [String: Any], callbackId: String) {
        if method == "showAlert" {
            if let title = params["title"] as? String,
               let message = params["message"] as? String {
                showAlert(title: title, message: message) { result in
                    self.callJSCallback(callbackId: callbackId, result: result)
                }
            }
        }
        // 处理其他方法
    }
    
    // 显示警告框,并在完成后调用回调
    func showAlert(title: String, message: String, completion: @escaping (Any?) -> Void) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
            completion(["status": "OK"])
        }))
        self.present(alert, animated: true, completion: nil)
    }
    
    // 调用 JavaScript 回调函数
    func callJSCallback(callbackId: String, result: Any?) {
        // 将结果转换为 JSON 格式,并进行 Base64 编码
        let jsonResult = (try? JSONSerialization.data(withJSONObject: result ?? [:]))?.base64EncodedString() ?? ""
        // 构建 JavaScript 代码,调用回调函数 (jsBridge,_invokeCallback 都是通过注入 JS 代码实现的)
        let jsCode = "window.jsBridge._invokeCallback('\(callbackId)', '\(jsonResult)')"
        webView.evaluateJavaScript(jsCode, completionHandler: nil)
    }
    
    deinit {
        // 移除消息处理器以避免内存泄漏
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "jsBridge")
    }
}

步骤 3: 注入 JS 代码

在网页加载完成后注入 JavaScript 代码,使得网页可以调用 Native 方法并处理回调。为了避免重复注入,我们在 JavaScript 中添加一个标志变量。

override func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    let jsCode = """
    (function() {
        if (window.jsBridgeInjected) {
            return;
        }
        window.jsBridgeInjected = true;

        window.jsBridge = {
            callbacks: {},

            // 调用 Native 方法
            callNative: function(method, params, callback) {
                const callbackId = 'cb_' + (new Date()).getTime();
                this.callbacks[callbackId] = callback;
                // 能够这样调用的原因是:在 WKWebView 中进行过配置 ->`contentController.add(self, name: "jsBridge")`
                window.webkit.messageHandlers.jsBridge.postMessage({
                    method: method,
                    params: params,
                    callbackId: callbackId
                });
            },

            // Native 调用的回调方法
            _invokeCallback: function(callbackId, result) {
                if (this.callbacks[callbackId]) {
                    const resultObj = JSON.parse(atob(result));
                    this.callbacks[callbackId](resultObj);
                    delete this.callbacks[callbackId];
                }
            }
        };
    })();
    """
    webView.evaluateJavaScript(jsCode, completionHandler: nil)
}

怎样使用 ?

在 JS 中调用 Native 方法并处理回调

在你的网页 JavaScript 代码中,通过 jsBridge.callNative 方法调用 Native 方法,并处理回调:

function showAlert() {
    jsBridge.callNative("showAlert", { title: "Hello", message: "This is a message from JavaScript" }, function(result) {
        console.log("Alert closed with status:", result.status);
    });
}

更完整的代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSBridge Example</title>
    <script>
        function showAlert() {
            if (window.jsBridge && window.jsBridge.callNative) {
                jsBridge.callNative("showAlert", { title: "Hello", message: "This is a message from JavaScript" }, function(result) {
                    console.log("Alert closed with status:", result.status);
                });
            } else {
                console.error('jsBridge or callNative is not defined');
            }
        }
    </script>
</head>
<body>
    <h1>JSBridge Example</h1>
    <button onclick="showAlert()">Show Alert</button>
</body>
</html>

Native 调用 JavaScript 方法

假设我们有一个 JavaScript 函数 displayMessage,用于显示消息。我们可以通过以下方式从 Native 调用它:

JavaScript 代码

function displayMessage(params) {
    alert(params.message);
}

为了确保 displayMessage 函数能够从 Native 中调用

  • 它需要在被调用时,定义所在文件已经加载完成,displayMessage 已被成功定义好;
  • 可被全局访问,这意味着你需要在 JavaScript 代码的全局作用域中定义该函数;

示例实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSBridge Example</title>
    <script>
        function displayMessage(params) {
            alert(params.message);
        }
        
        (function() {
            if (window.jsBridgeInjected) {
                return;
            }
            window.jsBridgeInjected = true;

            window.jsBridge = {
                callbacks: {},

                // 调用 Native 方法
                callNative: function(method, params, callback) {
                    const callbackId = 'cb_' + (new Date()).getTime();
                    this.callbacks[callbackId] = callback;
                    window.webkit.messageHandlers.jsBridge.postMessage({
                        method: method,
                        params: params,
                        callbackId: callbackId
                    });
                },

                // Native 调用的回调方法
                _invokeCallback: function(callbackId, result) {
                    if (this.callbacks[callbackId]) {
                        const resultObj = JSON.parse(atob(result));
                        this.callbacks[callbackId](resultObj);
                        delete this.callbacks[callbackId];
                    }
                }
            };
        })();
    </script>
</head>
<body>
    <h1>JSBridge Example</h1>
</body>
</html>

通过在网页的全局作用域中定义 JavaScript 函数,并确保在网页加载完成后调用这些函数,可以实现从 Native 代码调用 JavaScript 函数。这种方式可以确保函数在需要时已经定义并可用。


为了让 Native 代码能够调用 JavaScript 方法,我们需要在 Swift 代码中添加调用 JavaScript 函数的功能。以下是如何在 Native 代码中调用 JavaScript 的示例:

// 调用 JavaScript 方法
func callJSFunction(functionName: String, params: [String: Any]) {
    // 将参数转换为 JSON 格式
    let jsonData = try? JSONSerialization.data(withJSONObject: params)
    let jsonString = String(data: jsonData!, encoding: .utf8)
    
    // 构建 JavaScript 代码
    let jsCode = "\(functionName)(\(jsonString!))"
    
    // 执行 JavaScript 代码
    webView.evaluateJavaScript(jsCode, completionHandler: { (result, error) in
        if let error = error {
            print("Error calling JS function: \(error)")
        } else {
            print("JS function called successfully with result: \(String(describing: result))")
        }
    })
}
示例:从 Native 调用 JavaScript

Swift 代码

// 调用 JavaScript 函数
callJSFunction(functionName: "displayMessage", params: ["message": "Hello from Native!"])

总结

通过以上步骤和代码示例,你可以实现一个优雅的 jsBridge,使 JavaScript 和 Native 代码之间可以互相调用并支持回调机制。这种实现方式可以确保数据传输的稳定性和安全性,同时提升应用的交互能力。


技术细节

异步执行的设计哲学

设计 evaluateJavaScript (Native端调用) 和 window.webkit.messageHandlers.jsBridge.postMessage (Js端调用) 为异步执行的主要原因是为了确保应用的响应性和用户体验,同时避免阻塞主线程或 JavaScript 执行。这种设计带来了多方面的好处,以下是详细的解释。

1. 保证主线程的响应性

避免 UI 卡顿

在 iOS 应用中,主线程(也称为 UI 线程)负责处理用户界面更新和响应用户交互。如果在主线程上执行耗时操作,例如等待 JavaScript 代码执行或处理消息,会导致界面卡顿,影响用户体验。

示例

webView.evaluateJavaScript("heavyComputation()") { (result, error) in
    // 回调处理结果
}

如果 evaluateJavaScript 是同步执行的,那么在 heavyComputation() 完成之前,主线程会被阻塞,导致应用无法响应用户的操作。

异步执行确保流畅的用户体验

通过异步执行,evaluateJavaScriptpostMessage 会立即返回,允许主线程继续处理其他任务,如用户界面更新和交互。这确保了应用的流畅性和响应性。

2. 提升性能和效率

并行处理

异步执行允许并行处理多个任务。例如,WebView 可以同时加载页面内容和执行 JavaScript 代码,而不会相互阻塞。这种并行处理提高了整体性能和效率。

示例

window.webkit.messageHandlers.jsBridge.postMessage({ method: "doWork" });
// 同时执行其他任务
console.log("Message sent, continue executing other tasks.");

3. 防止死锁和资源争用

避免死锁

同步调用可能会导致死锁,特别是在跨语言调用时。如果 JavaScript 和 Native 代码相互等待对方完成操作,可能会导致死锁,导致应用无法继续执行。

管理资源争用

异步执行可以更好地管理资源争用,避免长时间占用某些资源(如网络、文件系统等),从而提高系统的稳定性和可靠性。

4. 提供更好的错误处理机制

异步回调处理错误

异步执行允许通过回调机制处理错误和异常。这样可以在不阻塞主线程的情况下,优雅地处理和记录错误,确保应用的健壮性。

示例

webView.evaluateJavaScript("document.title") { (result, error) in
    if let error = error {
        print("JavaScript execution failed with error: \(error)")
    } else if let result = result {
        print("JavaScript execution result: \(result)")
    }
}

5. 提高开发效率和代码可维护性

简化代码逻辑

异步执行通过回调或 Promises 简化了代码逻辑,避免了复杂的同步处理和锁定机制。这提高了代码的可读性和可维护性。

示例

function sendMessageToNative() {
    console.log("Sending message to native code");
    window.webkit.messageHandlers.jsBridge.postMessage({
        method: "showAlert",
        params: {
            title: "Hello",
            message: "This is a message from JavaScript"
        }
    });
    console.log("Message sent to native code");
}

总结

设计 evaluateJavaScriptwindow.webkit.messageHandlers.jsBridge.postMessage 为异步执行,是为了确保应用的响应性和用户体验,提升性能和效率,防止死锁和资源争用,提供更好的错误处理机制,并提高开发效率和代码可维护性。通过异步执行,这些方法可以立即返回,避免阻塞主线程或 JavaScript 执行,从而确保应用在进行跨语言调用时的流畅性和稳定性。

谨记:didReceiveMessage 是在 main 线程上调用执行的

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 方法是在主线程上被调用的。这确保了所有的 UI 更新和操作都在主线程上完成,保持线程安全和 UI 的一致性。为了避免在处理消息时阻塞主线程,可以将耗时操作放到后台线程中执行,并在需要时回到主线程更新 UI。通过这种方式,可以确保应用的响应性和用户体验。


在实际开发中,jsBridge 会存在性能问题么 ?怎么监控 ?

在实际开发中,jsBridge 的性能问题主要来自于以下几个方面:

  1. 频繁的跨语言调用:每次 JavaScript 和 Native 代码之间的调用都涉及到上下文切换,这可能会导致性能瓶颈,尤其是在频繁调用的情况下。
  2. 数据传输开销:较大的数据通过 jsBridge 传输可能会影响性能,因为需要进行序列化和反序列化操作。
  3. 线程阻塞:某些操作可能会在主线程上执行,导致界面卡顿或阻塞。
  4. 复杂的业务逻辑:如果 jsBridge 处理的业务逻辑过于复杂,也可能影响整体性能。

性能监控

为了监控和优化 jsBridge 的性能,可以采用以下方法:

  1. 日志记录:在 jsBridge 的每次调用前后记录时间戳,以计算调用的耗时。

  2. 性能分析工具:使用系统自带或第三方性能分析工具,如 Xcode 的 Instruments,来监测 CPU 使用率、内存占用和线程活动等指标。

  3. 定期分析和优化:通过定期的性能分析和代码审查,识别和优化性能瓶颈。

具体实现

1. 日志记录

在每次 jsBridge 调用前后记录时间戳,并计算耗时:

JavaScript 端

(function() {
    window.jsBridge = {
        callbacks: {},
        callNative: function(method, params, callback) {
            const startTime = Date.now();
            const callbackId = 'cb_' + startTime;
            this.callbacks[callbackId] = (result) => {
                const endTime = Date.now();
                console.log(`Call to ${method} took ${endTime - startTime}ms`);
                callback(result);
            };
            window.webkit.messageHandlers.jsBridge.postMessage({
                method: method,
                params: params,
                callbackId: callbackId
            });
        },
        _invokeCallback: function(callbackId, result) {
            if (this.callbacks[callbackId]) {
                this.callbacks[callbackId](result);
                delete this.callbacks[callbackId];
            }
        }
    };
})();

Swift 端

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    let startTime = Date().timeIntervalSince1970
    guard let messageBody = message.body as? [String: Any],
          let method = messageBody["method"] as? String,
          let params = messageBody["params"] as? [String: Any],
          let callbackId = messageBody["callbackId"] as? String else {
        return
    }
    
    handleJSMethod(method, params: params, callbackId: callbackId)
    
    let endTime = Date().timeIntervalSince1970
    print("Call to \(method) took \((endTime - startTime) * 1000)ms")
}
2. 使用性能分析工具

Xcode Instruments

  1. 启动 Instruments:打开 Xcode,选择菜单栏中的 Xcode > Open Developer Tool > Instruments
  2. 选择模板:选择合适的模板,如 Time Profiler 或 Activity Monitor。
  3. 运行应用:通过 Instruments 运行你的应用,开始性能监测。
  4. 分析结果:查看和分析 CPU 使用率、内存占用和线程活动,找出性能瓶颈。
3. 定期分析和优化

定期进行代码审查和性能分析,找出性能瓶颈,并进行优化。例如:

  • 减少频繁调用:合并多次调用,减少跨语言调用的次数。
  • 优化数据传输:通过压缩或分批传输数据,减少数据传输的开销。
  • 优化线程处理:避免在主线程上执行耗时操作,将其放到后台线程处理。

小结

jsBridge 的性能问题主要来自于频繁的跨语言调用、数据传输开销、线程阻塞和复杂的业务逻辑。通过日志记录、使用性能分析工具和定期分析优化,可以有效监控和提升 jsBridge 的性能。


使用 WKScriptMessage (实现 Native JavaScript 的交互)与使用 JavaScriptCore 有什么不一样 ?

在 iOS 开发中,实现 Native 和 JavaScript 交互的主要方式有两种:WKScriptMessageJavaScriptCore。它们在功能、使用场景和性能上有一些明显的不同。

WKScriptMessage

WKScriptMessage 是 WKWebView 的一种机制,允许 JavaScript 发送消息给 Native 代码。它是通过 WKUserContentController 来添加消息处理器,并通过 WKScriptMessageHandler 来处理这些消息。

特点
  1. 现代化

    • 适用于 WKWebView,这是苹果推荐的现代 WebView 实现,性能和安全性都优于 UIWebView
  2. 简单易用

    • 配置和使用相对简单,只需要添加消息处理器并处理消息。
  3. 安全性

    • 通过消息传递,避免了直接调用 Native 方法的安全隐患。
使用示例

Swift 端

import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let contentController = WKUserContentController()
        contentController.add(self, name: "jsBridge")
        
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        webView = WKWebView(frame: self.view.bounds, configuration: config)
        self.view.addSubview(webView)
        
        if let url = URL(string: "https://your-web-page-url.com") {
            webView.load(URLRequest(url: url))
        }
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "jsBridge" {
            if let body = message.body as? [String: Any] {
                handleJSMessage(body)
            }
        }
    }
    
    func handleJSMessage(_ message: [String: Any]) {
        // 处理来自 JavaScript 的消息
    }
    
    deinit {
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "jsBridge")
    }
}

JavaScript 端

function sendMessageToNative() {
    window.webkit.messageHandlers.jsBridge.postMessage({
        method: "showAlert",
        params: {
            title: "Hello",
            message: "This is a message from JavaScript"
        }
    });
}

JavaScriptCore

JavaScriptCore 是 iOS 上的一个框架,提供了一个 JavaScript 引擎,允许在 Native 代码中直接执行 JavaScript 代码,并在 JavaScript 中调用 Native 方法。它主要用于 UIWebView 或不使用 WebView 的纯 JavaScript 处理。

特点
  1. 直接调用

    • 允许直接在 Native 代码中执行 JavaScript 代码,并获取结果。
    • 允许在 JavaScript 中直接调用 Native 方法。
  2. 高级功能

    • 支持更复杂的 JavaScript 操作,例如创建和操作 JSContext、JSValue 等。
  3. 灵活性

    • 适用于需要复杂 JavaScript 处理的场景。
使用示例

Swift 端

import JavaScriptCore

class ViewController: UIViewController {
    var jsContext: JSContext!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        jsContext = JSContext()
        
        // 定义一个 Native 方法,供 JavaScript 调用
        let showAlert: @convention(block) (String) -> Void = { message in
            DispatchQueue.main.async {
                let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }
        jsContext.setObject(showAlert, forKeyedSubscript: "showAlert" as NSString)
        
        // 执行 JavaScript 代码
        let jsCode = """
        showAlert("Hello from JavaScript");
        """
        jsContext.evaluateScript(jsCode)
    }
}

对比总结

特性WKScriptMessageJavaScriptCore
适用 WebViewWKWebViewUIWebView(已弃用)或不使用 WebView
消息传递通过消息传递直接调用
复杂度简单易用功能强大但复杂
性能较高取决于使用场景
安全性需要处理潜在的安全问题
使用场景适用于现代 WebView 交互适用于需要复杂 JavaScript 处理的场景

性能问题与监控

性能问题
  • WKScriptMessage:由于通过消息传递,性能通常较好,但频繁的大数据传输可能会导致性能问题。
  • JavaScriptCore:直接调用,性能较高,但复杂操作可能会影响性能。
性能监控
  1. 日志记录:在调用前后记录时间戳,计算耗时。
  2. 使用 Instruments:分析 CPU 使用率、内存占用和线程活动。
  3. 代码审查:定期分析和优化代码。

小结

WKScriptMessageJavaScriptCore 各有优缺点,选择哪种方式取决于具体的使用场景和需求。通过适当的监控和优化,可以确保 jsBridge 的高效运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/773842.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算两个经纬度之间的球面距离(基于Mysql和PHP实现)

计算两个经纬度之间的球面距离 1、MySQL实现方式 - 基于空间函数(ST_Distance_Sphere)实现 前置条件&#xff1a;确保您使用的是 MySQL 8.0 或更高版本&#xff0c;因为较早的版本对地理空间的支持有限。 1.1 创建表和索引 说明&#xff1a;设置 location 为 point 类型 #…

驭码CodeRider将亮相世界人工智能大会,AI 产品、重磅分享,真的很City!

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…

Redis 中 Set 和 Zset 类型

目录 1.Set类型 1.1 Set集合 1.2 普通命令 1.3 集合操作 1.4 内部编码 1.5 使用场景 2.Zset类型 2.1 Zset有序集合 2.2 普通命令 2.3 集合间操作 2.4 内部编码 2.5 使用场景 1.Set类型 1.1 Set集合 集合类型也是保存多个字符串类型的元素&#xff0c;但是和列表类型不同的是&…

LVS+Keepalived 实现高可用负载均衡

前言 在业务量达到一定量的时候&#xff0c;往往单机的服务是会出现瓶颈的。此时最常见的方式就是通过负载均衡来进行横向扩展。其中我们最常用的软件就是 Nginx。通过其反向代理的能力能够轻松实现负载均衡&#xff0c;当有服务出现异常&#xff0c;也能够自动剔除。但是负载…

基于Redisson实现分布式锁

基于redisson实现分布式锁 之前背过分布式锁几种实现方案的八股文&#xff0c;但是并没有真正自己实操过。现在对AOP有了更深一点的理解&#xff0c;就自己来实现一遍。 1、分布式锁的基础知识 分布式锁是相对于普通的锁的。普通的锁在具体的方法层面去锁&#xff0c;单体应…

搜维尔科技:详谈ART的工具追踪技术

您的生产流程中是否已经受益于刀具跟踪系统&#xff1f;您是否意识到它们的价值&#xff1f;因为它们可以优化您的装配顺序&#xff0c;从而节省您的时间和金钱。 目前我们提供两种工具跟踪解决方案&#xff1a; 1.ART与 VERPOSE的解决方案——易于使用的图像识别 安装在工…

探索智能合约在医疗健康领域的革新应用

随着区块链技术的发展&#xff0c;智能合约作为其重要应用之一&#xff0c;在医疗健康领域展示了巨大的潜力和革新性。智能合约是一种基于区块链的自动化执行协议&#xff0c;它可以在无需中介的情况下执行和验证合同。在医疗健康领域&#xff0c;智能合约不仅简化了数据管理和…

房屋租赁管理小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;中介管理&#xff0c;房屋信息管理&#xff0c;房屋类型管理&#xff0c;租房订单管理&#xff0c;租房信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;房屋信息&a…

ctfshow-web入门-命令执行(web66-web70)

目录 1、web66 2、web67 3、web68 4、web69 5、web70 1、web66 show_source 被禁用 highlight_file 发现 flag 不在 flag.php 里面 先使用 scandir() 进行目录扫描&#xff1a; cprint_r(scandir("./")); 当前目录下只有 index.php 和 flag.php 扫一下根目…

图书商城系统java项目ssm项目jsp项目java课程设计java毕业设计

文章目录 图书商城系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 图书商城系统 一、项目演示 图书商城系统 二、项目介绍 语言: Java 数据库&#xff1a;MySQL 技术栈&#xff1a;SpringS…

「ETL趋势」FDL定时任务区分开发/生产模式、API输入输出支持自定义响应解析

FineDataLink作为一款市场上的顶尖ETL工具&#xff0c;集实时数据同步、ELT/ETL数据处理、数据服务和系统管理于一体的数据集成工具&#xff0c;进行了新的维护迭代。本文把FDL4.1.7最新功能作了介绍&#xff0c;方便大家对比&#xff1a;&#xff08;产品更新详情&#xff1a;…

spark shuffle——shuffle管理

ShuffleManager shuffle系统的入口。ShuffleManager在driver和executor中的sparkEnv中创建。在driver中注册shuffle&#xff0c;在executor中读取和写入数据。 registerShuffle&#xff1a;注册shuffle&#xff0c;返回shuffleHandle unregisterShuffle&#xff1a;移除shuff…

LED显示屏跟COB显示屏有哪些不同?

COB显示屏跟LED显示屏的主要区别在于产品的显示效果、封装技术、耐用性、防护力、维护以及制造成本方面的不同&#xff0c;这里所说的LED显示屏主要指的是使用SMD封装的LED显示屏&#xff0c;今天跟随COB显示屏厂家中品瑞科技一起来详细看看具体分析&#xff1a; 一、封装技术 …

视图库对接系列(GA-T 1400)九、视图库对接系列(本级)机动车数据推送

背景 在上几章中,我们已经可以将视图库的平台写到我们的数据库中了。 换句话说就已经接入我们的平台了,这几期的话,我们就对接设备, 将设备的数据接入到我们平台来。 机动车数据推送 接入机动车数据推送相对比较简单,我们只需要实现对应的接口就ok了。 具体如图: 有增…

77. UE5 RPG 创建角色的技能栏

在前面的文章里&#xff0c;我们实现了角色属性技能和场景。接下来&#xff0c;我们要优化角色显示UI&#xff0c;在屏幕底部显示角色血量&#xff0c;蓝量&#xff0c;技能和经验值。 创建新的用户控件 选择创建新的控件蓝图 父类为我们自定义的RPGUserWidget&#xff0c;这…

这样拼板帮你省近万元,堪称PCB工程师成本终结者!

别再被骗了&#xff0c;打PCB板价格高不是单价高&#xff01;而是你的拼板导致利用率太低了&#xff01; 今天给大家讲个小故事&#xff0c;教大家如何省钱...... 一个爽朗的晴天&#xff0c;我听闻同事说有客户对他吐槽打板子价格太高&#xff0c;说着说着就开始吹起了牛逼...…

【论文阅读】VASA-1: Lifelike Audio-Driven Talking FacesGenerated in Real Time

整体框架。不直接生成视频帧&#xff0c;而是在潜在空间中生成整体面部动态和头部运动&#xff0c;条件是音频和其他信号。给定这些运动潜在编码&#xff0c;通过面部解码器生成视频帧&#xff0c;还接受从输入图像中提取的外观和身份特征作为输入。 构建了一个面部潜在空间并…

【C#】ProgressBar进度条异步编程思想

1.控件介绍 进度条通常用于显示代码的执行进程进度&#xff0c;在一些复杂功能交互体验时告知用户进程还在继续。 在属性栏中&#xff0c;有三个值常用&#xff1a; Value表示当前值&#xff0c;Minimum表示进度条范围下限&#xff0c;Maximum表示进度条范围上限。 2.简单实…

【网络安全】第8讲 网络安全协议(笔记)

一、网络安全协议概述 1、协议 是指两个或多个以上参与者为完成某项特定的任务而采取的一系列步骤。 2、网络协议 是指计算机网络中通信各方关于如何进行数据交换所达成的一致性规则、标准或约定的集合&#xff0c;即由参与通信的各方按确定的步骤做出一系列通信动作&#xff…

Linux系统安装青龙面板结合内网穿透实现使用公网地址远程访问

文章目录 前言一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用。本教程使用Docker部署青龙&#xff0c;如何安装Docker详见&#xff1a; 二、安装青龙面板三、映射本地部署的青龙面板至公网四、使用固定公网地址访问本地部署的青龙面板 …
最新文章