实现一个小程序分享图 wxml2canvas
我们经常会遇上动态生成海报的需求,而在小程序中,生成图片非Canvas莫属。但是在实际工作当中,为了追求效率,我们会不可避免地去使用一些JS插件,而 wxml-to-canvas
就是一款官方推荐且非常优秀的插件,它可以轻松地帮你将HTML代码转换成Canvas,进而生成可保存分享的图片。
但是wxml-to-canvas
是通过静态模板和样式绘制 canvas ,进而导出图片,需要单独写一份静态模板用于编译,对于很多场景还是有些限制,比如有时需要将图文混排的富文本内容生成分享图,对于这种长度不定,内容动态变化的图片生成需求,直接利用官方的canvas接口绘制是十分困难的,包括但不限于文字换行、表情文字图片混排、文字加粗、子标题等元素都需要一一绘制
我们的目标是实现一个通过wxml
节点标记,收集元素从而进行编译转换,仅依赖wxml
直出需要绘制的canvas进而快速实现图片分享,为此学习了 wxml2canvas
npm包,实现了基础功能的wxml2canvas
- 此文暂不讨论图片类型的
wxml
转canvas
先看结果🌰
我们针对简单的demo进行处理,包含了对块级元素
、行内元素
、背景色
、等简单样式的转换,达到 wxml
->canvas
->image
的一次性处理,且不需要重复书写静态代码模板进行编译
下面让我们一步步探求如何实现这个tiny版的wxml2canvas
实现
小程序提供了如下特性,可供我们便捷使用:
- measureText接口能直接测量出文本的宽度;
- SelectorQuery可以查询到节点对应的computedStyle。
同时小程序也存在一些弊端,比如:
- canvas属于原生组件,在移动端会置于最顶层;
- 通过SelectorQuery只能拿到节点的style,而无法获取文本节点的内容
所以我们第一步获取元素就面临两个问题:
1.如何获取需要转成canvas的元素
2.如何拿到获取元素的对应属性(样式、节点、内容…)
当获取到待收集的元素后,就可以将元素绘制到指定的canvas上,也就实现了wxml2canvas
所以初始化时,需要传入如下参数
/*** element:需要渲染的canvas节点* class:查找所有类名为exc-c的节点,并进行加入绘制队列* limit:限定相对位置 ***/
wxml2Canvas({element: "over-canvas",options: {class: ".exc-c",limit: ".limit-r",},
})
在wxml中需要绘制的元素需添加 exc-c
类名,方便对元素进行查找,并且如果需要限定相对位置,也需要在父级添加 limit-r
类名,例如,一个文本的位置(left, top) = (50, 80),class为panel的节点的位置为(left, top) = (20, 40),则文本canvas上实际绘制的位置(x, y) = (50 - 20, 80 -40) = (30, 40)。如果不传入limit,则以实际的位置(x, y) = (50, 80) 绘制
对于 wxml
而言,如有需要渲染的文本,也需要将文本内容通过 data-text="xxx"
属性的方式进行挂载,因为 SelectorQuery
无法获取文本节点的内容但是可以获取到节点的 dataset
属性,从而拿到文本内容
wxml
代码如下:
<view class="content">Wxml:</view><view class="-box limit-r"><view class="-line exc-c" data-type="inline-text" data-text="这是{{txt}}">这是{{txt}}</view><view class="-line -blue exc-c" data-type="inline-text" data-text="这是蓝色行内文字试试">这是蓝色行内文字试试</view><view class="-line exc-c" data-type="inline-text" data-text="这是换行的行内行内行内行内行内行内行内行内行内行行内行内内文字">这是换行的行内行内行内行内行内行行内行内内行内行内行内行内文字</view><view class="-line -red exc-c" data-type="inline-text" data-text="这是红色行内红色行内红色行内红色行内文字试试">这是红色行内红色行内红色行内红色行内文字试试</view><view class="-il exc-c" data-type="text" data-text="这是无边距文字">这是无边距文字</view><view class="-il-2 exc-c" data-type="text" data-text="这是margin文字">这是margin文字</view><view class="-il-3 exc-c" data-type="text" data-text="这是padding文字">这是padding文字</view><view class="-il-4 exc-c" data-type="text" data-text="这是居中有背景文字">这是居中有背景文字</view><view class="-il-5 exc-c" data-type="text" data-text="这是背景文字" data-background="rgba(255, 0, 0, 0.4)" data-padding="0 0 0 0">这是背景文字</view></view><view class="content">Canvas:</view><canvas canvas-id="over-canvas" class="-box"></canvas>
…
上面是需要配置的代码,那下面让我看看如何具体实现:
1.配置基础的样式 如:字体大小、颜色、边距,并返回一个函数用来追加配置
2.解析元素 拿到所有需要绘制的元素以及相对的父级元素
3.按照是否涉及换行进行分类绘制 (块级元素 / 行内元素)
4.全部绘制结束时进行 resolve
const Wxml2Canvas = config => {return new Promise(async (resolve, reject) => {const appendSet = setInit(config); // 配置基础const [render, limit] = await getWxml(config.options); // 获取待绘制元素、限制区域const [block, inlineTmp] = sortListByTop(render); // 划分块级元素 \ 行内元素appendSet({ limit }); // 追加配置Promise.all([...drawB(block), drawL(inlineTmp)]).then(resolve).catch(reject);});
};
初始化
在上面代码中,通过 setInit
设置 canvas
的基本属性,并将可配置的默认参数(宽度、字体大小、字体颜色…)以及 ctx
维护成一个对外暴露的公共对象,最后返回一个函数,供使用者更新配置
const DEFAULT_CONFIG = {width: 340,TOP: "top",FONT_SIZE: "14",PADDING: "0 0 0 0",FONT_COL: "#454545",SHADOW_COL: "#ffffff",background: "#ffffff",font: "14px PingFang SC",STROKECOLOR: "white",
};export const CACHE_INFO = {};export const setInit = (config = {}) => {const info = { ...DEFAULT_CONFIG, ...config };const { background, font, width, height, TOP, STROKECOLOR } = info;CACHE_INFO.options = info;CACHE_INFO.ctx = wx.createCanvasContext(info.element, info._this);CACHE_INFO.ctx.font = font;CACHE_INFO.ctx.setTextBaseline(TOP);CACHE_INFO.ctx.setStrokeStyle(STROKECOLOR); // 设置基本样式drawRectToCanvas(0, 0, width, height, { fill: background });return arg => Object.keys(arg).forEach(i => (CACHE_INFO.options[i] = arg[i]));
};
获取元素
在 getWxml
中,通过小程序提供的 createSelectorQuery
获取到需要绘制元素的节点信息,同时也获取父级相对元素的节点信息,作为 limit
界定绘制元素的边界
/*** @param { object } item 待处理的节点* @returns array 解析后的节点信息*/
export const getWxml = item => {const { options } = CACHE_INFO;const { _this, width } = options;const query = _this? wx.createSelectorQuery().in(_this): wx.createSelectorQuery();const render = new Promise(resolve => {query.selectAll(item.class).fields({dataset: true,size: true,rect: true,computedStyle: COMPUT_STYLE,},res => resolve(res)).exec();});const limit = new Promise(resolve => {if (!item.limit) resolve({ top: 0, width });query.select(item.limit).fields({dataset: true,size: true,rect: true,},res => resolve(res)).exec();});return Promise.all([render, limit]);
};
其中 COMPUT_STYLE
规定了需要查找的元素样式名称:
export const COMPUT_STYLE = ["width","height","font","fontSize","fontFamily","fontWeight","fontStyle","textAlign","color","lineHeight","border","borderColor","borderStyle","borderWidth","verticalAlign","boxShadow","background","backgroundColor","backgroundImage","backgroundPosition","backgroundSize","paddingLeft","paddingTop","paddingRight","paddingBottom",
];
获取到相对父级元素,将获取的节点作为边界信息更新到配置中
appendSet({ limit }); // 追加配置
获取到的元素可以分为两类 即:
- 涉及文字换行的元素 (行内元素)
- 不涉及文字换行的元素 (块级元素)
我们可以通过在书写 wxml
时,通过事先声明 data-type="inline-text"
,这样获取到节点之后,可以通过 dataset
获取元素的类型,如果为行内元素时,需要将行内元素通过高度进行分层,将同一行的元素进行归类
/*** @param { array } list 待处理的节点* @returns array*/
export const sortListByTop = list => {const [arrBlock, arrLine, lineTemp] = [[], [], {}];list.forEach(i => {if (i.dataset.type && i.dataset.type.indexOf("inline") == -1) {arrBlock.push(i);} else {arrLine.push(i);}});arrLine.forEach(i => {lineTemp[i.top] = lineTemp[i.top] || [];lineTemp[i.top].push(i);});return [arrBlock, lineTemp];
};
处理元素
通过 drawWxmlBlock
和 drawWxmlInline
分别对元素进行处理和绘制,在此之前还需要 drawAfter
方法将节点信息(文字、位置…) + 元素的样式信息(padding、width…) 转化为对应的 canvas
位置信息:
drawAfter:
/*** 返回节点的真实位置** @param { object } el 需要渲染的节点* @param {*} leftOffset 从左侧开始绘制的起点* @param {*} maxWidth 一行文本的最大宽度* @returns 返回canvas位置*/
const drawAfter = (el, leftOffset, maxWidth) => {const { options } = CACHE_INFO;const { left: limitLeft = 0, top: limitTop = 0 } = options.limit;const leftFix = +el.dataset.left || 0;const topFix = +el.dataset.top || 0;el.width = transferNum(el.width);el.height = transferNum(el.height);el.left = transferNum(el.left) - limitLeft + leftFix;el.top = transferNum(el.top) - limitTop + topFix;let padding = el.dataset.padding || options.PADDING;if (typeof padding === "string") {padding = transferPadding(padding);}const paddingTop = +el.paddingTop.replace("px", "") + +padding[0];const paddingRight = +el.paddingRight.replace("px", "") + +padding[1];const paddingBottom = +el.paddingBottom.replace("px", "") + +padding[2];const paddingLeft = +el.paddingLeft.replace("px", "") + +padding[3];el.padding = [paddingTop, paddingRight, paddingBottom, paddingLeft];const text = el.dataset.text || "";el.background = el.dataset.background || el.backgroundColor;return {text,x: leftOffset || el.left,y: el.top,originX: el.left,...(leftOffset && { leftOffset }),...(maxWidth && { maxWidth }),};
};
drawWxmlBlock:
对于块级元素,将转为 canvas
位置信息的节点依次绘制,通过 Promise
将绘制完成的结果进行返回
/*** 绘制块级元素* @param { array } block 需要绘制的块级元素*/
export const drawWxmlBlock = (block = []) => {return block.map(el =>new Promise((resolve, reject) => {const textData = drawAfter(el);drawText(textData, el, "text", resolve, reject,);}));
};
drawWxmlInline:
对于行内元素,首先通过同一个行元素的左右边距计算出一行的最大宽度,用于换行,并且记录距离左侧的边距 leftOffset
,每次绘制完更新下一次绘制的起点,从上次结束的位置继续绘制,全部绘制完成后进行 resolve
(有些贪心算法的影子)
/*** 绘制行内元素* @param { object } inline 需要绘制的行内元素*/
export const drawWxmlInline = (inline = {}) => {let leftOffset = 0;return new Promise(resolve => {let maxWidth = 0;let minLeft = Infinity;let maxRight = -Infinity;Object.keys(inline).forEach(top => {inline[top].forEach(el => {minLeft = Math.min(el.left, minLeft);maxRight = Math.max(el.right, maxRight);});});// 找出同一top下的最小left和最大right,得到最大的宽度,用于换行maxWidth = Math.ceil(maxRight - minLeft);Object.keys(inline).forEach(top => {inline[top].forEach(el => {const textData = drawAfter(el, leftfOfset, maxWidth);const drawRes = drawText(textData, el, "inline");leftOffset = drawRes.leftOffset; // 每次绘制从上次结束地方开始});});resolve();});
};
渲染节点
无论是块级元素还是行内元素都是通过 drawText
方法最终渲染节点到指定的 canvas
中,只不过传参会发生变化,块级元素
每次绘制都是独立的,所以每次绘制成功之后会触发 resolve
的回调,但是行内元素,每次绘制需要返回当前绘制结束之后的定位,以此作为下次绘制的起始位置。
块级元素:
/*** 绘制文字 * @param { object } textData 节点位置* @param { object } el 节点信息* @param { string } type 渲染节点类型* @param { function } resolve 成功时候抛出* @param { function } reject 失败时候抛出*/
const DrawTxt = (textData, el, type, resolve, reject) => {const { ctx, options } = CACHE_INFO;try {...[x, y] = setTxtAlign(textData, el, textWidth, width);ctx.fillText(textData.text, x, y);ctx.draw(true);resolve();} catch (e) {reject && reject(e);}
};
以上对于块级元素时,我们只需要结合 textAlign
和 padding
就可以计算出 x
的值,通过 行数 * 行高
和 padding
计算出 y
的值,最后通过 ctx.fillText
进行绘制
行内元素:
/*** 绘制文字 * @param { object } textData 节点位置* @param { object } el 节点信息*/
const DrawTxt = (textData, el) => {const { ctx, options } = CACHE_INFO;...// 元素换行的情况:const maxw = textData.maxWidth; // 最大宽度let lineNum = Math.max(Math.floor(textWidth / maxw), 1); // 最大行数const singleLength = Math.floor(text.length / lineNum); // 每行字数const widthOffset = textData.leftOffset - textData.originX; // 计算真实的左边距let [endIdx, fSingle, fsWidth] = getTextSingleLine(text, maxw, singleLength, 0, widthOffset);[x, y] = setTxtAlign(textData, el, fsWidth);ctx.fillText(fSingle, x, y); // 绘制leftOffset = x + fsWidth; // 更新左边距topOffset = y; // 更新右边距...ctx.draw(true);return { leftOffset, topOffset }
};
上面行内元素,需要关心的是 getTextSingleLine
方法,即计算截取换行文字的索引和位置,通过 offset
矫正每行的真实文本数,如果 真实文本距离 + 左边距 > 一行最大长度
则不断进行截取,并且更新一行的文本字数,直到不超过一行最大长度时,返回截取的文字索引、截取的文字、截取后的文本长度
getTextSingleLine:
/*** 当文本超过宽度时,计算每一行应该绘制的文本** @param {*} text 需要绘制的文字* @param {*} width 一行最大长度* @param {*} singleLength 每行实际字数* @param {*} currentIndex 文字的起始位置索引* @param {*} widthOffset 左边距*/
export const getTextSingleLine = ( text,width,singleLength,currentIndex = 0,widthOffset = 0 ) => {let offset = 0;let endIndex = currentIndex + singleLength + offset;let single = text.substring(currentIndex, endIndex);let singleWidth = measureWidth(single);while (Math.round(widthOffset + singleWidth) > width) {offset -= 1;endIndex = currentIndex + singleLength + offset;single = text.substring(currentIndex, endIndex);singleWidth = measureWidth(single);}return [endIndex, single, singleWidth];
};
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:
实现一个小程序分享图 wxml2canvas
我们经常会遇上动态生成海报的需求,而在小程序中,生成图片非Canvas莫属。但是在实际工作当中,为了追求效率,我们会不可避免地去使用一些JS插件,而 wxml-to-canvas 就是一款官方推荐且非常优秀的插件,它可以…...
基于matlab设计x波段机载SAR系统
一、前言此示例说明如何设计在 X 波段工作的合成孔径雷达 (SAR) 传感器并计算传感器参数。SAR利用雷达天线在目标区域上的运动来提供目标区域的图像。当SAR平台在目标区域上空行进时,当脉冲从雷达天线发送和接收时,会产生合成孔径…...
WPF学习:Slider — 冒泡显示值
想做一个下图所示的Slider,以冒泡的方式显示其Value值,该怎么做呢? 功能要求,当鼠标放在滑块上的时候,冒“泡”显示值;当滑块移动的时候,“泡”跟随移动。 看似简单的功能,但要完美…...
Vue实战第4章:主页设计之中部内容设计
前言 本篇在讲什么 接上篇文章,我们制作了一个自定义的网页导航栏,本篇文章我们简单制作一个内容页 仅介绍简单的应用,仅供参考 本篇适合什么 适合初学Vue的小白 适合想要自己搭建网站的新手 适合没有接触过vue-router的前端程序 本篇…...
数据结构代码总结(C语言实现)
目录如何应对数据结构的代码题?采取的学习流程①首先对C语言的语法的熟悉②学习掌握基本代码的写法,做到熟练2.1插入排序2.2快速排序2.3二分查找2.4树的遍历③跟着网上视频开始熟悉对一些问题的解答④结合真题的代码,寻找其中的结题规律如何应…...
zookeeper 复习 ---- chapter04
zookeeper 复习 ---- chapter04zookeeper 的精髓是什么? 1:它有四个节点类型 持久无序的节点 临时无序的节点 持久有序的节点 临时有序的节点 临时的节点的特征:当客户端和服务器端断开连接,当前客户端创建的节点被服务器端自动删…...
thinkphp6.0连接MYSQL
目录8.连接多个数据库7.多级控制器不存在6.分页5.非法请求4.关于路由**3.初体验页面****2.加入fileheader添加注释****1.配置mysql0. 官方开发手册一些网址 http://127.0.0.1:8000/index 原桌面 http://127.0.0.1:8000/hello/fsh hello,fsh(index中hello方法&#x…...
商家必读!超店有数分享,tiktok达人营销变现如何更快一步?
近几年来,“粉丝经济”发展越来越迅猛,“网红带货”已经成为了一种营销的方式。这种方式让商家能基于达人的影响下迅速抢占自己的私域流量池。消费者会基于对达人的信任,购买达人推荐的产品。达人效应可以助力品牌走出营销困境。如果商家想要…...
操作系统(day11)--快表,两级页表
具有快表的地址变换机构 时间局限性:会有大量连续的指令需要访问同一个内存块的数据的情况(程序中的循环) 空间局限性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。…...
预告| 亮点抢先看!第四届OpenI/O启智开发者大会主论坛24日启幕!
2023年2月24日至25日,第四届OpenI/O启智开发者大会将在深圳隆重举行。“算网筑基、开源启智、AI赋能”作为今年大会的主题,吸引了全球业界关注的目光。大会集结中国算力网资源基座、开源社区治理及AI开源生态建设、国家级开放创新应用平台、NLP大模型等前…...
猪齿鱼(Choerodon UI )的通用提交的封装 —— 两种方案,A.使用dataSet的自身的submit,B.使用axios.post来提交
submit组件(otherSubmit/axiosSubmit) 一、背景与简介 1、首先我们申请表提交,分为【保存】提交与【其他】提交; 1.1【保存】提交,要求表单必须要有变更,DataToJSON默认为dirty(只转换变更的…...
CISCN(Web Ezpentest)GC、序列化、case when
目录 REGEXP的一个点(正则) like(默认不区分大小写) 当禁用了空格 regexp,like的区分大小写的使用方法 [CISCN 2022 初赛]ezpentest 卡点 2022 HFCTF babysql 最近又学到了一道新知识,case when的错…...
OSG三维渲染引擎编程学习之五十七:“第六章:OSG场景工作机制” 之 “6.1 OSG访问器”
目录 第六章 OSG场景工作机制 6.1 OSG访问器 6.1.1 访问器模式 6.1.2 osg::NodeVisitor 6.1.3 访问器示例...
Python3 输入和输出实例及演示
在前面几个章节中,我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。 输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第3种方式是使用文件对象的 write() 方法,标准输出文件可以用 sys.std…...
召回-回忆录(持续更新)
0.召回方法 词召回 swing、itemCF 缺点: 有冷启动问题不是全局召回,冷门活动难以得到召回结果容易召回过多的头部热门活动 向量召回 参考文献: 经典推荐算法学习(七)| Graph Embedding技术学习 | 从DeepWalk到No…...
1243. 糖果/状态压缩dp【AcWing】
1243. 糖果 糖果店的老板一共有 M种口味的糖果出售。 为了方便描述,我们将 M种口味编号 1∼M。 小明希望能品尝到所有口味的糖果。 遗憾的是老板并不单独出售糖果,而是 K颗一包整包出售。 幸好糖果包装上注明了其中 K颗糖果的口味,所以小…...
【Spring Cloud Alibaba】001-单体架构与微服务架构
【Spring Cloud Alibaba】001-单体架构与微服务 文章目录【Spring Cloud Alibaba】001-单体架构与微服务一、单体架构1、单体应用与单体架构2、单体应用架构图3、单体架构优缺点优点缺点二、微服务1、微服务的“定义”2、微服务的特性3、微服务架构图4、微服务的优缺点优点缺点…...
Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock
一、materials 与 sharedMaterials 1.1 使用上的区别与差异 Unity 开发时,在 C# 中通过 Renderer 取材质操作是非常常见的操作,Renderer 有两种常规获取材质的方式: sharedMaterials:可以理解这个就是原始材质,所有使…...
java学习----网络编程
网络编程入门 网络编程概述 计算机网络 计算机网络是指地理位置不同的具有独立功能的计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理协调下,实现资源共享和信息传递的计算机系统…...
这些「误区」99%的研发都踩过
意识不到误区的存在最为离谱; 01生活中,职场上,游戏里,都少不了正面对喷过:意识太差; 在个人的认知中意识即思维,意识太差即思维中存在的误区比较多; 每个人或多或少都存在思维上的…...
Bi系统跟数据中台的区别是什么?
随着数据时代的发展,BI分析是当今数据时代必不可少的能力之一。BI系统通过系统化产品化的方法,能够大幅降低数据的获取成本、提升数据使用效率。同时借助可视化、交互式的操作,可以高效支持业务的分析及发展。 BI如此火热,随之而…...
微信小程序反编译方法分享
文章目录一、前言二、准备工作(一)安装Nodejs(二)解密和逆向工具三、小程序缓存文件解密(一)定位小程序缓存路径(二)源码解密(三)源码反编译四、小结一、前言…...
有了这些接口测试用例+工具,测试效率想不提升都难
写在前面:在日常开发过程中,有人做前端开发,有人负责后端开发。接口的主要作用就是连接前后台。但是,由于前端和后端开发的速度可能不一样,尤其是后端开发好了,但前端还未开发。这种时候我们需要做接口测试…...
麒麟 arm架构安装nginx
目录 1、下载nginx安装包并解压 在线安装: 离线安装: 上传nginx安装包(下载地址:https://nginx.org/download/nginx-1.20.2.tar.gz)到指定目录 2、安装系统相关依赖软件、组件包 1、上传或者下载对应的组件包 2、安…...
logrotate失效的排查---selinux开启状态拦截问题及解决方法
首先测试环境selinux 处于关闭状态 disable # getenforce disable重新开启selinux配置与生产环境一致 [rootlocal]# cat /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX can take one of these three values: # enforcing - S…...
Allegro使用总结-查看Layout基本操作:
好久没用CSDN写过笔记了,没想到无意间打开,编辑器更新啦!以前巨难用的“富文本编辑器”终于改观了😭变的好像语雀,うれしい1. 视图/画面操作a. 画面缩放(Zoom):F11/F12 或 鼠标滚轮b…...
cmd del命令笔记
使用 /s 删除文件夹下所有的 del /s sub # 删除目录下所有文件,这个目录不会删除 /p 确认提示 /q 静默模式,不会提示要不要删除 如过和/p同时使用,那么不提示 /a 根据属性删除,a是attribute的意思 del /a:r 01.jpg # 01.jp…...
apifox持续集成+java+企微机器人+xxljob定时推送
总览: apifox做接口测试后,把用例合并组装成测试套件,然后apifox-cli通过终端命令实现把套件执行后,输出本地文件的测试报告html或json。本地解析后拿到有用的解决通过定时执行推送到企微群里。 然后把html一起推到群里。 这个…...
盘点Linux内核网络知识100道题,这篇就够了
计算机网络模型 1、五层因特网协议栈和七层OSI(Open System Interconnections)参考模型分别是什么? 5层:应用层、传输层、网络层、数据链路层、物理层 7层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理…...
数据库敏感字段脱敏
文章目录什么是脱敏脱敏后带来什么问题解决方案一解决方案二具体实施方案一方案二存量数据处理什么是脱敏 如果你有申请过一些软件资质,应该会被要求敏感数据进行加密,比如密码不能明文,用户的手机号,身份证信息,银行…...
免费微信小程序制作/百度seo流量
链表05--复杂链表的复制-jz25题目概述解析&参考答案注意事项说明题目概述 算法说明 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请…...
专门做招商的网站是什么情况/网络查询网站
背景一般说MySQL的索引,都清楚其索引主要以B树为主,此外还有Hash、RTree、FullText。本文简要说明一下MySQL的BTree索引,以及和其相关的二叉树、平衡二叉树、B-Tree,相关的知识网上很多,为了方便自己更快、清楚的了解&…...
番禺做网站设计/福建seo排名培训
利用mybatis generator反向生成数据库对应的model实体时,报如下错误:原因是: 配置文件 generatorConfig.xml 里面的context的子元素必须按照它给出的顺序。 我这里面报错是因为match 报错...
武汉平价网站建设/网络seo是什么工作
链接:https://ac.nowcoder.com/acm/contest/5646/J来源:牛客网题目描述对输入的字符串进行排序后输出输入描述:多个测试用例,每个测试用例一行。每行通过,隔开,有n个字符,n<100输出描述:对于每组用例输出一…...
建立免费网站/百度关键词优化软件排名
intellij 开发webservice 最近项目中有用到WebService,于是就研究了一下,但是关于intellij 开发 WebService 的文章极少,要不就是多年以前,于是研究一下,写这篇博文。纯属记录,分享,中间有不对的…...
百度登录/长沙网站优化方案
更多代码请见:https://github.com/xubo245/SparkLearning Spark中组件Mllib的学习之分类篇 1解释 MLlib决策树支持三种不纯度的计算:gini、entropy、variance。其他的目前不支持 def fromString(name: String): Impurity name match {case "gin…...