RingUI + JCEF开发IDEA插件

news2024/11/16 16:45:48

文章目录

    • RingUI
    • 知识储备
    • 示例插件
    • 实现逻辑
    • 开发环境
    • 开发流程
      • 新建一个IDEA插件项目
      • 新建一个前端项目
      • 验证前端项目
      • 丰富前端项目
      • 丰富插件内容

RingUI

This collection of UI components aims to provide all the necessary building blocks for web-based products built inside JetBrains, as well as third-party plugins developed for JetBrains’ products.

JetBrains团队发布了一套基于Web的、和JetBrains产品风格相同的UI组件库ring-ui,我觉得这个UI组件库有以下使用场景:

  1. 开发一个JetBrains风格的网页应用,比如开发了一个JetBrains插件,提供一个网站来介绍该插件;
  2. 开发一个基于Web的JetBrains插件,比如Markdown预览插件,这类插件如果使用Java Swing来实现,基本不可能,但是直接使用web组件却很方便;
  3. 开发一个JetBrains插件,但是界面非常复杂,开发人员对Java Swing不熟悉,但对react和web ui组件非常熟悉,可直接使用ring-ui来开发用户界面。

知识储备

  • RingUI:ring-ui

  • RingUI文档:ring-ui 文档

  • CEF(Chromium Embedded Framework)/JCEF(Java Chromium Embedded Framework)/JBCEF(JetBrains Chromium Embedded Framework):

    Chromium Embedded Framework: 基于Google Chromium项目的开源Web browser控件,C++实现,支持Windows, Linux, Mac平台,即可以在自己的C++项目中嵌入一个Chrome内核的浏览器,并且支持各种自定义接口,比如自定义响应下载、自定义拦截请求、自定义浏览器控制台等,类似一个非常强大的WebView;
    Java Chromium Embedded Framework: 通过JNI机制,将C++的接口通过Java暴露,并且支持将浏览器嵌入JFrame。
    JetBrains Chromium Embedded Framework: JetBrains在JCEF的基础上进一步作出的封装JetBrains/jcef
    IDEA 2020.1版本中,JCEF作为一个试验性功能,需要通过一些额外操作才能启用JCEF;在IDEA 2020.2+版本中,JCEF默认集成并启用,成为一个标准功能:JCEF - Java Chromium Embedded Framework

  • React: reactjs

    RingUI是一个React组件库,需要对React有一定的了解

  • JOOQ SQL Translation:SQL Translation

    这是一个在线的sql方言翻译工具,可以把一种数据库的SQL语法转换成另一种数据的SQL语法,比如Oracle转成MySQL

示例插件

希望有一个IDEA插件,点击后打开一个弹窗,展示类似于百度翻译的界面,选择源数据库和目标数据库,点击翻译能完成SQL语法的转换,有如下限制和要求:

  • JOOQ有开源版本,并且暴露了SQL转换的方法,但不能用开源版本的JOOQ实现,必须访问https://www.jooq.org/translate/translate接口来实现转换,因为开源版本的JOOQ只支持开源数据库之间的SQL转换,商业数据库需要购买license,而在线的SQL转换支持所有JOOQ支持的数据库,包含Oralce在内的商业数据库;
  • 不允许直接嵌入浏览器,直接访问https://www.jooq.org/translate/,因为默认的界面太丑,而且有很多与转换无关的信息;
  • 界面的风格需要跟随IDEA本身风格,即跟随IDEA的深色和浅色模式;

实现逻辑

  • 界面由单独的React + RingUI前端项目实现,npm run build构建后将build目录的内容拷贝至插件resource目录下;
  • 在IDEA顶部tools菜单下添加一个SQL Translator菜单,点击后弹出一个窗口,使用JCEF渲染resource目录下的index.html,JCEF访问一个虚拟地址,为JCEF添加一个请求资源拦截器,根据访问的URI决定返回resource目录下的静态资源,还是调用远程接口;
  • 风格切换实现逻辑:JCEF访问index.html之前,先获取当前IDEA的风格,然后在URL后面加上Query参数,如index.html?theme=dark,React解析参数,动态设定页面风格。

开发环境

  • IDEA:IntelliJ IDEA 2022.3 (Ultimate Edition)
  • Gradle:7.5.1
  • JDK: 11.0.11
  • React:18.2.0
  • node/npm: v16.18.1/8.19.2

开发流程

新建一个IDEA插件项目

假设项目名称为sql-translator
在这里插入图片描述

创建完成后,系统会自动下载Gradle、IDEA SDK、JBCEF(JetBrains CEF)等,下载时间可能比较长。

新建一个前端项目

假设项目名称为react-client

# 安装必要依赖
npm install --save react react-dom webpack webpack-dev-server html-webpack-plugin
npm install --save --dev babel-loader babel-core babel-preset-react babel-preset-env@next

# 安装create-react-app
npm install -g create-react-app

# 切换到插件项目根目录
cd sql-translator

# 新建项目
create-react-app react-client

# 切换到前端项目根目录
cd react-client

# 安装ring-ui
npm install @jetbrains/ring-ui

验证前端项目

替换前端项目src/App.js内容为

import '@jetbrains/ring-ui/dist/style.css';

import alertService from '@jetbrains/ring-ui/dist/alert-service/alert-service';
import Button from '@jetbrains/ring-ui/dist/button/button';


function App() {
  return (
    <Button onClick={() => alertService.successMessage('Hello world')}>
      Click me
    </Button>
  );
}

export default App;

在前端项目根目录下执行 npm start,查看浏览器是否正确显示按钮,点击按钮后,右下角是否出现消息

丰富前端项目

