Java后台生成ECharts图片

前言

 通过echarts的jar包,Java后台生成一张图片,并把图片插入到word中。关于word插图片的代码在下一章。

 需要用到的工具PhantomJS,Echarts-convert.js,jquery.js,echarts.js

1.PhantomJS 介绍

PhantomJS是一个不需要浏览器的富客户端。 

官方介绍:PhantomJS是一个基于 WebKit 的服务器端JavaScript API。它全面支持web而不需浏览器支持,支持各种Web标准:DOM处理,CSS选择器, JSON,Canvas,和SVG。
PhantomJS常用于页面自动化,网络监测,网页截屏,以及无界面测试等。

通常我们使用PhantomJS作为爬虫工具。传统的爬虫只能单纯地爬取html的代码,对于js渲染的页面,就无法爬取,如Echarts统计图。而PhantomJS正可以解决此类问题。

 我们可以这么理解PhantomJS,PhantomJS是一个无界面、可运行脚本的谷歌浏览器。

1.1 PhantomJS下载安装

PhantomJS安装非常简单,官网http://phantomjs.org/download.html下载最新的安装包, 安装包有Windows,Mac OS X, Linux 64/32 bit,选择对应的版本下载解压即可使用,在下载包里有个example文件夹,里面对应了许多示例供参考。

为方便使用,我们将phantomjs添加至环境变量中。

windows下操作:

path 下添加  C:\Users\ming\Desktop\java-echarts\phantomjs-2.1.1-windows\bin
打开cmd,查看是否配置成功
C:\Users\ming>phantomjs --version
2.1.1

linux 下操作:

export PHANTOMJS=/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64
export PATH=$PATH:$PHANTOMJS/bin
# or
export PATH=$PATH:/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin
# 刷新配置
source /etc/profile

注:linux虽然不需要其他的依赖包,但仍旧需要GLIBCXX_3.4.9和GLIBC_2.7,当然大多数linux是有这两个依赖包的。

 1.2 运行脚本测试

 安装包下 example 文件夹下提供了好多示例,这里以hello.js 为例,在example文件夹下输入 phantomjs hello.js 命令 会输出 Hello, world! 更多用法可参考官方文档。

C:\Users\ming\Desktop\java-echarts\phantomjs-2.1.1-windows\examples>phantomjs hello.js
Hello, world!

 2.Echartsconvert

上面讲述了PhantomJS如何使用,下面我们就从Echarts官网使用JS截图的方式来获取我们想要的图片 

Echartsconvert (Gitee)copy下来代码
注意:因为该源码长期没有更新,script目录下echarts.min.js太过于老旧,无法支持目前Echarts的图形,请大家copy下代码后,更新替换其文件 。最新echarts.min.js下载传送门 

其中echarts-convert.js就是我们要使用到的主C,这个Js就相当于帮我们去Echarts官方运行Dome->生成折线图/柱状图->保存到指定文件夹下

jquery.js自行下载对应版本,我的是3.6.3

附上echarts-converts.js文件


