文章一览
发布于 2023-08-31
Vue 2 中使用腾讯开源markdown编辑器——Cherry Markdown Editor
249 热度
1 条评论
博客相关
介绍 Cherry Markdown Editor 是一款 Javascript Markdown 编辑器,具有开箱即用、轻量简洁、易于扩展等特点. 它可以运行在浏览器或服务端(NodeJs). 开箱即用 开发者可以使用非常简单的方式调用并实例化 Cherry Markdown 编辑器,实例化的编辑器默认支持大部分常用的 markdown 语法(如标题、目录、流程图、公式等)。 易于拓展 当 Cherry Markdown 编辑器支持的语法不满足开发者需求时,可以快速的进行二次开发或功能扩展。同时,CherryMarkdown 编辑器应该由纯 JavaScript 实现,不应该依赖 angular、vue、react 等框架技术,框架只提供容器环境即可。 特性 语法特性 图片缩放、对齐、引用 根据表格内容生成图表 字体颜色、字体大小 字体背景颜色、上标、下标 checklist 音视频 多种模式 双栏编辑预览模式(支持同步滚动) 纯预览模式 无工具栏模式(极简编辑模式) 移动端预览模式 功能特性 复制 Html 粘贴成 MD 语法 经典换行&常规换行 多光标编辑 图片尺寸 导出长图、pdf float toolbar 在新行行首支持快速工具栏 bubble toolbar 选中文字时联想出快速工具栏 性能特性 局部渲染 局部更新 安全 Cherry Markdown 有内置的安全 Hook,通过过滤白名单以及 DomPurify 进行扫描过滤. 样式主题 Cherry Markdown 有多种样式主题选择 特性展示 这里可以看到特性的简短的演示 screenshot 在线体验 basic H5 多实例 无 toolbar 纯预览模式 注入(默认防注入,需要配置才允许注入) API 图片所见即所得编辑尺寸 表格编辑 标题自动序号 安装 通过 yarn yarn add cherry-markdown 通过 npm npm install cherry-markdown --save 如果需要开启 mermaid 画图、表格自动转图表功能,需要同时添加mermaid 与echarts包。 目前Cherry推荐的插件版本为echarts@4.6.0、mermaid@9.4.3 # 安装mermaid依赖开启mermaid画图功能yarn add mermaid@9.4.3 安装echarts依赖开启表格自动转图表功能 yarn add echarts@4.6.0 封装组件 template部分 <div @click.prevent.stop> <div :id="mdId" :style="{ height: height }"/> </div> script部分 import Cherry from 'cherry-markdown' import {getToken} from '@/utils/auth' import 'cherry-markdown/dist/cherry-markdown.min.css' export default { props: { height: { type: String, default: 'fit-content' }, value: { type: String, default: '' }, mdId: { type: String, default: 'markdown-container' }, editModel: { type: String, default: 'edit&preview' } }, data() { return { content: null, cherryInstance: null } }, mounted() { this.initCherryMD() }, methods: { // 初始化编辑器 initCherryMD(value, config) { const { fileUpload, mdId } = this const defaultValue = value || this.value /** 自定义一个自定义菜单 点第一次时,把选中的文字变成同时加粗和斜体 保持光标选区不变,点第二次时,把加粗斜体的文字变成普通文本 / const customMenuA = Cherry.createMenuHook('加粗斜体', { iconName: 'font', onClick: function (selection) { // 获取用户选中的文字,调用getSelection方法后,如果用户没有选中任何文字,会尝试获取光标所在位置的单词或句子 let $selection = this.getSelection(selection) || '同时加粗斜体'; // 如果是单选,并且选中内容的开始结束内没有加粗语法,则扩大选中范围 if (!this.isSelections && !/^\s(***)[\s\S]+(\1)/.test($selection)) { this.getMoreSelection('', '', () => { const newSelection = this.editor.editor.getSelection(); const isBoldItalic = /^\s*(***)[\s\S]+(\1)/.test(newSelection); if (isBoldItalic) { $selection = newSelection; } return isBoldItalic; }); } // 如果选中的文本中已经有加粗语法了,则去掉加粗语法 if (/^\s*(***)[\s\S]+(\1)/.test($selection)) { return $selection.replace(/(^)(\s*)(***)([^\n]+)(\3)(\s*)($)/gm, '$1$4$7'); } /** 注册缩小选区的规则 注册后,插入“TEXT”,选中状态会变成“【TEXT】” 如果不注册,插入后效果为:“【TEXT】” / this.registerAfterClickCb(() => { this.setLessSelection('', ''); }); return $selection.replace(/(^)([^\n]+)($)/gm, '$1**$2***$3'); } }); /** 定义一个空壳,用于自行规划cherry已有工具栏的层级结构 / const customMenuB = Cherry.createMenuHook('实验室', { iconName: '', }); /* 定义一个自带二级菜单的工具栏 */ const customMenuC = Cherry.createMenuHook('帮助中心', { iconName: 'question', onClick: (selection, type) => { switch (type) { case 'markdown': return ${selection}markdown教程在这里:https://markdown.com.cn/; case 'Emoji': return ${selection}Emoji表情在这里:https://emojipedia.org/zh/; case 'formula': return ${selection}LaTeX公式编辑器在这里:https://www.latexlive.com/; case 'Example': return ${selection}完整示例看这里:https://tencent.github.io/cherry-markdown/examples/index.html; default: return selection; } }, subMenuConfig: [{ noIcon: true, name: 'markdown教程', onclick: (event) => { this.cherryInstance.toolbar.menus.hooks.customMenuCName.fire(null, 'markdown') } }, { noIcon: true, name: 'Emoji 表情', onclick: (event) => { this.cherryInstance.toolbar.menus.hooks.customMenuCName.fire(null, 'Emoji') } }, { noIcon: true, name: '公式编辑器', onclick: (event) => { this.cherryInstance.toolbar.menus.hooks.customMenuCName.fire(null, 'formula') } }, { noIcon: true, name: '完整示例', onclick: (event) => { this.cherryInstance.toolbar.menus.hooks.customMenuCName.fire(null, 'Example') } }, ] }); this.cherryInstance = new Cherry({ id: mdId, value: defaultValue, fileUpload: fileUpload, // 第三方包 externals: { // externals }, // 解析引擎配置 engine: { // 全局配置 global: { // 是否启用经典换行逻辑 // true:一个换行会被忽略,两个以上连续换行会分割成段落, // false: 一个换行会转成<br>,两个连续换行会分割成段落,三个以上连续换行会转成<br>并分割段落 classicBr: false, /** 全局的URL处理器 @param {string} url 来源url @param {'image'|'audio'|'video'|'autolink'|'link'} srcType 来源类型 @returns */ urlProcessor: this.urlProcessor, /** 额外允许渲染的html标签 标签以英文竖线分隔,如:htmlWhiteList: 'iframe|script|style' 默认为空,默认允许渲染的html见src/utils/sanitize.js whiteList 属性 需要注意: 启用iframe、script等标签后,会产生xss注入,请根据实际场景判断是否需要启用 一般编辑权限可控的场景(如api文档系统)可以允许iframe、script等标签 / htmlWhiteList: '' }, // 内置语法配置 syntax: { // 语法开关 // 'hookName': false, // 语法配置 // 'hookName': { // // } autoLink: { /* 是否开启短链接 */ enableShortLink: true, /** 短链接长度 */ shortLinkLength: 20 }, list: { listNested: false, // 同级列表类型转换后变为子级 indentSpace: 2 // 默认2个空格缩进 }, table: { enableChart: false // chartRenderEngine: EChartsTableEngine, // externals: ['echarts'], }, inlineCode: { theme: 'red' }, codeBlock: { theme: 'dark', // 默认为深色主题 wrap: true, // 超出长度是否换行,false则显示滚动条 lineNumber: true, // 默认显示行号 copyCode: true, // 是否显示“复制”按钮 customRenderer: { // 自定义语法渲染器 }, mermaid: { svg2img: false, // 是否将mermaid生成的画图变成img格式 }, /** indentedCodeBlock是缩进代码块是否启用的开关 在6.X之前的版本中默认不支持该语法。 因为cherry的开发团队认为该语法太丑了(容易误触) 开发团队希望用```代码块语法来彻底取代该语法 但在后续的沟通中,开发团队发现在某些场景下该语法有更好的显示效果 因此开发团队在6.X版本中才引入了该语法 已经引用6.x以下版本的业务如果想做到用户无感知升级,可以去掉该语法: indentedCodeBlock:false */ indentedCodeBlock: true }, emoji: { useUnicode: false, // 是否使用unicode进行渲染 customResourceURL: 'https://github.githubassets.com/images/icons/emoji/unicode/${code}.png?v8', upperCase: true, }, fontEmphasis: { /** 是否允许首尾空格 首尾、前后的定义: 语法前语法首+内容+语法尾语法后 例: true: __ hello __ ====> <strong> hello </strong> hello ====> <strong>hello</strong> false: __ hello __ ====> <em>_ hello _</em> hello ====> <strong>hello</strong> / allowWhitespace: false }, strikethrough: { /* 是否必须有前后空格 首尾、前后的定义: 语法前语法首+内容+语法尾语法后 例: true: hello world ====> hello world hello wor l d ====> hello wor <del>l</del> d false: hello world ====> hello wor<del>l</del>d hello wor l d ====> hello wor <del>l</del> d */ needWhitespace: false }, mathBlock: { engine: 'MathJax', // katex或MathJax src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js', // 如果使用MathJax plugins,则需要使用该url通过script标签引入 plugins: true // 默认加载插件 }, inlineMath: { engine: 'MathJax', // katex或MathJax src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js' // 如果使用MathJax plugins,则需要使用该url通过script标签引入 }, toc: { /** 默认只渲染一个目录 / allowMultiToc: false }, header: { /* 标题的样式: default 默认样式,标题前面有锚点 autonumber 标题前面有自增序号锚点 none 标题没有锚点 / anchorStyle: 'default' } } }, editor: { id: 'code', // textarea 的id属性值 name: 'code', // textarea 的name属性值 autoSave2Textarea: false, // 是否自动将编辑区的内容回写到textarea里 theme: 'default', // depend on codemirror theme name: https://codemirror.net/demo/theme.htm // 编辑器的高度,默认100%,如果挂载点存在内联设置的height则以内联样式为主 height: '100%', // defaultModel 编辑器初始化后的默认模式,一共有三种模式:1、双栏编辑预览模式;2、纯编辑模式;3、预览模式 // edit&preview: 双栏编辑预览模式 // editOnly: 纯编辑模式(没有预览,可通过toolbar切换成双栏或预览模式) // previewOnly: 预览模式(没有编辑框,toolbar只显示“返回编辑”按钮,可通过toolbar切换成编辑模式) defaultModel: this.editModel, // 粘贴时是否自动将html转成markdown convertWhenPaste: true, codemirror: { // 是否自动focus 默认为true autofocus: true } }, toolbars: { theme: 'light', // light or dark showToolbar: true, // false:不展示顶部工具栏; true:展示工具栏; toolbars.showToolbar=false 与 toolbars.toolbar=false 等效 toolbar: ['bold', 'italic', { strikethrough: ['strikethrough', 'underline', 'sub', 'sup', 'ruby', 'customMenuAName'], }, 'size', '|', 'color', 'header', '|', 'drawIo', '|', 'list', 'panel', 'justify', // 对齐方式,默认不推荐这么“复杂”的样式要求 'detail', '|', { insert: ['image', 'audio', 'video', 'link', 'hr', 'br', 'code', 'formula', 'toc', 'table', 'line-table', 'bar-table', 'pdf', 'word' ] }, 'graph', 'export', 'codeTheme', 'switchModel', 'togglePreview', // { // customMenuBName: ['ruby', 'audio', 'video', 'customMenuAName'], //实验室 // }, 'settings', 'customMenuCName', 'theme' ], toolbarRight: ['fullScreen', '|'], bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', 'ruby', '|', 'size', 'color' ], // array or false "float": ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'quickTable', 'code'], // array or false sidebar: ['mobilePreview', 'theme'], // 'copy', customMenu: { customMenuAName: customMenuA, customMenuBName: customMenuB, customMenuCName: customMenuC, }, }, // 打开draw.io编辑页的url,如果为空则drawio按钮失效 drawioIframeUrl: window.location.origin + '/CherryMarkdown/drawio_demo.html', /* 上传文件的时候用来指定文件类型 / fileTypeLimitMap: { video: 'video/', audio: 'audio/', image: 'image/', word: '.doc,.docx', pdf: '.pdf', file: '*', }, callback: { afterChange: this.afterChange, afterInit: this.afterInit, beforeImageMounted: this.beforeImageMounted, // 预览区域点击事件,previewer.enablePreviewerBubble = true 时生效 onClickPreview: this.onClickPreview, // 复制代码块代码时的回调 onCopyCode: this.onCopyCode, // 把中文变成拼音的回调,当然也可以把中文变成英文、英文变成中文 changeString2Pinyin: this.changeString2Pinyin }, // 预览页面不需要绑定事件 isPreviewOnly: false, // 预览区域跟随编辑器光标自动滚动 autoScrollByCursor: true, // 外层容器不存在时,是否强制输出到body上 forceAppend: true, // The locale Cherry is going to use. Locales live in /src/locales/ locale: 'zh_CN' }) }, // 上传通用接口 fileUpload(file) { const formData = new FormData() formData.append('file', file) const request = new XMLHttpRequest() // 图片上传路径修改为自己连接 request.open('POST', process.env.VUE_APP_BASE_API + '/common/upload') request.setRequestHeader('Authorization', "Bearer " + getToken()) request.onload = this.onloadCallback request.send(formData) }, onloadCallback(oEvent) { const currentTarget = oEvent.currentTarget if (currentTarget.status !== 200) { return this.$message({ type: 'error', message: currentTarget.status + ' ' + currentTarget.statusText }) } const resp = JSON.parse(currentTarget.response) let imgMdStr = '' if (resp.code !== 200) { return this.$message({ type: 'error', message: resp.msg }) } if (resp.code === 200) { let fileSuffix = resp.fileName.slice(resp.fileName.lastIndexOf('.')); if (/mp4|avi|rmvb/i.test(fileSuffix)) { imgMdStr = !video[${resp.fileName}](${resp.url}); } else if (/mp3/i.test(fileSuffix)) { imgMdStr = !audio[${resp.fileName}](${resp.url}); } else if (/bmp|gif|jpg|jpeg|png/i.test(fileSuffix)) { imgMdStr =  } else { imgMdStr = [${resp.fileName}](${resp.url}) } } this.cherryInstance.insert(imgMdStr) }, // 全局的URL处理器 urlProcessor(url, srcType) { return url; }, // 变更事件回调 afterChange(text, html) { this.content = text this.$emit('mdChange', html, text) this.$emit('input', text) }, // 初始化事件回调 afterInit(e) { }, // 图片加载回调 beforeImageMounted(e, src) { return { [e]: src } }, // 预览区域点击事件 onClickPreview(event) { }, // 粘贴事件 onCopyCode(event, code) { // 阻止默认的粘贴事件 // return false; // 对复制内容进行额外处理 return code; }, // 获取中文的拼音 changeString2Pinyin(string) { /** 推荐使用这个组件:https://github.com/liu11hao11/pinyin_js 可以在 ../scripts/pinyin/pinyin_dist.js 里直接引用 / const pinyin = require("./pinyin/pinyin.js"); return pinyin.pinyin(string, " "); }, setMarkdown(content, keepCursor) { if (!this.cherryInstance) { // 未加载则重新初始化 this.initCherryMD(content) return } this.cherryInstance.setMarkdown(content) }, getCherryContent() { // 获取markdown内容 return this.cherryInstance.getMarkdown() }, getCherryHtml() { return this.cherryInstance.getHtml() }, getData() { return this.cherryInstance.getHtml() }, /* type:{'pdf'|'img'} / exportMD(type = 'pdf') { this.cherryInstance.export(type) }, /* model{'edit&preview'|'editOnly'|'previewOnly'} */ switchModel(model) { if (this.isInit()) { this.cherryInstance.switchModel(model) } }, insert(content, isSelect = false, anchor = [], focus = true) { this.cherryInstance.insert(content, isSelect, anchor, focus) }, isInit() { if (this.cherryInstance) { return true } this.$message.warning('编辑器未初始化,请检查') return false }, } } style部分 // draw.io样式修改,不能加scoped否则不生效 .cherry-dialog { z-index: 9999 !important; .cherry-dialog-iframe { width: 100%; height: 100%; } } 使用 <CherryMarkdownEditor :edit-model="editFlag ? 'edit&preview' : 'previewOnly'" ref="CherryMarkdown" v-if="active.type === 'note' && !loading" :height="'80vh" v-model="note.richText"></CherryMarkdownEditor> 其中edit-model用于设置编辑的状态: defaultModel 编辑器初始化后的默认模式,一共有三种模式:1、双栏编辑预览模式;2、纯编辑模式;3、预览模式 edit&preview: 双栏编辑预览模式 editOnly: 纯编辑模式(没有预览,可通过toolbar切换成双栏或预览模式) previewOnly: 预览模式(没有编辑框,toolbar只显示“返回编辑”按钮,可通过toolbar切换成编辑模式) height属性可设置为具体高度,默认高度为内容高度。 获取编辑器HTML格式 this.$refs.CherryMarkdown.getData(); 大功告成!!! 补充的是使用过程中遇到的问题 一、安装版本问题 启动报错,默认安装的话是CherryMarkdown的最新版本,与vue2不兼容,选装低版本比如:0.8.19。 二、使用过程中,编辑器内容不更新 没找到什么好办法,加个v-if,更新内容前注销一下。 有什么问题欢迎留言,一起学习交流~
发布于 2022-12-26
Java 中对 List 去重排序
209 热度
0 条评论
Java
一、Java中List集合去除重复数据的五种方法: 1、通过HashSet踢除重复元素 HashSet h = new HashSet(list); list.clear(); list.addAll(h); 2、删除ArrayList中重复元素,保持顺序 Set set = new HashSet(); List newList = new ArrayList(); for (Iterator iter = list.iterator(); iter.hasNext();) { Object element = iter.next(); if (set.add(element)) newList.add(element); } list.clear(); list.addAll(newList); System.out.println( " remove duplicate " + list); 3、把list里的对象遍历一遍,用list.contains()判断是否存在,如果不存在就放入到另外一个list集合中 List listTemp=new ArrayList(); for(int i=0;i<list.size();i++){ if(!listTemp.contains(list.get(i))){ listTemp.add(list.get(i)); } } 4、retainAll和retainAll用法 List<String> a = Arrays.asList ("a", "f", "e", "x", "w"); List<String> b = Arrays.asList ("a", "b", "c", "d"); List<String> c = new List<>(); List<String> d = new List<>(); c = new ArrayList(a); // 得到a, b 的交集。 c.retainAll(b); d = new ArrayList(a); // 合并a, b 值到d 中。 d.addAll(b); // 去掉交集 c 中的所有条目。留下只出现在a 或 b 中的条目。 d.removeAll(c); System.out.println(d); 5、用JDK1.8 Stream中对List进行去重:list.stream().distinct(); List<String> a = new ArrayList<> (); a.add("a"); a.add("b"); a.add("b"); List<String> b = new ArrayList<> (); b.add("a"); b.add("c"); b.add("b"); a.addAll(b); List list=(List) a.stream().distinct().collect(Collectors.toList()); System.out.println(list); 二、Java中对List集合排序的三种方法: 按照本文设计的场景,我们创建一个包含了用户列表的 List 集合,并按用户的年龄从大到小进行排序: 1、使用 Comparable 排序 /** * @author by PHY * @classname ListSortExample2 * @date 2022/12/26 16:02 * @description: 描述 / public class ListSortExample { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(1, 30, "北京")); add(new Person(2, 20, "西安")); add(new Person(3, 40, "上海")); }}; // 使用 Comparable 自定的规则进行排序 Collections.sort(list); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } } // 以下 set/get/toString 使用的是 lombok 的注解 @Getter @Setter @ToString class Person implements Comparable<Person> { private int id; private int age; private String name; public Person(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } @Override public int compareTo(Person p) { return p.getAge() - this.getAge(); } } 以上代码的执行结果,如下图所示: 核心代码如下: 2、使用 Comparator 排序 Comparable 是类内部的比较方法,而 Comparator 是排序类外部的比较器。使用 Comparator 比较器,无需修改原 Person 类,只需要扩充一个 Person 类的比较器就行了,Comparator 的实现方法有以下两种: 新建 Comparator 比较器; 使用 Comparator 匿名类比较器。 其中,第二种实现方法要更简洁一些,我们通过下面的具体代码,来观察一下二者的区别。 2.1、新建 Comparator 比较器 /* * @author by PHY * @classname ListSortExample2 * @date 2022/12/26 16:02 * @description: 描述 / public class ListSortExample2 { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(1, 30, "北京")); add(new Person(2, 20, "西安")); add(new Person(3, 40, "上海")); }}; // 使用 Comparator 比较器排序 Collections.sort(list, new PersonComparator()); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } } /* * 新建 Person 比较器 / class PersonComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { return p2.getAge() - p1.getAge(); } } @Getter @Setter @ToString class Person { private int id; private int age; private String name; public Person(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } } 以上代码的执行结果,如下图所示: 本方法的核心实现代码如下: 2.2、匿名类比较器 比较器 Comparator 可以使用更简洁的匿名类的方式,来实现排序功能,具体实现代码如下: /* * @author by PHY * @classname ListSortExample2 * @date 2022/12/26 16:02 * @description: 描述 / public class ListSortExample2 { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(1, 30, "北京")); add(new Person(2, 20, "西安")); add(new Person(3, 40, "上海")); }}; // 使用匿名比较器排序 Collections.sort(list, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p2.getAge() - p1.getAge(); } }); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } } @Getter @Setter @ToString class Person { private int id; private int age; private String name; public Person(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } } 以上代码的执行结果,如下图所示: 3、使用 Stream 流排序 在 JDK 8 之后可以使用更加简单的方法 Stream 流来实现排序功能,它的实现只需要一行代码,具体实现如下: /* * @author by PHY * @classname ListSortExample3 * @date 2022/12/26 16:02 * @description: 描述 / public class ListSortExample3 { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(1, 30, "北京")); add(new Person(2, 20, "西安")); add(new Person(3, 40, "上海")); }}; // 使用 Stream 排序 list = list.stream().sorted(Comparator.comparing(Person::getAge).reversed()) .collect(Collectors.toList()); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } @Getter @Setter @ToString static class Person { private int id; private int age; private String name; public Person(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } } } 其中 reversed() 表示倒序的意思,如果不使用此方法则是正序。 以上代码的执行结果,如下图所示: 扩展:排序字段为 null 使用 Stream 进行排序时,如果排序的字段出现 null 值就会导致异常发生,具体示例如下: /* * @author by PHY * @classname ListSortExample4 * @date 2022/12/26 16:02 * @description: 描述 / public class ListSortExample4 { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(30, "北京")); add(new Person(10, "西安")); add(new Person(40, "上海")); add(new Person(null, "上海")); // 年龄为 null 值 }}; // 按照[年龄]正序,但年龄中有一个 null 值 list = list.stream().sorted(Comparator.comparing(Person::getAge)) .collect(Collectors.toList()); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } } @Getter @Setter @ToString class Person { private Integer age; private String name; public Person(Integer age, String name) { this.age = age; this.name = name; } } 以上代码的执行结果,如下图所示: 想要解决上述问题,需要给 Comparator.comparing 传递第二个参数:Comparator.nullsXXX,如下代码所示: /* * @author by PHY * @classname ListSortExample4 * @date 2022/12/26 16:02 * @description: 描述 */ public class ListSortExample4 { public static void main(String[] args) { // 创建并初始化 List List<Person> list = new ArrayList<Person>() {{ add(new Person(30, "北京")); add(new Person(10, "西安")); add(new Person(40, "上海")); add(new Person(null, "上海")); }}; // 按照[年龄]正序,但年龄中有一个 null 值 list = list.stream().sorted(Comparator.comparing(Person::getAge, Comparator.nullsFirst(Integer::compareTo))) .collect(Collectors.toList()); // 打印 list 集合 list.forEach(p -> { System.out.println(p); }); } } @Getter @Setter @ToString class Person { private Integer age; private String name; public Person(Integer age, String name) { this.age = age; this.name = name; } } Comparator.nullsFirst 表示将排序字段中的 null 值放到集合最前面,如果想要将 null 值放到集合最后面可以使用 Comparator.nullsLast。 以上代码的执行结果,如下图所示: 以上便是这次整理的Java中对List集合五种去重方法和三种排序方法。
发布于 2022-12-12
获取客户端ip的方法
220 热度
1 条评论
开发杂记
| 像本博客中,有天气以及获取留言者的ip地址,之前一直使用的是搜狐的api,但最近频频报错4.3,无奈自己在后端写了一个。 首先先说一下常规的js通过api获取ip的方法: 搜狐接口: <script src="http://pv.sohu.com/cityjson?ie=utf-8"></script> <script type="text/javascript"> document.write(returnCitySN["cip"]+','+returnCitySN["cname"]) // xxx.xxx.xxx.xxx, xx省xx市 </script> 新浪接口: <!--获取接口数据,注意charset --> <script type="text/javascript" src="http://counter.sina.com.cn/ip/" charset="gb2312"></script> <script type="text/javascript"> //输出接口数据中的IP地址 document.writeln("IP地址:"+ILData[0]+"<br />"); //输出接口数据中的IP地址的类型 document.writeln("地址类型:"+ILData[1]+"<br />"); //输出接口数据中的IP地址的省市 document.writeln("地址类型:"+ILData[2]+"<br />"); //输出接口数据中的IP地址的 document.writeln("地址类型:"+ILData[3]+"<br />"); //输出接口数据中的IP地址的运营商 document.writeln("地址类型:"+ILData[4]+"<br />"); </script> 如果使用这两个api不行的话,可以考虑自己整一个,先看一下我开放出来的一个:http://pnkx.top:8068/open/getIp { "msg": "123.232.10.234", // 返回的IP "code": 200 // 成功的状态码 } 我这是用的java语言、springboot框架: 工具类代码: private String getIpAddress() { String ip = request.getHeader("x-forwarded-for"); if (ip != null && !"".equals(ip) && !"unknown".equalsIgnoreCase(ip)) { if (ip.indexOf (",") > 0) { ip = ip.substring (0, ip.indexOf (",")); } if (ip.equals ("127.0.0.1")) { //根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost (); } catch (Exception e) { e.printStackTrace (); } ip = inet.getHostAddress (); } } if (request.getHeader("X-Real-IP") != null && !"".equals(request.getHeader("X-Real-IP")) && !"unknown".equalsIgnoreCase(request.getHeader("X-Real-IP"))) { ip = request.getHeader("X-Real-IP"); } if (request.getHeader("Proxy-Client-IP") != null && !"".equals(request.getHeader("Proxy-Client-IP")) && !"unknown".equalsIgnoreCase(request.getHeader("Proxy-Client-IP"))) { ip = request.getHeader("Proxy-Client-IP"); } if (request.getHeader("WL-Proxy-Client-IP") != null && !"".equals(request.getHeader("WL-Proxy-Client-IP")) && !"unknown".equalsIgnoreCase(request.getHeader("WL-Proxy-Client-IP"))) { ip = request.getHeader("WL-Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } log.info("用户ip:"+ip); return ip; } 如果有博主不太了解java的,也可以查询下其他语言的获取客户端ip的方法,大同小异。实在不行也可以使用我开放出来的api。 如果有用,记得评论夸奖一下~
发布于 2022-09-01
node的版本控制
129 热度
0 条评论
Vue
vue对node的版本有时候会受到影响; 老项目可能要求14,但是vue3会要求16; 可以使用nvm管理node版本,nvm不要用npm安装,官方已经废弃了这个安装方式,需要去github下载安装程序, nvm下载 安装后简单的使用:nvm -v查看版本,nvm ls查看安装的node版本,nvm install 16.16.0安装该版本node,nvm use16.16.0使用该版本node。 使用nvm install 16.16.0安装node时,可能会报错 找到nvm文件夹 打开settings.txt 在里面添加两句代码 node_mirror:npm.taobao.org/mirrors/node/ npm_mirror:npm.taobao.org/mirrors/npm/ 默认nvm安装路径:C:\Users\用户名\AppData\Roaming\nvm
发布于 2022-07-11
vue3 集成百度翻译
132 热度
0 条评论
Vue
一、准备工作 百度翻译开放平台账号注册 http://api.fanyi.baidu.com/ 注册个人开发者。 具体可看文档说明->http://api.fanyi.baidu.com/doc/12 二、vue中集成 import md5 from 'js-md5'; import { jsonp } from 'vue-jsonp' export function translate(code) { let params ={ q: code, from:'auto', to:'auto', appid:'自己的appid', salt: Math.round(Math.random()10000000000), key: '秘钥' } let sign = md5(params.appid+params.q+params.salt+params.key); return jsonp(http://api.fanyi.baidu.com/api/trans/vip/translate?q=${params.q}&from=${params.from}&to=${params.to}&appid=${params.appid}&salt=${params.salt}&sign=${sign}) } 注:这里没有使用axios,是因为会有跨域! 三、效果 单击复制的代码: /* * 复制 */ const copyText = (text) => { const input = document.createElement("input"); // js创建一个input输入框 input.value = text; // 将需要复制的文本赋值到创建的input输入框中 document.body.appendChild(input); // 将输入框暂时创建到实例里面 input.select(); // 选中输入框中的内容 document.execCommand("Copy"); // 执行复制操作 document.body.removeChild(input); // 最后删除实例中临时创建的input输入框,完成复制操作 ElMessage({ message: "复制成功", type: "success", }); };