在插件项目resource目录下新建一个html目录
项目中用到了react-codemirroraxios等,需要安装相关依赖:npm install @uiw/react-codemirror axios @codemirror/lang-sql @jetbrains/icons
修改package.json中build脚本,方便每次构建完成后自动拷贝静态资源到目标位置

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build && rm -rf ../src/main/resources/html/* && cp -r build/* ../src/main/resources/html/",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }

App.js

import './App.css';
import '@jetbrains/ring-ui/dist/style.css';

import React, {Component} from 'react'
import Button from '@jetbrains/ring-ui/dist/button/button';
import compareIcon from '@jetbrains/icons/compare';
import Select from '@jetbrains/ring-ui/dist/select/select';
import Icon from '@jetbrains/ring-ui/dist/icon/icon';
import Input, {Size} from '@jetbrains/ring-ui/dist/input/input';
import Checkbox from '@jetbrains/ring-ui/dist/checkbox/checkbox';
import Theme, {ThemeProvider, ThemedWrapper} from '@jetbrains/ring-ui/dist/global/theme';
import CodeMirror from '@uiw/react-codemirror';
import {sql} from '@codemirror/lang-sql';
import axios from "axios"

export default class App extends Component {

  constructor(props) {
    super(props);
    this.changeHandle = this.changeHandle.bind(this);
    this.selectHandle = this.selectHandle.bind(this);
    const params = new URLSearchParams(window.location.search);
    this.state = {
      "from-dialect": "DEFAULT",
      "from-search-path": "PUBLIC",
      "from-unknown-functions": false,
      "from-date-format": "YYYY-MM-DD",
      "from-timestamp-format": "YYYY-MM-DD HH24:MI:SS.FF",
      "from-retain-comments-between-queries": false,
      "from-ignore-comments": true,
      "from-ignore-comment-start": "[jooq ignore start]",
      "from-ignore-comment-stop": "[jooq ignore stop]",
      "to-dialect": "DEFAULT",
      "to-keywords": "LOWER",
      "to-name-case": "AS_IS",
      "to-name-quoted": "EXPLICIT_DEFAULT_QUOTED",
      "to-param-type": "NAMED",
      "to-patterns": "OFF",
      "to-join-style": "DEFAULT",
      "to-qualify": "WHEN_NEEDED",
      "to-rownum": "WHEN_NEEDED",
      "to-inline-cte": "WHEN_NEEDED",
      "to-group-by-column-index": "WHEN_NEEDED",
      "to-unnecessary-arithmetic": "INTERNAL",
      "to-field-as": "DEFAULT",
      "to-table-as": "DEFAULT",
      "to-inner-keyword": "DEFAULT",
      "to-outer-keyword": "DEFAULT",
      sql: "",
      translateResult: "",
      loading: false,
      theme: params.get("theme")
    }
  }

  fromDialect = [
    {label: 'No specific dialect', key: 'DEFAULT', type: 'from-dialect'},
    {label: 'BigQuery', key: 'BIGQUERY', type: 'from-dialect'},
    {label: 'CockroachDB', key: 'COCKROACHDB', type: 'from-dialect'},
    {label: 'DB2 LUW', key: 'DB2', type: 'from-dialect'},
    {label: 'Derby', key: 'DERBY', type: 'from-dialect'},
    {label: 'Exasol', key: 'EXASOL', type: 'from-dialect'},
    {label: 'Firebird', key: 'FIREBIRD', type: 'from-dialect'},
    {label: 'H2', key: 'H2', type: 'from-dialect'},
    {label: 'HANA', key: 'HANA', type: 'from-dialect'},
    {label: 'HSQLDB', key: 'HSQLDB', type: 'from-dialect'},
    {label: 'Ignite', key: 'IGNITE', type: 'from-dialect'},
    {label: 'Informix', key: 'INFORMIX', type: 'from-dialect'},
    {label: 'Ingres', key: 'INGRES', type: 'from-dialect'},
    {label: 'MariaDB', key: 'MARIADB', type: 'from-dialect'},
    {label: 'MemSQL (SingleStore)', key: 'MEMSQL', type: 'from-dialect'},
    {label: 'MySQL', key: 'MYSQL', type: 'from-dialect'},
    {label: 'MS Access', key: 'ACCESS', type: 'from-dialect'},
    {label: 'Oracle', key: 'ORACLE', type: 'from-dialect'},
    {label: 'PostgreSQL', key: 'POSTGRES', type: 'from-dialect'},
    {label: 'Redshift', key: 'REDSHIFT', type: 'from-dialect'},
    {label: 'Snowflake', key: 'SNOWFLAKE', type: 'from-dialect'},
    {label: 'SQL Data Warehouse (Azure Synapse Analytics)', key: 'SQLDATAWAREHOUSE', type: 'from-dialect'},
    {label: 'SQLite', key: 'SQLITE', type: 'from-dialect'},
    {label: 'SQL Server', key: 'SQLSERVER', type: 'from-dialect'},
    {label: 'Sybase ASE', key: 'ASE', type: 'from-dialect'},
    {label: 'Sybase SQL Anywhere', key: 'SYBASE', type: 'from-dialect'},
    {label: 'Teradata', key: 'TERADATA', type: 'from-dialect'},
    {label: 'Vertica', key: 'VERTICA', type: 'from-dialect'},
    {label: 'YugabyteDB', key: 'YUGABYTEDB', type: 'from-dialect'},
  ];
  toDialect = [
    {label: 'No specific dialect', key: 'DEFAULT', type: 'to-dialect'},
    {label: 'Aurora MySQL', key: 'AURORA_MYSQL', type: 'to-dialect'},
    {label: 'Aurora PostgreSQL', key: 'AURORA_POSTGRES', type: 'to-dialect'},
    {label: 'BigQuery', key: 'BIGQUERY', type: 'to-dialect'},
    {label: 'CockroachDB', key: 'COCKROACHDB', type: 'to-dialect'},
    {label: 'DB2 LUW 9', key: 'DB2_9', type: 'to-dialect'},
    {label: 'DB2 LUW 10', key: 'DB2_10', type: 'to-dialect'},
    {label: 'DB2 LUW 11', key: 'DB2_11', type: 'to-dialect'},
    {label: 'DB2 LUW (latest version)', key: 'DB2', type: 'to-dialect'},
    {label: 'Derby', key: 'DERBY', type: 'to-dialect'},
    {label: 'Exasol', key: 'EXASOL', type: 'to-dialect'},
    {label: 'Firebird 2.5', key: 'FIREBIRD_2_5', type: 'to-dialect'},
    {label: 'Firebird 3.0', key: 'FIREBIRD_3_0', type: 'to-dialect'},
    {label: 'Firebird 4.0', key: 'FIREBIRD_4_0', type: 'to-dialect'},
    {label: 'Firebird (latest version)', key: 'FIREBIRD', type: 'to-dialect'},
    {label: 'H2 1.4', key: 'H2_1_4_200', type: 'to-dialect'},
    {label: 'H2 2.0', key: 'H2_2_0_202', type: 'to-dialect'},
    {label: 'H2 (latest version)', key: 'H2', type: 'to-dialect'},
    {label: 'HANA', key: 'HANA', type: 'to-dialect'},
    {label: 'HSQLDB', key: 'HSQLDB', type: 'to-dialect'},
    {label: 'Ignite', key: 'IGNITE', type: 'to-dialect'},
    {label: 'Informix', key: 'INFORMIX', type: 'to-dialect'},
    {label: 'Ingres', key: 'INGRES', type: 'to-dialect'},
    {label: 'MariaDB 10.0', key: 'MARIADB_10_0', type: 'to-dialect'},
    {label: 'MariaDB 10.1', key: 'MARIADB_10_1', type: 'to-dialect'},
    {label: 'MariaDB 10.2', key: 'MARIADB_10_2', type: 'to-dialect'},
    {label: 'MariaDB 10.3', key: 'MARIADB_10_3', type: 'to-dialect'},
    {label: 'MariaDB 10.4', key: 'MARIADB_10_4', type: 'to-dialect'},
    {label: 'MariaDB 10.5', key: 'MARIADB_10_5', type: 'to-dialect'},
    {label: 'MariaDB 10.6', key: 'MARIADB_10_6', type: 'to-dialect'},
    {label: 'MariaDB (latest version)', key: 'MARIADB', type: 'to-dialect'},
    {label: 'MemSQL (SingleStore)', key: 'MEMSQL', type: 'to-dialect'},
    {label: 'MySQL 5.7', key: 'MYSQL_5_7', type: 'to-dialect'},
    {label: 'MySQL 8.0', key: 'MYSQL_8_0', type: 'to-dialect'},
    {label: 'MySQL 8.0.19', key: 'MYSQL_8_0_19', type: 'to-dialect'},
    {label: 'MySQL (latest version)', key: 'MYSQL', type: 'to-dialect'},
    {label: 'MS Access', key: 'ACCESS', type: 'to-dialect'},
    {label: 'Oracle 10g', key: 'ORACLE10G', type: 'to-dialect'},
    {label: 'Oracle 11g', key: 'ORACLE11G', type: 'to-dialect'},
    {label: 'Oracle 12c', key: 'ORACLE12C', type: 'to-dialect'},
    {label: 'Oracle 18c', key: 'ORACLE18C', type: 'to-dialect'},
    {label: 'Oracle 20c', key: 'ORACLE20C', type: 'to-dialect'},
    {label: 'Oracle (latest version)', key: 'ORACLE', type: 'to-dialect'},
    {label: 'PostgreSQL 9.3', key: 'POSTGRES_9_3', type: 'to-dialect'},
    {label: 'PostgreSQL 9.4', key: 'POSTGRES_9_4', type: 'to-dialect'},
    {label: 'PostgreSQL 9.5', key: 'POSTGRES_9_5', type: 'to-dialect'},
    {label: 'PostgreSQL 10', key: 'POSTGRES_10', type: 'to-dialect'},
    {label: 'PostgreSQL 11', key: 'POSTGRES_11', type: 'to-dialect'},
    {label: 'PostgreSQL 12', key: 'POSTGRES_12', type: 'to-dialect'},
    {label: 'PostgreSQL 13', key: 'POSTGRES_13', type: 'to-dialect'},
    {label: 'PostgreSQL 14', key: 'POSTGRES_14', type: 'to-dialect'},
    {label: 'PostgreSQL 15', key: 'POSTGRES_15', type: 'to-dialect'},
    {label: 'PostgreSQL (latest version)', key: 'POSTGRES', type: 'to-dialect'},
    {label: 'Redshift', key: 'REDSHIFT', type: 'to-dialect'},
    {label: 'Snowflake', key: 'SNOWFLAKE', type: 'to-dialect'},
    {label: 'SQL Data Warehouse (Azure Synapse Analytics)', key: 'SQLDATAWAREHOUSE', type: 'to-dialect'},
    {label: 'SQLite 3.25', key: 'SQLITE_3_25', type: 'to-dialect'},
    {label: 'SQLite 3.28', key: 'SQLITE_3_28', type: 'to-dialect'},
    {label: 'SQLite 3.30', key: 'SQLITE_3_30', type: 'to-dialect'},
    {label: 'SQLite (latest version)', key: 'SQLITE', type: 'to-dialect'},
    {label: 'SQL Server 2008', key: 'SQLSERVER2008', type: 'to-dialect'},
    {label: 'SQL Server 2012', key: 'SQLSERVER2012', type: 'to-dialect'},
    {label: 'SQL Server 2014', key: 'SQLSERVER2014', type: 'to-dialect'},
    {label: 'SQL Server 2016', key: 'SQLSERVER2016', type: 'to-dialect'},
    {label: 'SQL Server 2017', key: 'SQLSERVER2017', type: 'to-dialect'},
    {label: 'SQL Server 2022', key: 'SQLSERVER2022', type: 'to-dialect'},
    {label: 'SQL Server (latest version)', key: 'SQLSERVER', type: 'to-dialect'},
    {label: 'Sybase ASE 12.5', key: 'ASE_12_5', type: 'to-dialect'},
    {label: 'Sybase ASE 15.5', key: 'ASE_15_5', type: 'to-dialect'},
    {label: 'Sybase ASE 15.7', key: 'ASE_15_7', type: 'to-dialect'},
    {label: 'Sybase ASE 16.0', key: 'ASE_16_0', type: 'to-dialect'},
    {label: 'Sybase ASE (latest version)', key: 'ASE', type: 'to-dialect'},
    {label: 'Sybase SQL Anywhere', key: 'SYBASE', type: 'to-dialect'},
    {label: 'Teradata', key: 'TERADATA', type: 'to-dialect'},
    {label: 'Vertica', key: 'VERTICA', type: 'to-dialect'},
    {label: 'YugabyteDB', key: 'YUGABYTEDB', type: 'to-dialect'},
    {label: 'jOOQ (Java)', key: 'JAVA', type: 'to-dialect'},
  ];

  toKeywords = [
    {label: 'lower case', key: 'LOWER', type: 'to-keywords'},
    {label: 'UPPER CASE', key: 'UPPER', type: 'to-keywords'},
    {label: 'Pascal Case', key: 'PASCAL', type: 'to-keywords'},
  ];

  identifierCase = [
    {label: 'Unmodified', key: 'AS_IS', type: 'to-name-case'},
    {label: 'lower case', key: 'LOWER', type: 'to-name-case'},
    {label: 'lower case (if unquoted)', key: 'LOWER_IF_UNQUOTED', type: 'to-name-case'},
    {label: 'UPPER', key: 'UPPER CASE', type: 'to-name-case'},
    {label: 'UPPER CASE (if unquoted)', key: 'UPPER_IF_UNQUOTED', type: 'to-name-case'},
  ];

  identifierQuoting = [
    {label: 'Always', key: 'ALWAYS', type: 'to-name-quoted'},
    {label: 'Unmodified (default quoted)', key: 'EXPLICIT_DEFAULT_QUOTED', type: 'to-name-quoted'},
    {label: 'Unmodified (default unquoted)', key: 'EXPLICIT_DEFAULT_UNQUOTED', type: 'to-name-quoted'},
    {label: 'Never', key: 'NEVER', type: 'to-name-quoted'},
  ];

  bindVariables = [
    {label: 'Named', key: 'NAMED', type: 'to-param-type'},
    {label: 'Indexed', key: 'INDEXED', type: 'to-param-type'},
    {label: 'Force Indexed', key: 'FORCE_INDEXED', type: 'to-param-type'},
  ];

  toPatterns = [
    {label: 'Unmodified', key: 'OFF', type: 'to-patterns'},
    {label: 'Transform patterns', key: 'ON', type: 'to-patterns'},
  ];

  joinStyle = [
    {label: 'Unmodified', key: 'DEFAULT', type: 'to-join-style'},
    {label: 'Oracle style to ANSI join', key: 'ANSI', type: 'to-join-style'},
    {label: 'ANSI join to Oracle style', key: 'ORACLE', type: 'to-join-style'},
  ];

  qualify = [
    {label: 'Transform QUALIFY', key: 'ALWAYS', type: 'to-qualify'},
    {label: 'Transform QUALIFY when not supported', key: 'WHEN_NEEDED', type: 'to-qualify'},
    {label: "Don't transform QUALIFY", key: 'NEVER', type: 'to-qualify'},
  ];

  rownum = [
    {label: 'Transform ROWNUM', key: 'ALWAYS', type: 'to-rownum'},
    {label: 'Transform ROWNUM when not supported', key: 'WHEN_NEEDED', type: 'to-rownum'},
    {label: "Don't transform ROWNUM", key: 'NEVER', type: 'to-rownum'},
  ];

  inlineCte = [
    {label: 'Transform inline CTE', key: 'ALWAYS', type: 'to-inline-cte'},
    {label: 'Transform inline CTE when not supported', key: 'WHEN_NEEDED', type: 'to-inline-cte'},
    {label: "Don't transform inline CTE", key: 'NEVER', type: 'to-inline-cte'},
  ];

  groupByColumnIndex = [
    {label: 'Transform GROUP BY <column index>', key: 'ALWAYS', type: 'to-group-by-column-index'},
    {label: 'Transform GROUP BY <column index> when not supported', key: 'WHEN_NEEDED', type: 'to-group-by-column-index'},
    {label: "Don't transform GROUP BY <column index>", key: 'NEVER', type: 'to-group-by-column-index'},
  ];

  unnecessaryArithmetic = [
    {label: 'Internal (from emulations)', key: 'INTERNAL', type: 'to-unnecessary-arithmetic'},
    {label: 'Never', key: 'NEVER', type: 'to-unnecessary-arithmetic'},
    {label: "Always", key: 'ALWAYS', type: 'to-unnecessary-arithmetic'},
  ];

  toFieldAs = [
    {label: 'Default', key: 'DEFAULT', type: 'to-field-as'},
    {label: 'Off', key: 'OFF', type: 'to-field-as'},
    {label: "On", key: 'ON', type: 'to-field-as'},
  ];

  toTableAs = [
    {label: 'Default', key: 'DEFAULT', type: 'to-table-as'},
    {label: 'Off', key: 'OFF', type: 'to-table-as'},
    {label: "On", key: 'ON', type: 'to-table-as'},
  ];

  toInnerKeyword = [
    {label: 'Default', key: 'DEFAULT', type: 'to-inner-keyword'},
    {label: 'Off', key: 'OFF', type: 'to-inner-keyword'},
    {label: "On", key: 'ON', type: 'to-inner-keyword'},
  ];

  toOuterKeyword = [
    {label: 'Default', key: 'DEFAULT', type: 'to-outer-keyword'},
    {label: 'Off', key: 'OFF', type: 'to-outer-keyword'},
    {label: "On", key: 'ON', type: 'to-outer-keyword'},
  ];

  setStateValue = (e) => {
    const targetName = e.type;
    const targetValue = e.key;
    this.setState({[targetName]: targetValue}, () => {
      console.log(this.state[targetName])
    });
    console.log(this.state);
  }

  translate = () => {
    this.setState({loading: true}, () => {
      const header = {headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Access-Control-Allow-Origin': '*'}};
      axios.post("translate", {
        ...this.state
      }, header).then((res) => {
        console.log(res)
        this.setState({
          translateResult: res.data,
          loading: false
        })
      }).catch((reason) => {
        this.setState({
          translateResult: reason.message,
          loading: false
        })
      });
    });
  }

  changeHandle(e) {
    e.preventDefault()
    let key = e.target.dataset.key;
    this.setState({[key]: e.target.value}, () => {
      console.log(this.state[key])
    })
  }

  selectHandle(e) {
    console.log(e)
    let key = e.target.dataset.key;
    this.setState({[key]: e.target.checked}, () => {
      console.log(this.state[key])
    })
  }

  render() {
    const snapshot = this.state;
    const theme = this.state.theme && this.state.theme === 'dark' ? "dark" : "light";
    const jbTheme = this.state.theme && this.state.theme === 'dark' ? Theme.DARK : Theme.LIGHT;
    return <ThemeProvider theme={jbTheme} passToPopups>
      <div style={{padding: "10px"}} className="themed-wrapper">
        <Select className={"gap-right"} filter={true} label={"---"} selected={this.fromDialect.filter((i) => i.key === snapshot['from-dialect'])} onChange={(e) => this.setStateValue(e)} data={this.fromDialect}></Select>
        <Icon glyph={compareIcon} className={"gap-large"} color={Icon.Color.BLUE}/>
        <Select className={"gap-right"} filter={true} label={"---"} selected={this.toDialect.filter((i) => i.key === snapshot['to-dialect'])} onChange={(e) => this.setStateValue(e)} data={this.toDialect}></Select>
        <Button primary onClick={this.translate} loader={this.state.loading}>
          Translate
        </Button>
        <div className="ring-loader-inline" style={{marginLeft: "5px", display: this.state.loading ? "inline-block" : "none"}}/>
        <div style={{display: "flex"}}>
          <div style={{flex: "1", marginRight: "5px", marginTop: "15px"}}>
            <CodeMirror
              height="400px"
              extensions={[sql()]}
              theme={theme}
              width="calc(100vw/2 - 25px)"
              onChange={(value, view) => {
                this.setState({sql: value})
              }}
            />
            <ThemeProvider theme={jbTheme} className="dark inputs">
              <Input size={Size.FULL} data-key="from-search-path" label="Search path, comma separated" onChange={this.changeHandle} value={snapshot["from-search-path"]}/>
              <div className="themed-wrapper-with-top-gap">
                <Checkbox data-key="from-unknown-functions" label="Parse unknown functions" onChange={this.selectHandle} checked={snapshot["from-unknown-functions"]}/>
              </div>
              <Input size={Size.FULL} data-key="from-date-format" label="Date Format" onChange={this.changeHandle} value={snapshot["from-date-format"]}/>
              <Input size={Size.FULL} data-key="from-timestamp-format" label="Timestamp Format" onChange={this.changeHandle} value={snapshot["from-timestamp-format"]}/>
              <div className="themed-wrapper-with-top-gap">
                <Checkbox data-key="from-retain-comments-between-queries" label="Retain comments between queries" onChange={this.selectHandle} checked={snapshot["from-retain-comments-between-queries"]}/>
              </div>
              <div className="themed-wrapper-with-top-gap">
                <Checkbox data-key="from-ignore-comments" label="Enable ignore comment syntax" onChange={this.selectHandle} checked={snapshot["from-ignore-comments"]}/>
              </div>
              <Input size={Size.FULL} data-key="from-ignore-comment-start" label="Ignore comment start token" onChange={this.changeHandle} value={snapshot["from-ignore-comment-start"]}/>
              <Input size={Size.FULL} data-key="from-ignore-comment-stop" label="Ignore comment stop token" onChange={this.changeHandle} value={snapshot["from-ignore-comment-stop"]}/>
            </ThemeProvider>
          </div>
          <div style={{flex: "1", marginLeft: "5px", marginTop: "15px"}}>
            <CodeMirror
              value={this.state.translateResult}
              height="400px"
              width="calc(100vw/2 - 25px)"
              extensions={[sql()]}
              theme={theme}
            />
            <ThemeProvider theme={jbTheme} className="dark inputs">
              <Select size={Size.FULL} selectedLabel="Keywords" selected={this.toKeywords.filter((i) => i.key === this.state['to-keywords'])} onChange={(e) => this.setStateValue(e)} data={this.toKeywords}></Select>
              <Select size={Size.FULL} selectedLabel="Identifier Case" selected={this.identifierCase.filter((i) => i.key === this.state['to-name-case'])} onChange={(e) => this.setStateValue(e)} data={this.identifierCase}></Select>
              <Select size={Size.FULL} selectedLabel="Identifier Quoting" selected={this.identifierQuoting.filter((i) => i.key === this.state['to-name-quoted'])} onChange={(e) => this.setStateValue(e)} data={this.identifierQuoting}></Select>
              <Select size={Size.FULL} selectedLabel="Bind variables" selected={this.bindVariables.filter((i) => i.key === this.state['to-param-type'])} onChange={(e) => this.setStateValue(e)} data={this.bindVariables}></Select>
              <Select size={Size.FULL} selectedLabel="Patterns (experimental)" selected={this.toPatterns.filter((i) => i.key === this.state['to-patterns'])} onChange={(e) => this.setStateValue(e)} data={this.toPatterns}></Select>
              <Select size={Size.FULL} selectedLabel="Join style" selected={this.joinStyle.filter((i) => i.key === this.state['to-join-style'])} onChange={(e) => this.setStateValue(e)} data={this.joinStyle}></Select>
              <Select size={Size.FULL} selectedLabel="QUALIFY" selected={this.qualify.filter((i) => i.key === this.state['to-qualify'])} onChange={(e) => this.setStateValue(e)} data={this.qualify}></Select>
              <Select size={Size.FULL} selectedLabel="ROWNUM" selected={this.rownum.filter((i) => i.key === this.state['to-rownum'])} onChange={(e) => this.setStateValue(e)} data={this.rownum}></Select>
              <Select size={Size.FULL} selectedLabel="inline CTE" selected={this.inlineCte.filter((i) => i.key === this.state['to-inline-cte'])} onChange={(e) => this.setStateValue(e)} data={this.inlineCte}></Select>
              <Select size={Size.FULL} selectedLabel="GROUP BY <column index>" selected={this.groupByColumnIndex.filter((i) => i.key === this.state['to-group-by-column-index'])} onChange={(e) => this.setStateValue(e)} data={this.groupByColumnIndex}></Select>
              <Select size={Size.FULL} selectedLabel="Remove unnecessary arithmetic" selected={this.unnecessaryArithmetic.filter((i) => i.key === this.state['to-unnecessary-arithmetic'])} onChange={(e) => this.setStateValue(e)} data={this.unnecessaryArithmetic}></Select>
              <Select size={Size.FULL} selectedLabel="AS keyword in SELECT" selected={this.toFieldAs.filter((i) => i.key === this.state['to-field-as'])} onChange={(e) => this.setStateValue(e)} data={this.toFieldAs}></Select>
              <Select size={Size.FULL} selectedLabel="AS keyword in FROM" selected={this.toTableAs.filter((i) => i.key === this.state['to-table-as'])} onChange={(e) => this.setStateValue(e)} data={this.toTableAs}></Select>
              <Select size={Size.FULL} selectedLabel="INNER keyword in JOIN" selected={this.toInnerKeyword.filter((i) => i.key === this.state['to-inner-keyword'])} onChange={(e) => this.setStateValue(e)} data={this.toInnerKeyword}></Select>
              <Select size={Size.FULL} selectedLabel="OUTER keyword in JOIN" selected={this.toOuterKeyword.filter((i) => i.key === this.state['to-outer-keyword'])} onChange={(e) => this.setStateValue(e)} data={this.toOuterKeyword}></Select>
            </ThemeProvider>
          </div>
        </div>
      </div>
    </ThemeProvider>
  }
  ;
}

App.css

.App {
    text-align: center;
}

.gap-right {
    margin-right: 5px;
}

.gap-large {
    margin-right: 10px;
    margin-left: 5px;
}

.themed-wrapper {
    border: solid 1px var(--ring-borders-color);
    border-radius: var(--ring-border-radius);
    background-color: var(--ring-content-background-color);
    padding: var(--ring-unit);
}

.themed-wrapper-with-top-gap {
    border: solid 1px var(--ring-borders-color);
    border-radius: var(--ring-border-radius);
    background-color: var(--ring-content-background-color);
    margin-top: var(--ring-unit);
    padding: var(--ring-unit);
}

前端主要逻辑为:

  1. 复刻JOOQ SQL Translate页面,左右两侧的SQL输入和展示组件用codemirror,实现SQL代码高亮和提示
  2. 点击翻译按钮,用axios发送/translate请求,参数和JOOQ SQL Translate一样
  3. 请求成功后,将返回值填充到右侧代码展示区

npm start后可看到页面基本和JOOQ SQL Translate页面相差无几,在浏览器地址栏后添加?theme=dark?theme=light可看到风格切换。
npm run build后,插件的resource/html目录下会生成用户界面静态资源。

丰富插件内容

build.gradle.kts添加okhttp3依赖:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.10.0")
}

新建com.github.clyoudu.sqltrans.frame包,在该包下创建TranslatorFrame.java:

package com.github.clyoudu.sqltrans.frame;

import java.awt.*;

import javax.swing.*;

import com.intellij.ui.jcef.JBCefBrowser;
import com.intellij.ui.jcef.JBCefClient;
import com.intellij.util.ui.UIUtil;

import com.github.clyoudu.sqltrans.jcefhandler.LocalRequestHandler;

/**
 * TranslatorFrame.
 *
 * @author leichen
 * @since 1.0, 2022/12/27 5:28 PM
 */