;(function (window, document, undefined) {
    "use strict";

    // 引入module
    var system = require('system'), // 获取参数
        path = phantom.libraryPath,
        command = require(path + '/module/command.js');// 参数module

    /**
     * phantomJs 全局异常监听
     * @param msg
     * @param trace
     */
    phantom.onError = function (msg, trace) {
        var msgStack = ['Convert ERROR: ' + msg];
        if (trace && trace.length) {
            msgStack.push('TRACE:');
            trace.forEach(function (t) {
                msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function + ')' : ''));
            });
        }
        console.error(msgStack.join('\n'));
        phantom.exit(1);
    };

    /**
     * 参数
     * @type {Command}
     */
    var commandParams = command
        .version('0.0.1')
        .option('-s, --server', 'provide echarts convert http server')
        .option('-p, --port <number>', 'change server port when add -s or --server', 9090)
        .option('-o, --opt <json>', 'add the param of echarts method [ eChart.setOption(opt) ]')
        .option('-t, --type <value>', 'provide file/base64 for image, default file', /^(file|base64)$/i, 'base64')
        .option('-f, --outfile <path>', 'add output of the image file path')
        .option('-w, --width <number>', 'change image width', '600')
        .option('-h, --height <number>', 'change image height', '400')
        .parse(system.args);


    // ***********************************
    // Echarts转换器
    // ***********************************
    function Convert(params) {
        this.params = params || commandParams; // 参数命令
        this.external = {
            JQUERY3: path + '/script/jquery-3.6.3.min.js',
            ECHARTS: path + '/script/echarts.min.js',
            ECHARTS_CHINA: path + '/script/china.js'
        }; // 外部js
    }

    /**
     * 初始化
     */
    Convert.prototype.init = function () {
        var params = this.params;
        this.check(params);
        if (params.server) {
            this.server(params);
        } else {
            this.client(params);
        }
    };

    /**
     * 参数检查
     * @param params
     */
    Convert.prototype.check = function (params) {
        if (undefined === params.server && undefined === params.opt) {
            this.error("option argument missing -o, --opt <json>");
        }

        if (undefined !== params.opt) {
            var isJson = this.checkJson(params.opt);
            if (!isJson) {
                this.error("--opt <json> args not json string");
            }
        }

        if ('file' === params.type && undefined === params.outfile) {
            this.createTmpDir();
        }

    };

    /**
     * 检查是否是json字符串
     * @param value
     * @returns {boolean}
     */
    Convert.prototype.checkJson = function (value) {
        var re = /^\{[\s\S]*\}$|^\[[\s\S]*\]$/;
        // 类型为string
        if (typeof value !== 'string') {
            return false;
        }
        // 正则验证
        if (!re.test(value)) {
            return false;
        }
        // 是否能解析
        try {
            value = "\"" + value + "\"";
            JSON.parse(value);
        } catch (err) {
            return false;
        }
        return true;
    };

    /**
     * 创建临时目录,并指定输出路径
     */
    Convert.prototype.createTmpDir = function () {
        var fs = require('fs'); // 文件操作
        var tmpDir = fs.workingDirectory + '/tmp';
        // 临时目录是否存在且可写
        if (!fs.exists(tmpDir)) {
            if (!fs.makeDirectory(tmpDir)) {
                this.error('Cannot make ' + tmpDir + ' directory\n');
            }
        }
        this.params.outfile = tmpDir + "/" + new Date().getTime() + ".png";
    };

    /**
     * 服务
     * @param params
     */
    Convert.prototype.server = function (params) {
        var server = require('webserver').create(), // 服务端
            convert = this;

        var listen = server.listen(params.port, function (request, response) {
            /**
             * 输出
             * @param data
             * @param success
             */
            function write(data, success, msg) {
                response.statusCode = 200;
                response.headers = {
                    'Cache': 'no-cache',
                    'Content-Type': 'application/json;charset=utf-8'
                };
                response.write(convert.serverResult(data, success, msg));
                response.close();
            }

            //获取参数
            var args = convert.serverGetArgs(request);

            if (args.opt !== undefined) {
                var check = convert.serverCheckAndSet(params, args);

                if (check) {
                    convert.client(params, write);
                } else {
                    write("", false, "failed to get image, please check parameter [opt] is a JSON");
                }
            } else {
                write("", false, "failed to get image, missing parameter [opt]");
            }

        });

        // 判断服务是否启动成功
        if (!listen) {
            this.error("could not create echarts-convert server listening on port " + params.port);
        } else {
            console.log("echarts-convert server start success. [pid]=" + system.pid);
        }
    };

    /**
     * 服务参数检查和赋值
     * @param params
     * @param args
     * @returns {boolean}
     */
    Convert.prototype.serverCheckAndSet = function (params, args) {
        if (this.checkJson(args.opt)) {
            params.opt = args.opt;
        } else {
            return false;
        }

        if (/^(file|base64)$/i.exec(args.type)) {
            params.type = args.type;
        }

        if (!isNaN(args.width)) {
            params.width = args.width;
        }

        if (!isNaN(args.height)) {
            params.height = args.height;
        }
        return true;
    };

    /**
     * 结果返回
     * @param data
     * @param success
     * @param msg
     */
    Convert.prototype.serverResult = function (data, success, msg) {
        var result = {
            code: success ? 1 : 0,
            msg: undefined === msg ? success ? "success" : "failure" : msg,
            data: data
        };

        return JSON.stringify(result);
    };

    /**
     * 获取参数
     * @param request
     * @returns {{}}
     */
    Convert.prototype.serverGetArgs = function (request) {
        var args = {};
        if ('GET' === request.method) {
            var index = request.url.indexOf('?');
            if (index !== -1) {
                var getQuery = request.url.substr(index + 1);
                args = this.serverParseArgs(getQuery);
            }
        } else if ('POST' === request.method) {
            var postQuery = request.post;
            args = this.serverParseArgs(postQuery);
        }
        return args;
    };

    /**
     * 解析参数
     * @param query 字符串
     * @returns {{}} 对象
     */
    Convert.prototype.serverParseArgs = function (query) {
        var args = {},
            pairs = query.split("&");
        for (var i = 0; i < pairs.length; i++) {
            var pos = pairs[i].indexOf('=');
            if (pos === -1)
                continue;
            var key = pairs[i].substring(0, pos);
            var value = pairs[i].substring(pos + 1);
            // 中文解码,必须写两层
            value = decodeURIComponent(decodeURIComponent(value));
            args[key] = value;
        }
        return args;
    };

    /**
     * 访问渲染
     * @param params
     * @param fn
     */
    Convert.prototype.client = function (params, fn) {
        var page = require('webpage').create(); // 客户端
        var convert = this,
            external = this.external,
            render,
            output;

        /**
         *  渲染
         * @returns {*}
         */
        render = function () {
            switch (params.type) {
                case 'file':
                    // 渲染图片
                    page.render(params.outfile);
                    return params.outfile;
                case 'base64':
                default:
                    var base64 = page.renderBase64('PNG');
                    return base64;

            }
        };

        /**
         * 输出
         * @param content 内容
         * @param success 是否成功
         */
        output = function (content, success, msg) {
            if (params.server) {
                fn(content, success, msg);
                page.close();
            } else {
                console.log(success ? "[SUCCESS]:" : "[ERROR]:" + content);
                page.close();
                convert.exit(params);// exit
            }
        };

        /**
         * 页面console监听
         * @param msg
         * @param lineNum
         * @param sourceId
         */
        page.onConsoleMessage = function (msg, lineNum, sourceId) {
            console.log(msg);
        };

        /**
         * 页面错误监听
         * @param msg
         * @param trace
         */
        page.onError = function (msg, trace) {
            output("", false, msg); // 失败,返回错误信息
        };

        // 空白页
        page.open("about:blank", function (status) {
            // 注入依赖js包
            var hasJquery = page.injectJs(external.JQUERY3);
            var hasEchart = page.injectJs(external.ECHARTS);
            var hasEchartChina = page.injectJs(external.ECHARTS_CHINA);

            // 检查js是否引用成功
            if (!hasJquery && !hasEchart) {
                output("Could not found " + external.JQUERY3 + " or " + external.ECHARTS, false);
            }

            // 创建echarts
            page.evaluate(createEchartsDom, params);

            // 定义剪切范围,如果定义则截取全屏
            page.clipRect = {
                top: 0,
                left: 0,
                width: params.width,
                height: params.height
            };

            // 渲染
            var result = render();
            // 成功输出,返回图片或其他信息
            output(result, true);
        });
    };

    /**
     * 创建eCharts Dom层
     * @param params 参数
     */
    function createEchartsDom(params) {
        // 动态加载js,获取options数据
        $('<script>')
            .attr('type', 'text/javascript')
            .html('var options = ' + params.opt)
            .appendTo(document.head);

        // 取消动画,否则生成图片过快,会出现无数据
        if (options !== undefined) {
            options.animation = false;
        }

        // body背景设置为白色
        $(document.body).css('backgroundColor', 'white');
        // echarts容器
        var container = $("<div>")
            .attr('id', 'container')
            .css({
                width: params.width,
                height: params.height
            }).appendTo(document.body);

        var eChart = echarts.init(container[0]);
        eChart.setOption(options);
    }

    /**
     * debug,将对象转成json对象
     * @param obj
     */
    Convert.prototype.debug = function (obj) {
        console.log(JSON.stringify(obj, null, 4));
    };

    /**
     * 错误信息打印并退出
     * @param str 错误信息
     */
    Convert.prototype.error = function (str) {
        console.error("Error:" + str);
        this.exit();
    };

    /**
     * 退出,参数为空或是server时,不退出
     * @param params 参数
     */
    Convert.prototype.exit = function (params) {
        if (undefined === params || undefined === params.server) {
            phantom.exit();
        }
    };

    // 构建,入口
    new Convert(commandParams).init();

}(this, this.document));

 结构如下:

 2.1 启动命令

