Balidrop-spring-boot-browser讲解

2021年1月4日 作者 陈益

一、破解 jxbrowser 4 Google chromium

static {
        try {
            Field e = bb.class.getDeclaredField("e");
            e.setAccessible(true);
            Field f = bb.class.getDeclaredField("f");
            f.setAccessible(true);
            Field modifersField = Field.class.getDeclaredField("modifiers");
            modifersField.setAccessible(true);
            modifersField.setInt(e, e.getModifiers() & ~Modifier.FINAL);
            modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
            e.set(null, new BigInteger("1"));
            f.set(null, new BigInteger("1"));
            modifersField.setAccessible(false);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

二、启动springboot且设置主窗口的大小和初始化用户的信息

  1. 初始化 initialize方法

    public static void main(String[] args) {
          //设置加载等待页面 
        LoadSplashScreen splashScreen = new LoadSplashScreen();
        //默认加载主 窗口   MainView.class  -->
        launchApp(BalidropBrowserApp.class, MainView.class, splashScreen, new String[]{});
    }
    
      @Override
    public void start(Stage stage) throws Exception {
        Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
        stage.setX(primaryScreenBounds.getMinX());
        stage.setY(primaryScreenBounds.getMinY());
        stage.setWidth(primaryScreenBounds.getWidth());
        stage.setHeight(primaryScreenBounds.getHeight());
    
        super.start(stage);
    }
    
    @Override
    public void init() throws Exception {
        super.init();
    
        CustomerHandle.getCustomer();
    }

三、 针对主view的 控制器 MainStageController

    1、初始化默认的 selectedTab 
CreateTabController.selectedTab = defaultTab;

2、获取之前登陆的信息登陆系统
new Thread(new Runnable() {
    @Override
    public void run() {
    if (CustomerHandle.getCustomer() != null) {
        String isloginUrl = SERVER_HOST + "/subject/isLogin?needMenu=false&_=" + Math.random();
        ResponseJsonData requestServer = BrowerUtils.requestServer(isloginUrl, Method.GET, null,
                ResponseJsonData.class);
        if (!requestServer.isFlag()) {
            CustomerHandle.setCustomer(new Customer());
            // 开始进行解析
            MainStageController.this.loginStatusInfo();
        }
    }
    }
}).start();

3、设置 主页面的 链接框的宽度

//设置searchText宽度
searchText.setPrefWidth(new Double(Double.MAX_VALUE).intValue());

4、设置 谷歌 浏览器 调试端口

//设置searchText宽度
BrowserPreferences.setChromiumSwitches("--remote-debugging-port=9222");

5、设置 tabls的关闭策略

tabs.setTabClosingPolicy(TabClosingPolicy.SELECTED_TAB);

6、设置默认一个的tab是否可关闭

defaultTab.setClosable(false);

6、初始化谷歌浏览器内核

// 1. 创建浏览器上下文,数据存放路径
BROWSERCONTEXT = new BrowserContext(
new BrowserContextParams(new File(BrowerUtils.getTmpDir(), "balidrop").getAbsolutePath()));
BROWSERCONTEXT.getNetworkService().setCertificateVerifier(params -> CertificateVerifyResult.OK);
// 2. 创建浏览器
BrowserView browserView = new BrowserView(new Browser(BROWSERCONTEXT));

7、设置border panel面板中的 center布局为 browerView

defaultBorderPane.setCenter(browserView);

8、加载之前的cookie信息,这样保证浏览器在关闭的时候cookie功能可用

// 加载cookie
CookieStorage cookieStorage = browserView.getBrowser().getCookieStorage();
BrowerUtils.addCookieByHost(cookieStorage);

9、设置default tab的主页

Platform.runLater(() -> { browserView.getBrowser().loadURL(MainStageController.this.HOME_URL);
});

10、设置 defaultTab 的监听 ,因为页面的头信息都是开启一个的时候动态的渲染出来的

defaultTab.setOnSelectionChanged(event -> {
    // 1. 将当前选中的tab页的头信息设置到选中的tab页中
    Tab source = CreateTabController.selectedTab;
    BorderPane sourceContent = (BorderPane) source.getContent();

    Tab target = (Tab) event.getTarget();
    BorderPane targetContent = (BorderPane) target.getContent();

    targetContent.setTop(sourceContent.getTop());

    // 2. 更换选中的tab
    CreateTabController.selectedTab = target;

    // 3. 开发产品按钮是否显示
    BrowerUtils.showOrHiddenButton(targetContent, mainStageController);
});

11、初始化browser

一、 设置页面中的超链接的方式

 browserView.getBrowser().setPopupHandler(popupParams -> (a, b) -> Platform.runLater(() -> {
            CreateTabController createTabController = new CreateTabController(this);
            createTabController.addTab(a);
        }));

二、设置是否使用cookie的信息的功能

browserView.getBrowser().getContext().getNetworkService().setNetworkDelegate(new DefaultNetworkDelegate() {
    @Override
    public boolean onCanSetCookies(String url, List cookies) {
    return true;
    }

    @Override
    public boolean onCanGetCookies(String url, List cookies) {
    return true;
    }
});

三、设置alert弹窗的逻辑

browserView.getBrowser().setDialogHandler(new DefaultDialogHandler(browserView) {
    @Override
        public void onAlert(DialogParams params) {
        Platform.runLater(() -> {
        BrowerUtils.alertDialog(AlertType.INFORMATION, params.getMessage());
        });
    }
});

四、设置 border 的document的加载回调, 根据链接的匹配行,向页面注入自己的脚本

MainStageController mainStageController = this;
browserView.getBrowser().addLoadListener(new LoadAdapter() {

    @Override
    public void onStartLoadingFrame(StartLoadingEvent arg0) {

    if (arg0.isMainFrame() && (!arg0.isSameDocument())) {
        logger.info("## start loading...");
    }
    }

    @Override
    public void onDocumentLoadedInMainFrame(LoadEvent event) {

       注入页面的java对象为 JavaApp 
    JSValue executeScriptResult = browserView.getBrowser().executeJavaScriptAndReturnValue("window");
    executeScriptResult.asObject().setProperty("JavaApp", MainStageController.this);

    try {
        // 显示链接
        URI rUri = new URI(browserView.getBrowser().getURL());
        String currentUrl = browserView.getBrowser().getURL();
        logger.info("## currentUrl:" + currentUrl);
        // 处理不打开tab页进入详情页面
        BrowerUtils.showOrHiddenButton(mainStageController, currentUrl);

         //开始向页面注入自己的脚本信息,
        String executeJs = JSHandle.findExecuteJSByHost(rUri.getHost());
        if (StringUtils.isNotBlank(executeJs)) {
            browserView.getBrowser().executeJavaScript(executeJs);
        }

        // 保存cookie
        List allCookies = browserView.getBrowser().getCookieStorage().getAllCookies();
        Platform.runLater(() -> {
            String title = browserView.getBrowser().getTitle();
            if (title.length() > 10) {
                title = title.substring(0, 10) + "...";
            }
            tab.setText(title);
            BrowerUtils.setCookie2File(allCookies);
        });

    } catch (URISyntaxException e) {
        logger.error("## onDocumentLoadedInMainFrame error", e);
    }
    }

});

五、打印 border 的调试端口信息

logger.info("## dubug地址:" + browserView.getBrowser().getRemoteDebuggingURL());

六、设置 F12监听 开启 浏览器的调试

private void addKeyEventsHandler(BrowserView browserView, Tab tab) {
        browserView.setKeyEventsHandler(handler -> {
            if ("F12".equalsIgnoreCase(handler.getCode().getName())) {
                Platform.runLater(() -> {
                    int smallHeight = 350;
                    int bigHeight = 600;
                    BorderPane content = (BorderPane) CreateTabController.selectedTab.getContent();
                    if(content.getBottom() != null){
                        logger.info("## 已打开了,关闭debug");
                        content.setBottom(null);
                        return;
                    }
                    final BrowserView br;
                    if (content.getUserData() == null) {
                        br = new BrowserView(new Browser(BrowserType.HEAVYWEIGHT, BROWSERCONTEXT));
                        br.getBrowser().loadURL(browserView.getBrowser().getRemoteDebuggingURL());
                        br.setPrefHeight(smallHeight);
                        content.setUserData(br);
                    } else {
                        br = (BrowserView) content.getUserData();
                        br.setPrefHeight(smallHeight);
                    }

                    BorderPane borderPane = new BorderPane();
                    borderPane.setCenter(br);
                    HBox hBox = new HBox();
                    hBox.setAlignment(Pos.CENTER_RIGHT);

                    Text reSizeText = new Text("放大");
                    reSizeText.setFill(Color.GREEN);
                    reSizeText.setCursor(Cursor.HAND);
                    hBox.setStyle("-fx-font-size:14px;-fx-font-weight:bold");
                    HBox.setMargin(reSizeText, new Insets(0, 20, 0, 0));
                    reSizeText.setOnMouseClicked((EventHandler) event -> {
                        if(br.getPrefHeight() < bigHeight) {
                            br.setPrefHeight(bigHeight);
                            reSizeText.setText("恢复");
                        }else{
                            br.setPrefHeight(smallHeight);
                            reSizeText.setText("放大");
                        }
                    });
                    hBox.getChildren().add(reSizeText);

                    //关闭按钮
                    Text closeText = new Text("关闭");
                    closeText.setFill(Color.RED);
                    closeText.setCursor(Cursor.HAND);
                    HBox.setMargin(closeText, new Insets(0, 10, 0, 0));
                    closeText.setOnMouseClicked((EventHandler) event -> content.setBottom(null));
                    hBox.getChildren().add(closeText);

                    borderPane.setTop(hBox);
                    content.setBottom(borderPane);
                });
                return true;
            }
            return false;
        });
    }

七、设置 tab的右键 关闭的逻辑

        MenuItem closeAllMenuItem = new MenuItem("Close All");
        closeAllMenuItem.setOnAction(event -> {

            // 关闭所有的,将第一个进行导航到首页
            if (tabs.getTabs().size() > 1) {
                BorderPane content = (BorderPane) defaultTab.getContent();
                BrowserView browserView1 = (BrowserView) content.getCenter();
                browserView1.getBrowser().loadURL(HOME_URL);
                tabs.getTabs().retainAll(Lists.newArrayList(defaultTab));
            }
        });
        tab.setContextMenu(new javafx.scene.control.ContextMenu(closeAllMenuItem));

12、 设置用户信息的展示

public void loginStatusInfo() {

        String loginImgUrl = "static/4.png";
        String customerInfo = "Log in";
        if (CustomerHandle.isLogin()) {
            customerInfo = CustomerHandle.getCustomer().getNickName();
            loginImgUrl = "static/4-1.png";
        }

        // 统一设置登录信息【设置选中的tab页的信息】
        BorderPane borderPane = (BorderPane) CreateTabController.selectedTab.getContent();
        GridPane gridPane = (GridPane) borderPane.getTop();

        HBox hbox = (HBox) gridPane.getChildren().get(1);

        // 2.设置图片
        ImageView loginImageView = (ImageView) hbox.getChildren().get(3);
        loginImageView.setImage(new Image(new ClassPathResource(loginImgUrl).getPath()));

        // 3.设置用户信息
        Text customerInfoText = (Text) hbox.getChildren().get(4);
        customerInfoText.setText(customerInfo);
    }

 13、重新定义主窗口的关闭逻辑,需要关闭 browserView
        GUIState.getStage().setOnCloseRequest(event -> {
            // 关闭窗口必须开启一个线程进行关闭
            new Thread(() -> browserView.getBrowser().dispose(true)).start();
        });
14、显示 测试环境和线上环境的主名称

        // 重新定义app名称
        GUIState.getStage().setOnShown(event -> Platform.runLater(() -> {
            // 环境
            String env = BrowerUtils.getActiveProfile();
            String versionName = "测试环境";
            if ("prod".equalsIgnoreCase(env)) {
                versionName = "";

                mainStageController.searchText.setVisible(false);
            }else{
                mainStageController.searchText.setVisible(true);
            }
            GUIState.getStage().setTitle(BrowerUtils.getAppName() + " " + BrowerUtils.getAppVersionDesc() + " "
                    + BrowerUtils.getAppVersion() + " " + versionName);
        }));

2、监听JS中的 提交事件 ,当页面中注入的JS脚本的点击 产品开发后,

public void executeAction4ProductList() {
        MainStageController mainStageController = this;
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                // 操作前请先登录
                boolean login = CustomerHandle.isLogin();
                if (!login) { // 弹出登录框
                    showLogin();
                    return;
                }

                // 开始调用上传Service进行上传
                try {
                    mainStageController.showLoading();
                    BorderPa ne content = (BorderPane) CreateTabController.selectedTab.getContent();
                    BrowserView browserView = (BrowserView) content.getCenter();

                    JSValue executeScriptResult = browserView.getBrowser()
                            .executeJavaScriptAndReturnValue("executeProductListWork()");

                    if (StringUtils.isNotBlank(executeScriptResult.getStringValue())) {
                        logger.info("productList....." + executeScriptResult.getStringValue());
                        ProductSaveDto productSaveDto = new Gson().fromJson(executeScriptResult.getStringValue(),
                                ProductSaveDto.class);

                        DataGrid dataGrid = BrowerUtils.requestServer4RequestBody(SERVER_HOST + "/products/",
                                productSaveDto, DataGrid.class);

                        final String message;
                        final AlertType alertType;
                        if (dataGrid.isFlag()) {
                            if (StringUtils.isBlank(dataGrid.getMsg())) {
                                message = "Successful operation";
                            } else {
                                message = dataGrid.getMsg();
                            }
                            alertType = AlertType.INFORMATION;
                        } else {
                            alertType = AlertType.ERROR;
                            if (StringUtils.isBlank(dataGrid.getMsg())) {
                                message = "Failed operation";
                            } else {
                                message = dataGrid.getMsg();
                            }
                        }
                        mainStageController.hideLoading();
                        BrowerUtils.alertDialog(alertType, message);
                    } else {
                        mainStageController.hideLoading();
                    }

                } catch (Exception exception) {
                    mainStageController.hideLoading();
                    logger.error(exception.getMessage(), exception);
                    BrowerUtils.alertDialog(AlertType.ERROR, "Parameter error:" + exception.getMessage());
                }
            }
        });
    }

 public void executeAction4PurchasingCart() {
        MainStageController mainStageController = this;
        Platform.runLater(new Runnable() {
            @SuppressWarnings("unchecked")
            @Override
            public void run() {

                // 操作前请先登录

                boolean login = CustomerHandle.isLogin();
                if (!login) { // 弹出登录框
                    showLogin();
                    return;
                }
                try {
                    mainStageController.showLoading();
                    BorderPane content = (BorderPane) CreateTabController.selectedTab.getContent();
                    BrowserView browserView = (BrowserView) content.getCenter();
                    JSValue executeScriptResult = browserView.getBrowser()
                            .executeJavaScriptAndReturnValue("executePurchasingCartWork()");
                    // 开始调用上传Service进行上传
                    if (StringUtils.isNotBlank(executeScriptResult.getStringValue())) {
                        logger.info("purchars....." + executeScriptResult.getStringValue());
                        // 开始保存代购商品

                        Map agentParams = new Gson().fromJson(executeScriptResult.getStringValue(),
                                Map.class);
                        ResponseJsonData responseJsonData = BrowerUtils.requestServer(SERVER_HOST + "/agentOrder/save",
                                Method.POST, agentParams, ResponseJsonData.class);
                        final String message;
                        final AlertType alertType;
                        if (responseJsonData.isFlag()) {

                            message = "Successful operation";

                            alertType = AlertType.INFORMATION;
                        } else {
                            alertType = AlertType.ERROR;

                            message = "Failed operation";

                        }
                        mainStageController.hideLoading();
                        BrowerUtils.alertDialog(alertType, message);

                    } else {
                        mainStageController.hideLoading();
                    }
                } catch (Exception exception) {
                    mainStageController.hideLoading();
                    logger.error(exception.getMessage(), exception);
                    BrowerUtils.alertDialog(AlertType.ERROR, "Parameter error:" + exception.getMessage());
                }
            }
        });
    }

3、以淘宝为案例 说明 代购和选品的 逻辑,其中的 JavaApp 为事先注入进来的

function rendererBaldripBtn(selector){
    var divElement = document.createElement("div");             
    var productListBtn= document.createElement("button");
    productListBtn.innerText="Develop Product";
    productListBtn.style="padding:10px 0px;text-align:center;background-color:#EA0627;border-radius:4px;color:white;width:160px;font-size:18px;border:0px;margin:0px 20px 20px 0px;cursor:pointer;"
    productListBtn.onclick=function(){
        JavaApp.executeAction4ProductList();
    }
    var purcharBtn = document.createElement("button");
    purcharBtn.innerText="Purchase Now";
    purcharBtn.style="padding:10px 0px;text-align:center;background-color:#0F83EA;border-radius:4px;color:white;width:160px;font-size:18px;border:0px;cursor:pointer;"
    purcharBtn.onclick=function(){
        JavaApp.executeAction4PurchasingCart();
    }
    document.querySelector(selector).innerHTML = "";
    document.querySelector(selector).appendChild(divElement);
    divElement.appendChild(productListBtn);
    divElement.appendChild(purcharBtn);
}