public class TranslatorFrame extends JFrame {

    private JBCefBrowser jbCefBrowser;

    private JBCefClient jbCefClient;

    private final JPanel dialogPanel;

    private TranslatorFrame() {
        super("SQLTranslator");
        dialogPanel = new JPanel(new BorderLayout());
        add(dialogPanel);

    }

    @Override
    public void setVisible(boolean b) {
        if (b) {
            setSize(1024, 750);
            setLocationRelativeTo(null);
            jbCefBrowser = new JBCefBrowser();
            jbCefClient = jbCefBrowser.getJBCefClient();
            jbCefClient.addRequestHandler(new LocalRequestHandler(), jbCefBrowser.getCefBrowser());
            // 将 JBCefBrowser 的UI控件设置到Panel中
            dialogPanel.add(jbCefBrowser.getComponent(), BorderLayout.CENTER);

            // 刷新组件
            EventQueue.invokeLater(TranslatorFrame.this::repaint);

            String theme = UIUtil.isUnderDarcula() ? "dark" : "light";
            jbCefBrowser.loadURL("http://localhost:7654/index.html?theme=" + theme);
        } else {
            jbCefClient.dispose();
            jbCefBrowser.dispose();
        }
        super.setVisible(b);
    }

    public static TranslatorFrame getInstance() {
        return TranslatorFrame.SingletonRegistryHolder.INSTANCE;
    }