windows下启动命令 端口默认 9090

C:\Users\ming\Desktop\java-echarts\echartsconvert>phantomjs echarts-convert.js -s

 linux 下启动命令 端口默认 9090

nohup phantomjs /usr/local/phantomjs/echartsconvert/echarts-convert.js -s &

 2.2 中文字体乱码问题

 linux 下 phantonjs 没有支持的中文, 需要进行安装对应包。也可安装其它字体库,这里不做更多叙述。

此处执行linux字符集安装即可
在centos中执行:yum install bitmap-fonts bitmap-fonts-cjk
在ubuntu中执行:sudo apt-get install xfonts-wqy

 2.3 饼图无法生成,Null异常等问题

饼图绘制不了,request时就卡住的问题
solution1:此处并不是饼图绘制不了,而是只要opt中含有'%'都会挂,原因是作者在代码里执行了两次decodeURIComponent(详情参考echarts-convert.js源码259行),所以'%'传递时也必需encode两次,否则会造成%后的json串无法被decode导致卡住的问题。
此处可以将'%'替换为'%25'解决,或是改源码将decodeURIComponent改为一次,暂时没有发现改为一次decode会出现中文问题

3.JSON格式数据生成图表图片

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 以 EchartsData 示例中 getBar() 数据为例,将生成的Echarts数据转为base64图片
 *
 * @author demain_lee
 * @since  2022/12/28
 */