    private static class SingletonRegistryHolder {

        private static final TranslatorFrame INSTANCE = new TranslatorFrame();
    }
}

新建com.github.clyoudu.sqltrans.jecefhandler包,创建
LocalRequestHandler.java

package com.github.clyoudu.sqltrans.jcefhandler;

import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefRequestHandlerAdapter;
import org.cef.handler.CefResourceRequestHandler;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;

/**
 * LocalRequestHandler.
 *
 * @author leichen
 * @since 1.0, 2022/12/28 5:10 PM
 */
public class LocalRequestHandler extends CefRequestHandlerAdapter {

    @Override
    public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, CefRequest request,
        boolean isNavigation, boolean isDownload, String requestInitiator, BoolRef disableDefaultHandling) {
        return new LocalResourceRequestHandler();
    }

}

LocalResourceRequestHandler.java

package com.github.clyoudu.sqltrans.jcefhandler;

import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefResourceHandler;
import org.cef.handler.CefResourceRequestHandlerAdapter;
import org.cef.network.CefRequest;

/**
 * LocalResourceRequestHandler.
 *
 * @author leichen
 * @since 1.0, 2022/12/28 5:11 PM
 */
public class LocalResourceRequestHandler extends CefResourceRequestHandlerAdapter {

    private static final String TRANSLATE = "translate";