public class EchartData2Base64 {


    public static void main(String[] args) throws IOException {

      	// json 格式数据
        String jsonData = "{"xAxis":[{"type":"category","data":["Matcha Latte","Milk Tea","Cheese Cocoa","Walnut Brownie"]}],"yAxis":[{"type":"value"}],"tooltip":{"trigger":"item"},"legend":{},"series":[{"type":"bar","name":"2020","data":[43.3,83.1,86.4,72.4]},{"type":"bar","name":"2021","data":[85.8,73.4,65.2,53.9]},{"type":"bar","name":"2022","data":[93.7,55.1,82.5,39.1]}]}";

        // PhantomJS 服务器地址
        String url = "http://localhost:9090";
        Map<String, String> map = new HashMap<>();
        //此处已将%处理为%25
        jsonData = jsonData.replaceAll("\\s+", "").replaceAll("\"", "'").replaceAll("%", "%25");
        map.put("opt", jsonData);
        String resultData = post(url, map, "utf-8");
        System.out.println(resultData);

    }

    public static String post(String url, Map<String, String> map, String encoding) throws IOException {
        String body = "";

        // 创建httpclient对象
        CloseableHttpClient client = HttpClients.createDefault();
        // 创建post方式请求对象
        HttpPost httpPost = new HttpPost(url);

        // 装填参数
        List<NameValuePair> nvp = new ArrayList<>();
        if (map != null) {
            for (Map.Entry<String, String> entry : map.entrySet()) {
                nvp.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        // 设置参数到请求对象中
        httpPost.setEntity(new UrlEncodedFormEntity(nvp, encoding));

        // 执行请求操作,并拿到结果(同步阻塞)
        CloseableHttpResponse response = client.execute(httpPost);
        // 获取结果实体
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            // 按指定编码转换结果实体为String类型
            body = EntityUtils.toString(entity, encoding);
        }
        EntityUtils.consume(entity);
        // naps
        response.close();
        return body;
    }
}

3.1 base64格式数据

resultData 返回的数据包含base64格式数据 

//返回数据
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

3.2在线查看Base64图片

Base64/图片转换 - 在线工具 (try8.cn)

 注意添加data:image/png;base64,