    private static final String LOCALHOST = "http://localhost:7654/";

    @Override
    public CefResourceHandler getResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request) {
        String url = request.getURL();
        String file = url.replace(LOCALHOST, "").replaceAll("\\?.*", "");
        if (file.equals(TRANSLATE)) {
            return new HttpClientResourceHandler(request.getPostData());
        }
        return new LocalStaticResourceHandler(file);
    }

}

StCefResourceHandlerAdapter.java

package com.github.clyoudu.sqltrans.jcefhandler;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandlerAdapter;
import org.cef.misc.IntRef;
import org.cef.network.CefRequest;

/**
 * StCefResourceHandlerAdapter.
 *
 * @author leichen
 * @since 1.0, 2022/12/28 5:16 PM
 */
public class StCefResourceHandlerAdapter extends CefResourceHandlerAdapter {

    String html;

    int startPos = 0;

    @Override
    public boolean processRequest(CefRequest request, CefCallback callback) {
        startPos = 0;
        callback.Continue();
        return true;
    }

    @Override
    public boolean readResponse(byte[] dataOut, int bytesToRead, IntRef intRef, CefCallback callback) {
        byte[] bytes = html.getBytes(StandardCharsets.UTF_8);
        int length = bytes.length;
        if (startPos >= length) {
            return false;
        }

        int endPos = startPos + bytesToRead;
        byte[] dataToSend =
            (endPos > length) ? Arrays.copyOfRange(bytes, startPos, length) : Arrays.copyOfRange(bytes, startPos,
                endPos);

        ByteBuffer result = ByteBuffer.wrap(dataOut);
        result.put(dataToSend);
        intRef.set(dataToSend.length);

        startPos = endPos;
        return true;
    }

}