 3.3在线修改图表样式

Examples - Apache ECharts

 3.4在线将opt转换为Json

 在线JS对象转JSON工具 - UU在线工具 (uutool.cn)

注意只需要将opt的内容进行转换,不需要带上option=; 

4.结束语

本篇文章到这里就结束了,本文介绍了如何通过Java 第三方库去处理对应的图表数据,以及通过基于PhantomJS的第三方开源项目echartsconvert进行数据转换,获取最后需要的Base64格式的图片数据。有了这个数据可以把它运用到自己需要的地方。比如,写到Word或PDF文档中

将Base64图片通过POI插入Word中 

Echarts图表Java后端生成Base64图片格式,POI写入Base64图片到Word中_青冘的博客-CSDN博客

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

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

相关文章

第八章:Linux信号

系列文章目录 文章目录 系列文章目录前言linux中的信号进程对信号的处理信号的释义 信号的捕捉信号的捕捉signal()信号的捕捉sigaction() 信号的产生通过终端按键产生信号前台进程与后台进程 kill()用户调用kill向操作系统发送信号raise()进程自己给自己发任意信号&#xff08;…

利用Google Docs的评论功能投递钓鱼链接

情报背景 利用Google drive等可信云服务进行的网络钓鱼攻击活动日益增长&#xff0c;这种攻击手段利用了高可信度的云服务骗取受害者的信任&#xff0c;并且可以绕过基于域名的安全策略。 近期Avanan公司发现了一种新的邮件钓鱼方式&#xff0c;攻击者利用Google docs的评论功…

计蒜客T1115——字符串判等

水题不解释&#xff0c;考研复习压力偶尔写一道换换心情还不错~ 这里有一个比较有趣的知识点&#xff0c;对于同时输入多个字符串时还要允许空格的输入&#xff0c;那么普通的cin函数就不能满足要求了&#xff0c;这里采用getline函数解决&#xff0c;如下&#xff1a; string …

使用最新技术实现智能考试系统源码

智能考试系统是一种重要的教育技术应用&#xff0c;它能够通过结合计算机科学和教育理论&#xff0c;为教育工作者提供一个高效、灵活和可靠的考试平台。最近&#xff0c;随着人工智能和大数据技术的飞速发展&#xff0c;智能考试系统受到了越来越多的关注。本文将详细介绍如何…

接口测试如何在json中引用mock变量

在测试接口的时候&#xff0c;有的接口需要测试随机传入大量数据&#xff0c;查看数据库是否正常&#xff0c;但是大量的随机数据全靠自己手写会很慢&#xff0c;而且是通过json传递的数据。 这里我们就可以使用mock生成随机变量&#xff0c;然后在json中引用mock变量 首先看…

ElasticSearch 7.4学习记录(基础概念和基础操作)

若你之前从未了解过ES&#xff0c;本文将由浅入深的一步步带你理解ES&#xff0c;简单使用ES。作者本人就是此状态&#xff0c;通过学习和梳理&#xff0c;产出本文&#xff0c;已对ES有个全面的了解和想法&#xff0c;不仅将知识点梳理&#xff0c;也涉及到自己的理解&#xf…

vue3:新特性

一、react和vue的主要区别 &#xff08;1&#xff09;数据更新上&#xff1a; 1、 react 采用 fiber架构 &#xff0c;使用 链表 表示 DOM 结构可以在 diff 时随时中断和继续&#xff0c;利用requestIdleCallback 在空闲时 diff &#xff0c;防止数据量大 diff 时间长导致卡顿…

线程池-手写线程池C++11版本(生产者-消费者模型)

本项目是基于C11的线程池。使用了许多C的新特性&#xff0c;包含不限于模板函数泛型编程、std::future、std::packaged_task、std::bind、std::forward完美转发、std::make_shared智能指针、decltype类型推断、std::unique_lock锁等C11新特性功能。 本项目有一定的上手难度。推…

【Linux升级之路】5_基础IO

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux升级之路】 ✒️✒️本篇内容&#xff1a;文件操作&#xff0c;文件管理&#xff0c;重定向&#xff0c;简易shell添加重定向功能&#xff0c;文件属…

人物启示-张一鸣与陆奇

在科技行业中&#xff0c;张一鸣与陆奇可谓是两位颇具影响力的人物。张一鸣和陆奇分别是字节跳动&#xff08;TikTok 的母公司&#xff09;的创始人和百度前总裁。张一鸣作为字节跳动的创始人&#xff0c;成功打造了今日头条、抖音等知名产品&#xff0c;而陆奇则曾任微软副总裁…

Django Rest_Framework(二)

文章目录 1. http请求响应1.1. 请求与响应1.1.1 Request1.1.1.1 常用属性1&#xff09;.data2&#xff09;.query_params3&#xff09;request._request 基本使用 1.1.2 Response1.1.2.1 构造方式1.1.2.2 response对象的属性1&#xff09;.data2&#xff09;.status_code3&…

4G型无线液位变送器是什么?

4G型无线液位变送器采用了四代无线通讯技术&#xff0c;与普通液位计相比&#xff0c;免去了布线的烦恼&#xff0c;无需时刻监控现场&#xff0c;在大幅提高工作效率和减少人力成本的同时&#xff0c;还可以随时随地获取监测数据。 4G型无线液位变送器的功能优势&#xff1a;…

jmeter创建一个压测项目

1.jemeter新建一个项目&#xff1a; 2.接下来对Thread进行描述&#xff0c;也可以先使用默认的Thread进行操作。 3.添加http请求头的信息。按照如图所示操作 4.在请求头里面添加必要的字段&#xff0c;可以只填必要字段就可以 5.添加Http请求信息&#xff0c;如下图&#xff…

第三章 图论 No.4最小生成树的简单应用

文章目录 裸题&#xff1a;1140. 最短网络裸题&#xff1a;1141. 局域网裸题&#xff1a;1142. 繁忙的都市裸题&#xff1a;1143. 联络员有些麻烦的裸题&#xff1a;1144. 连接格点 存在边权为负的情况下&#xff0c;无法求最小生成树 裸题&#xff1a;1140. 最短网络 1140. 最…

八大排序

目录 选择排序-直接插入排序 插入排序-希尔排序 选择排序-简单选择排序 选择排序-堆排序 交换排序-冒泡排序 交换排序-快速排序 归并排序 基数排序 选择排序-直接插入排序 基本思想: 如果碰见一个和插入元素相等的&#xff0c;那么插入元素把想插入的元素放在相等元素…

Golang空结构体struct{}的作用是什么?

文章目录 占位符&#xff1a;通道标识&#xff1a;键集合&#xff1a;内存占用优化&#xff1a;总结&#xff1a; 在Go语言中&#xff0c;空结构体 struct{}是一种特殊的数据类型&#xff0c;它不占用任何内存空间。空结构体没有任何字段&#xff0c;也没有任何方法。尽管它看起…

使用vue模拟通讯录列表,对中文名拼音首字母提取并排序

一个功能需求,做一个类似联系人列表的功能,点击名称获取对应的id,样式简陋,只是一个模板,原来是uniapp项目,根据需要改成了vue,需要的自行设计css&#xff08;万是有一个mo的音&#xff09; 流程 获取数据提取首个字的拼音的首个字母排序并分组 上代码&#xff1a; <temp…

基于OFDM通信系统的低复杂度的资源分配算法matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .......................................................................%子载波分配[~,po…

使用MyBatis操作数据库

hi,大家好,今天为大家带来MyBatis操作数据库的知识 文章目录 &#x1f437;1.根据MyBatis操作数据库&#x1f9ca;1.1查询操作&#x1f347;1.1.1无参查询&#x1f347;1.1.2有参查询 &#x1f9ca;1.2删除操作&#x1f9ca;1.3修改操作&#x1f9ca;1.4增加操作&#x1f9ca;…

【MySQL】MySQL数据类型

文章目录 一、数据类型的分类二、tinyint类型2.1 创建有符号数值2.2 创建无符号数值 三、bit类型三、浮点类型3.1 float3.2 decimal类型 四、字符串类型4.1 char类型4.2 varchar类型 五、日期和时间类型六、枚举和集合类型6.1 enum的枚举值和set的位图结构6.2 查询集合find_in_…