LocalStaticResourceHandler.java

package com.github.clyoudu.sqltrans.jcefhandler;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.compress.utils.IOUtils;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefResponse;

/**
 * StaticResourceHandler.
 *
 * @author leichen
 * @since 1.0, 2022/12/28 5:13 PM
 */
public class LocalStaticResourceHandler extends StCefResourceHandlerAdapter {

    private final String file;

    public LocalStaticResourceHandler(String file) {
        this.file = file;
        InputStream fileInputStream = LocalStaticResourceHandler.class.getClassLoader().getResourceAsStream("html/" + file);
        try {
            html = new String(IOUtils.toByteArray(fileInputStream));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void getResponseHeaders(CefResponse response, IntRef responseLength, StringRef redirectUrl) {
        responseLength.set(html.length());
        String ext = file.substring(file.lastIndexOf('.') + 1);
        switch (ext) {
            case "html":
                response.setMimeType("text/html");
                break;
            case "js":
                response.setMimeType("text/javascript; charset=utf-8");
                break;
            case "css":
                response.setMimeType("text/css; charset=utf-8");
                break;
            default:
                break;
        }
        response.setStatus(200);
    }

}

HttpClientResourceHandler.java

package com.github.clyoudu.sqltrans.jcefhandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Vector;
import java.util.stream.Collectors;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefPostData;
import org.cef.network.CefPostDataElement;
import org.cef.network.CefResponse;

/**
 * HttpClientResourceHandler.
 *
 * @author leichen
 * @since 1.0, 2022/12/28 5:18 PM
 */
public class HttpClientResourceHandler extends StCefResourceHandlerAdapter {

    private static final OkHttpClient CLIENT = new OkHttpClient().newBuilder().build();

    public HttpClientResourceHandler(CefPostData postData) {
        Vector<CefPostDataElement> elements = new Vector<>();
        postData.getElements(elements);
        CefPostDataElement el = elements.get(0);
        int numBytes = el.getBytesCount();

        byte[] readBytes = new byte[numBytes];
        el.getBytes(numBytes, readBytes);

        String readString = new String(readBytes).trim();
        String[] stringPairs = readString.split("&");
        java.util.List<String> formData = new ArrayList<>();
        for (String s : stringPairs) {
            String[] params = s.split("=");
            if (params.length <= 1) {
                formData.add(params[0] + "= ");
            } else {
                formData.add(params[0] + "=" + params[1]);
            }

        }

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8");
        RequestBody body = RequestBody.create(mediaType, formData.stream().collect(Collectors.joining("&")));
        Request request = new Request.Builder().url("https://www.jooq.org/translate/translate").method("POST", body)
            .addHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8").build();

        try (Response response = CLIENT.newCall(request).execute();) {
            html = response.body().string();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void getResponseHeaders(CefResponse response, IntRef responseLength, StringRef redirectUrl) {
        responseLength.set(html.length());
        response.setMimeType("text/html; charset=UTF-8");
        response.setHeaderByName("content-encoding", "gzip", true);
        response.setHeaderByName("cache-control", "no-store, no-cache, must-revalidate", true);
        response.setStatus(200);
    }

}

新建com.github.clyoudu.sqltrans.actions包,新建PopupWindowAction.java

package com.github.clyoudu.sqltrans.actions;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;

import com.github.clyoudu.sqltrans.frame.TranslatorFrame;

/**
 * PopupWindowAction.
 *
 * @author leichen
 * @since 1.0, 2022/12/27 5:33 PM
 */
public class PopupWindowAction extends AnAction {

    @Override
    public void actionPerformed(@NotNull AnActionEvent event) {
        TranslatorFrame translatorFrame = TranslatorFrame.getInstance();
        if (translatorFrame.isVisible()) {
            translatorFrame.requestFocus();
        } else {
            translatorFrame.setVisible(true);
        }
    }

}

修改resource/plugin.xml

<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
    <!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. -->
    <id>com.clyoudu.sqltrans.sql-translator</id>

    <!-- Public plugin name should be written in Title Case.
         Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
    <name>Sql-translator</name>

    <!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
    <vendor email="1060659155@qq.com" url="https://github.com/clyoudu">clyoudu</vendor>

    <!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager.
         Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag.
         Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description -->
    <description><![CDATA[
    Sql dialect translator with jooq online translator.
  ]]></description>

    <!-- Product and plugin compatibility requirements.
         Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
    <depends>com.intellij.modules.platform</depends>

    <!-- Extension points defined by the plugin.
         Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html -->
    <extensions defaultExtensionNs="com.intellij">

    </extensions>

    <actions>
        <!-- Add your actions here -->
        <action id="com.github.clyoudu.sqltrans.actions.PopupWindowAction"
                class="com.github.clyoudu.sqltrans.actions.PopupWindowAction" text="SQL Translator"
                description="Open SQL translator window">
            <add-to-group group-id="ToolsMenu" anchor="last"/>
            <keyboard-shortcut keymap="$default" first-keystroke="control shift alt T"/>
        </action>
    </actions>
</idea-plugin>

jcefhandler主要逻辑为:

  1. 打开插件时,默认访问虚拟路径:http://localhost:7654/index.html
  2. 拦截所有请求,静态资源一律返回/resource/html路径下相同URI的文件内容
  3. 当请求路径为translate时,使用HttpClient发起代理请求,访问JOOQ SQL Translate接口

整个插件项目结构

.
└── main
    ├── java
    │   └── com
    │       └── szkingdom
    │           └── sqltrans
    │               └── sqltranslator
    │                   ├── actions
    │                   │   └── PopupWindowAction.java
    │                   ├── frame
    │                   │   └── TranslatorFrame.java
    │                   └── jcefhandler
    │                       ├── HttpClientResourceHandler.java
    │                       ├── LocalRequestHandler.java
    │                       ├── LocalResourceRequestHandler.java
    │                       ├── LocalStaticResourceHandler.java
    │                       └── StCefResourceHandlerAdapter.java
    └── resources
        ├── META-INF
        │   ├── plugin.xml
        │   └── pluginIcon.svg
        └── html
            ├── asset-manifest.json
            ├── favicon.ico
            ├── index.html
            ├── logo192.png
            ├── logo512.png
            ├── manifest.json
            ├── robots.txt
            └── static
                ├── css
                │   ├── main.3f1d99eb.css
                │   └── main.3f1d99eb.css.map
                ├── js
                │   ├── 5651.a6ce13cf.chunk.js
                │   ├── 5651.a6ce13cf.chunk.js.map
                │   ├── ...
                │   └── main.ae1dd244.js.map
                └── media
                    └── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg

双击右侧的Gradle->Run Plugin
在这里插入图片描述
插件效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

向 Windows 高级用户进阶,这 5 款效率工具帮你开路

工欲善其事&#xff0c;必先利其器。作为全球最多人使用的桌面操作系统&#xff0c;Windows 的使用效率与我们的工作学习息息相关。今天&#xff0c;小编就为大家整理了 10 款提高效率的利器&#xff0c;让你的 Windows 更具生产力。 1.文件预览——Seer MacOS 有一个非常方便…

优雅草YYC松鼠短视频2022年12月28日更新v5.1.6版本更新·修复因为消息提醒二开导致菜单栏无法显示·进一步完善推送

优雅草YYC松鼠短视频2022年12月28日更新v5.1.6版本更新修复因为消息提醒二开导致菜单栏无法显示进一步完善推送 更新日志 修复因为消息提醒二开导致菜单栏无法显示 继续进一步开发消息推送 目前消息推送登录以及推送判断返回刷新仍存在问题在开发中 目前goeasy官方也在更新关…

数据劫持基础

数据劫持数据劫持原理。属性描述符Object.defineProperty官网响应式原理![在这里插入图片描述](https://img-blog.csdnimg.cn/b9f900484f314334a0dc6139428b397c.png)getter和setterObject.defineProperty的不足proxy工作原理总结数据劫持原理。 数据劫持&#xff0c;指的是在…

Find My资讯|苹果 Find My 找到因交通事故坠崖的一名女子

美国加州圣贝纳迪诺县消防局在其官方 Facebook 更新动态&#xff0c;表示在接到家属报警&#xff0c;通过苹果 Find My 功能追踪家人的 iPhone&#xff0c;成功营救出在圣贝纳迪诺山区的 18 号高速公路上坠崖的一名妇女。 圣贝纳迪诺县消防局在帖子中表示这名妇女在圣诞节那天…

多线程与高并发(三)

【 day3课前复习 】&#xff1a; 【AtomicInteger】&#xff1a; 原子性——都是用CAS机制来实现。 【 expected , update 】: //有时候也会写三个值——你要修改的那个对象。 expected——期望值。&#xff08;旧值&#xff09; update——更新值。&#xff08;新值&#…

LeetCode刷题复盘笔记—一文搞懂动态规划之剑指 Offer 46. 把数字翻译成字符串问题(动态规划系列第三十四篇)

今日主要总结一下动态规划的一道题目&#xff0c;剑指 Offer 46. 把数字翻译成字符串 题目&#xff1a;剑指 Offer 46. 把数字翻译成字符串 Leetcode题目地址 题目描述&#xff1a; 给定一个数字&#xff0c;我们按照如下规则把它翻译为字符串&#xff1a;0 翻译成 “a” &am…

小型云台机械手的制作

1. 运动功能说明 小型云台机械手&#xff0c;下方的云台可以提供左右旋转和上下摆动的动作&#xff0c;与舵机夹爪配合可以完成简单的抓取和搬运。 2.结构说明 该样机由一个 R207小型舵机云台 上串联了一个 舵机夹爪模组 构成。 3. 运动功能实现 3.1 电子硬件 在这个示例中&a…

Redis集群系列五 —— 分区/片概念

什么是分区 分区就是将所存储的数据按照一定的规则存储在不同的存储服务或介质上&#xff0c;通过降低单服务或介质的数据量级来提升数据处理能力&#xff0c;从而达到拥有数据处理横向扩容的能力的目的。 还可简单的理解为分区就是将数据拆分到多个 Redis 实例的过程&#xf…

21. 合并两个有序链表播报文章

题目描述 这是一道难度为简单的题目&#xff0c;同时&#xff0c;这道题也是Leetcode148题中&#xff0c;链表归并排序中重要的组成部分。 题目描述 题目分析 本题的题目简单易懂&#xff0c;输入为两个有序链表&#xff0c;要求将链表合并为一个有序的链表。在此不在再赘述…

【消息中间件】RocketMQ如何实现Producer的负载均衡

目录 一、前言 二、实现Producer的负载均衡 1、负载均衡选取一条消息队列并且高可用 1.1、模拟随机递增取模消息队列数为5 1.2、模拟随机递增取模消息队列数为6 1.3、判断Broker代理是否可用 2、更新故障项维护startTimestamp字段 2.1、退避运算 2.2、更新故障项维护st…

干货 | 数字经济创新创业——数字经济下的商业模式与解决方案

下文整理自清华大学大数据能力提升项目能力提升模块课程“Innovation & Entrepreneurship for Digital Economy”&#xff08;数字经济创新创业课程)的精彩内容。主讲嘉宾&#xff1a;Kris Singh: CEO at SRII, Palo Alto, CaliforniaVisiting Professor of Tsinghua Unive…

卷积、转置卷积、膨胀卷积学习记录

Conv计算&#xff1a; 计算公示 1、pytorch中默认参数&#xff0c;以conv1d为例 torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_mode‘zeros’, deviceNone, dtypeNone&#xff09; 2、输出卷积尺寸&am…

MySQL常见内置函数及其使用

目录 1、聚合函数 2、日期函数 3、字符串函数 4、数学函数 5、其它函数 1、聚合函数 函数说明 COUNT([DISTINCT] expr) 返回查询到的数据的 数量SUM([DISTINCT] expr)返回查询到的数据的 总和&#xff0c;不是数字没有意义AVG([DISTINCT] expr)返回查询到的数据的 平均值…

数据蛙恢复软件替代产品有哪些?15款顶尖数据恢复软件清单

数据蛙恢复软件是一款国内数据恢复软件&#xff0c;可以在很多品牌的电脑上使用。但是你可能会遇到数据蛙恢复软件扫描不到需要恢复文件的情况。那么有没有更专业的数据恢复软件可以找到更多误删数据&#xff1f;本文将为你介绍最值的推荐的15个数据蛙恢复软件替代产品。 丢失…

Web兼容性测试的要点

对于网页的兼容性我们主要考虑的是各种浏览器对前台页面的兼容性&#xff0c;因为浏览器对页面的影响是最大的。 现在浏览器的种类越来越多&#xff0c;网页中展现出来的内容也越来越丰富&#xff0c;这些内容包括网页中的字体、图片、动画等&#xff0c;而且有些内容需要网页…

AlexNet学习笔记(2)

里面 有些东西 对于现在来说都是错误的 而且由大量的细节对于现在来说没有必要 而且是过度的enginnering 一篇论文的第一段通常是讲一则故事 我们在做什么研究 哪个方向 有什么东西然后为什么很重要 正则化 regularization好像没有那么重要&#xff0c;并不是最关键的 最关键…

前端监控系统的搭建

UI自动化测试库 puppeteer&#xff1a;https://zhuanlan.zhihu.com/p/524254998 - google出品 cypress - 据说比puppeteer好用 前端监控体系 性能监控 异常监控 行为监控&#xff1a;埋点体系 主动监控 被动监控 前端性能数据捕获&#xff1a; 打点方式&#xff0c;结…

pyqt5 QPainter绘制图形,并旋转

PyQt5 的绘图系统使用户可以在屏幕或打印设备上用相同的 API 绘图&#xff0c;QPainter 是用来进行 绘图操作的类&#xff0c;一般的绘图设备包括 QWidget、QPixmap、QImage 等&#xff0c;这些绘图设备为 QPainter 提供了一个“画布” QWidget 类是所有界面组件的基类&#xf…

【细节注入模型】

Detail-Injection-Model-Inspired Deep Fusion Network for Pansharpening &#xff08;细节注入模型启发的深度融合网络全色锐化算法&#xff09; 全色锐化是一种图像融合方法&#xff0c;其目的是将低空间分辨率的多光谱&#xff08;MS&#xff09;图像与高空间分辨率的全色…

FFmpeg学习笔记--FFplay简单过滤器、FFmpeg命令参数

目录 1--FFplay简单过滤器 2--FFmpeg命令参数 2-1 主要参数 2-1-1 -i设定输入流 2-1-2 -f设定输出格式 2-1-3 -ss设定开始时间 2-1-4 -t设定时间长度 2-1-5 代码实例 2-2 音频参数 2-2-1 -aframes设置输出的音频帧数 2-2-2 -b:a设置音频码率 2-2-3 -ar设置音频采样…