首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
2,529 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,147 阅读
3
解决 nginxProxyManager 申请证书时的SSL失败问题
657 阅读
4
Pointer-Focus:一款功能强大的教学、录屏辅助软件
635 阅读
5
使用cspell对项目做拼写规范检查
598 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
项目
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
frp
RabbitMQ
gitlab
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
朱治龙
累计撰写
146
篇文章
累计收到
9
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
项目
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
146
篇与
朱治龙
的结果
2022-10-14
20 个 JS 工具函数助力高效开发
前言日常开发中,面对各种不同的需求,我们经常会用到以前开发过的一些工具函数,把这些工具函数收集起来,将大大提高我们的开发效率。1、校验数据类型export const typeOf = function(obj) { return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() } // Example typeOf('zhuzl') // string typeOf([]) // array typeOf(new Date()) // date typeOf(null) // null typeOf(true) // boolean typeOf(() => { }) // function 2、防抖export const debounce = (() => { let timer = null return (callback, wait = 800) => { timer&&clearTimeout(timer) timer = setTimeout(callback, wait) } })() // Example // 如 vue 中使用 methods: { loadList() { debounce(() => { console.log('加载数据') }, 500) } }3、节流export const throttle = (() => { let last = 0 return (callback, wait = 800) => { let now = +new Date() if (now - last > wait) { callback() last = now } } })() 4、手机号脱敏export const hideMobile = (mobile) => { return mobile.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2") }5、开启全屏export const launchFullscreen = (element) => { if (element.requestFullscreen) { element.requestFullscreen() } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen() } else if (element.msRequestFullscreen) { element.msRequestFullscreen() } else if (element.webkitRequestFullscreen) { element.webkitRequestFullScreen() } }6、关闭全屏export const exitFullscreen = () => { if (document.exitFullscreen) { document.exitFullscreen() } else if (document.msExitFullscreen) { document.msExitFullscreen() } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen() } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen() } }7、大小写转换/** * 大小写转换 * @param str 待转换的字符串 * @param type 1-全大写 2-全小写 3-首字母大写 * @returns */ export const turnCase = (str, type) => { switch (type) { case 1: return str.toUpperCase() case 2: return str.toLowerCase() case 3: return str[0].toUpperCase() + str.substring(1).toLowerCase() default: return str } } // Example turnCase('vue', 1) // VUE turnCase('REACT', 2) // react turnCase('vue', 3) // Vue8、解析URL参数export const getSearchParams = () => { const searchPar = new URLSearchParams(window.location.search) const paramsObj = {} for (const [key, value] of searchPar.entries()) { paramsObj[key] = value } return paramsObj } // Example // 假设目前位于 https://****com/index?id=154513&age=18; getSearchParams(); // {id: "154513", age: "18"}9、判断手机是Andoird还是IOS/** * 1: ios * 2: android * 3: 其它 */ export const getOSType=() => { let u = navigator.userAgent, app = navigator.appVersion; let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); if (isIOS) { return 1; } if (isAndroid) { return 2; } return 3; }10、数组对象根据字段去重/** * 数组对象根据某字段去重 * @param arr 要去重的数组 * @param key 根据去重的字段名 * @returns */ export const uniqueArrayObject = (arr = [], key = 'id') => { if (arr.length === 0) return let list = [] const map = {} arr.forEach((item) => { if (!map[item[key]]) { map[item[key]] = item } }) list = Object.values(map) return list } // Example const responseList = [ { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, { id: 1, name: '黄老爷' }, { id: 2, name: '张麻子' }, { id: 3, name: '树哥' }, { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, ] uniqueArrayObject(responseList, 'id') // [{ id: 1, name: '树哥' },{ id: 2, name: '黄老爷' },{ id: 3, name: '张麻子' }] 11、滚动到页面顶部export const scrollToTop = () => { const height = document.documentElement.scrollTop || document.body.scrollTop; if (height > 0) { window.requestAnimationFrame(scrollToTop); window.scrollTo(0, height - height / 8); } }12、滚动到指定元素位置export const smoothScroll = element =>{ document.querySelector(element).scrollIntoView({ behavior: 'smooth' }) } // Example smoothScroll('#target') // 平滑滚动到 ID 为 target 的元素13、uuidexport const uuid = () => { const temp_url = URL.createObjectURL(new Blob()) const uuid = temp_url.toString() URL.revokeObjectURL(temp_url) //释放这个url return uuid.substring(uuid.lastIndexOf('/') + 1) } // Example uuid() // d9ffece8-0a9d-4ca2-bbab-0af7bc0f71df14、金额格式化/** * 金额格式化 * @param number 要格式化的数字 * @param decimals 保留几位小数 * @param dec_point 小数点符号 * @param thousands_sep 千分位符号 * @returns */ export const moneyFormat = (number, decimals, dec_point, thousands_sep) => { number = (number + '').replace(/[^0-9+-Ee.]/g, '') const n = !isFinite(+number) ? 0 : +number const prec = !isFinite(+decimals) ? 2 : Math.abs(decimals) const sep = typeof thousands_sep === 'undefined' ? ',' : thousands_sep const dec = typeof dec_point === 'undefined' ? '.' : dec_point let s = '' const toFixedFix = function(n, prec) { const k = Math.pow(10, prec) return '' + Math.ceil(n * k) / k } s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.') const re = /(-?\d+)(\d{3})/ while (re.test(s[0])) { s[0] = s[0].replace(re, '$1' + sep + '$2') } if ((s[1] || '').length < prec) { s[1] = s[1] || '' s[1] += new Array(prec - s[1].length + 1).join('0') } return s.join(dec) } // Example moneyFormat(10000000) // 10,000,000.00 moneyFormat(10000000, 3, '.', '-') // 10-000-000.00015、存储操作class MyCache { constructor(isLocal = true) { this.storage = isLocal ? localStorage : sessionStorage } setItem(key, value) { if (typeof (value) === 'object') value = JSON.stringify(value) this.storage.setItem(key, value) } getItem(key) { try { return JSON.parse(this.storage.getItem(key)) } catch (err) { return this.storage.getItem(key) } } removeItem(key) { this.storage.removeItem(key) } clear() { this.storage.clear() } key(index) { return this.storage.key(index) } length() { return this.storage.length } } const localCache = new MyCache() const sessionCache = new MyCache(false) export { localCache, sessionCache } // Example localCache.getItem('user') sessionCache.setItem('name','树哥') sessionCache.getItem('token') localCache.clear() 16、下载文件/** * 下载文件 * @param api API接口路径 * @param params 请求参数 * @param fileName 文件名 * @param type HTTP请求方式,默认为get */ const downloadFile = (api, params, fileName, type = 'get') => { axios({ method: type, url: api, responseType: 'blob', params: params }).then((res) => { let str = res.headers['content-disposition'] if (!res || !str) { return } let suffix = '' // 截取文件名和文件类型 if (str.lastIndexOf('.')) { fileName ? '' : fileName = decodeURI(str.substring(str.indexOf('=') + 1, str.lastIndexOf('.'))) suffix = str.substring(str.lastIndexOf('.'), str.length) } // 如果支持微软的文件下载方式(ie10+浏览器) if (window.navigator.msSaveBlob) { try { const blobObject = new Blob([res.data]); window.navigator.msSaveBlob(blobObject, fileName + suffix); } catch (e) { console.log(e); } } else { // 其他浏览器 let url = window.URL.createObjectURL(res.data) let link = document.createElement('a') link.style.display = 'none' link.href = url link.setAttribute('download', fileName + suffix) document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(link.href); } }).catch((err) => { console.log(err.message); }) } // Example downloadFile('/api/resources/download', {id}, '文件名')17、时间操作关于时间操作,没必要自己再写一大串代码了,强烈推荐使用 [Day.js](https://dayjs.gitee.io/zh-CN/) > Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样。 > 如果您曾经用过 Moment.js, 那么您已经知道如何使用 Day.js 。18、深拷贝此方法存在一定局限性:一些特殊情况没有处理: 例如Buffer对象、Promise、Set、Map。如果确实想要完备的深拷贝,推荐使用 lodash 中的 cloneDeep 方法。export const clone = parent => { // 判断类型 const isType = (obj, type) => { if (typeof obj !== "object") return false const typeString = Object.prototype.toString.call(obj) let flag; switch (type) { case "Array": flag = typeString === "[object Array]" break; case "Date": flag = typeString === "[object Date]" break; case "RegExp": flag = typeString === "[object RegExp]" break; default: flag = false } return flag; }; // 处理正则 const getRegExp = re => { var flags = "" if (re.global) flags += "g" if (re.ignoreCase) flags += "i" if (re.multiline) flags += "m" return flags; }; // 维护两个储存循环引用的数组 const parents = [] const children = [] const _clone = parent => { if (parent === null) return null if (typeof parent !== "object") return parent let child, proto if (isType(parent, "Array")) { // 对数组做特殊处理 child = [] } else if (isType(parent, "RegExp")) { // 对正则对象做特殊处理 child = new RegExp(parent.source, getRegExp(parent)) if (parent.lastIndex) child.lastIndex = parent.lastIndex } else if (isType(parent, "Date")) { // 对Date对象做特殊处理 child = new Date(parent.getTime()) } else { // 处理对象原型 proto = Object.getPrototypeOf(parent) // 利用Object.create切断原型链 child = Object.create(proto) } // 处理循环引用 const index = parents.indexOf(parent) if (index != -1) { // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象 return children[index] } parents.push(parent) children.push(child) for (let i in parent) { // 递归 child[i] = _clone(parent[i]) } return child; } return _clone(parent) }19、模糊搜索/** * 数组对象模糊搜索 * @param list 原数组对象 * @param keyWord 查询的关键词 * @param attribute 数组需要检索的对象属性,默认问name * @returns */ export const fuzzyQuery = (list, keyWord, attribute = 'name') => { const reg = new RegExp(keyWord) const arr = [] for (let i = 0; i < list.length; i++) { if (reg.test(list[i][attribute])) { arr.push(list[i]) } } return arr } // Example const list = [ { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, { id: 4, name: '汤师爷' }, { id: 5, name: '胡万' }, { id: 6, name: '花姐' }, { id: 7, name: '小梅' } ] fuzzyQuery(list, '树', 'name') // [{id: 1, name: '树哥'}]20、遍历树节点export const foreachTree = (data, callback, childrenName = 'children') => { for (let i = 0; i < data.length; i++) { callback(data[i]) if (data[i][childrenName] && data[i][childrenName].length > 0) { foreachTree(data[i][childrenName], callback, childrenName) } } } // Example const treeData = [{ id: 1, label: '一级 1', children: [{ id: 4, label: '二级 1-1', children: [{ id: 9, label: '三级 1-1-1' }, { id: 10, label: '三级 1-1-2' }] }] }, { id: 2, label: '一级 2', children: [{ id: 5, label: '二级 2-1' }, { id: 6, label: '二级 2-2' }] }, { id: 3, label: '一级 3', children: [{ id: 7, label: '二级 3-1' }, { id: 8, label: '二级 3-2' }] }], // 假设我们要从树状结构数据中查找 id 为 9 的节点 let result foreachTree(data, (item) => { if (item.id === 9) { result = item } }) console.log('result', result) // {id: 9,label: "三级 1-1-1"} 关于本文作者:呛再首https://juejin.cn/post/7132714583399071758
2022年10月14日
23 阅读
0 评论
0 点赞
2022-09-20
记一次KBS项目实施经验分享
背景介绍KBS 是我入职并行后接触的第一个项目,该项目之前有同事基于 OpenKB(采用 Node + Express + Mongodb技术栈的开源知识库应用,github地址:https://github.com/mrvautin/openKB) 搭建过一个简单的知识库,但是初步实施后的效果不是很符合预期,便安排我着手知识库相关的后续工作。以前在 TRS 的时候有接触过三一项目的知识库项目,该项目基于 TRS 自主研发的企业知识门户产品 —— TRS EKP V6.5 进行实施,是整合了公司的EKP、全文检索系统、数据网关系统等产品综合实施的一套企业内部知识分享的平台,是一个将公司内部各领域专家们的经验进行梳理沉淀的知识管理工具。经过前期的了解,我们将要做的知识库跟之前实施的三一知识库项目有非常大的出入,主要是用于一些产品文档的发布,类似阿里云、腾讯云的产品文档生成工具。最开始有考虑采用纯前端的方式实现,如当前较主流的VuePress实现,这样可以很方便的实现markdown文档编写 + algolia全文检索,并能基于git很方便达到文档版本控制的目的,但在深入沟通后,发现该方式不能满足其他的需求,如支持审核发布、支持用户认证后才可浏览。所以只能考虑其他的解决方案了。经过沟通后,决定先找个开源的 CMS,做个基础效果,再论证可行性技术调研及产品选型结合以往工作经验,经过沟通,最终考虑采用CMS的方式来实现我们的知识库项目。可考虑开源 CMS 或低成本的采购商业 CMS。以下是一些当时(2021-07-05)调研的一些开源 CMS 情况:经过沟通,最终选择了基于 jspxCMS 来进行项目可行性验证实施工作,主要有如下原因:1、jspxCMS 采用 SpringBoot + FreeMarker 跟我们团队技术栈较接近2、编辑器支持Ueditor 和 Markdown 两种编辑模式,适合给运营人员进行富文本排版及程序员轻量内容排版3、可商用,但需在页面底部保留jspxCMS版权声明,购买商业版后可去除,商业版价格也就几千块钱,且商业版支持站群模式,功能试用满足运营要求的话,可购买商业版License。项目实施过程1、搭建示例效果项目初期在本地部署了 jspxCMS 应用,准备基于该CMS实现基本的内容管理及网站发布工作。有阿里云文档做参考,主要是仿站,经过两天的实施工作,在没有UI参与的情况下实现了站点的最初的界面效果:知识库首页产品首页内容详情页搜索结果页按照目前的规划,KBS 系统可以通过栏目的方式很方便的做成公司整个产品体系的知识库门户。2、系统功能改造经过需求梳理,整理了如下待办事项:1、知识库站点模板{x} 知识库首页模板 zzl{x} 产品页模板 zzl{x} 内容页模板 zzl{x} 实现检索功能 zzl{x} 用户登录、认证对接 zzl{x} 北龙超云版知识库模板(优先级低) zzl2、后台改造{x} 改造成并行蓝风格 zzl{x} 改造左侧菜单导航样式 zzl{x} 改造顶部显示逻辑 zzl{x} 将列表中的操作列调整到表格最后一列 zzl{x} 去掉左侧菜单中商业版相关菜单入口 zzl{x} 去掉系统业务界面中商业版相关的功能入口 zzl{x} 将启动方式改成 spring-boot 模式,即 java -jar [xxx].war ,不用放到 tomcat 里 hx{x} 调研是否能支持多例 hx{x} 用户认证对接 hx{x} 支持多环境配置,即有生产,stage 和本地。 使用配置中心 hx{x} 支持 JDK 17 hx{x} 购买商业版后整合商业版相关功能模块 zzl{x} 应用资源文件分离改造。将上传文件目录、模板目录、索引文件目录从war包中分离 zzl{x} 后台界面支持并行蓝风格和北龙红风格 zzl3、技术实现细节完成该项目后主要有如下可圈可点的部分技术细节用于分享1、知识库前台模板制作网站模板的工作其实主要就是在html静态文件中插入 CMS 的标签,并结合CMS的静态资源引入机制改写页面相关的静态资源文件。一般会提取一些公共底部、公共底部模板之类的。CMS 实施过程中常用的标签其实并不多,主要也就是获取栏目列表、内容列表、栏目信息、内容信息这些最基础的。经过实践,发现jspxCMS的标签还是非常灵活的,支持模板嵌套、标签嵌套等jspxCMS 使用的freemarker实现静态化,自带了非常灵活的判断、循环等表达式。经过整理,部分标签使用记录如下:获取栏目列表及栏目信息以下是本项目中获取知识库左侧栏目结构树的模板示例<ul class="menu-list-container"> [@NodeList parentId=node.id;list] [#list list as pnode] <li class="level1" data-node-id="${pnode.id}"> <a href="javascript:void(0);"> <i class="icon-triangle kbsIconfont kbs-triangle"></i> <span class="menu-item-text">${pnode.name}</span> </a> <ul> [@NodeList parentId=pnode.id;subNodes] [#list subNodes as subNode] <li class="level2"> <a href="javascript:void(0);"> <i class="icon-triangle kbsIconfont kbs-triangle"></i> <span class="menu-item-text">${subNode.name}</span> </a> <ul> [@InfoList nodeId=subNode.id;subNodeDocs] [#list subNodeDocs as info] <li class="level3"> <a href="${info.url}" target="_self"> <span class="menu-item-text">${info.title}</span> </a> </li> [/#list] [/@InfoList] </ul> </li> [/#list] [/@NodeList] [@InfoList nodeId=pnode.id;infos] [#list infos as info] <li class="level2"> <a href="${info.url}" target="_self"> <span class="menu-item-text">${info.title}</span> </a> </li> [/#list] [/@InfoList] </ul> </li> [/#list] [/@NodeList] [@InfoList nodeId=node.id;infos] [#list infos as info] <li class="level1"> <a href="${info.url}" target="_self"> <span class="menu-item-text">${info.title}</span> </a> </li> [/#list] [/@InfoList] </ul>获取内容列表及内容信息 <div class="box-product-docs"> <div class="box"> <h3 class="box-title">最新发布</h3> <div class="box-content row"> [@InfoList nodeId=node.id limit='6' isIncludeChildren='true';list] [#list list as info] <div class="col-md-4 col-sm-6"> <div class="list-body"> <a href="${info.url}"> <p class="list-title text-left">${info.title}</p> <p class="list-content">${info.description}</p> <span class="list-more">详情+</span> <span class="list-date">更新时间:${info.publishDate?string('yyyy-MM-dd HH:mm')}</span> </a> </div> </div> [/#list] [/@InfoList] </div> </div> <div class="box"> <h3 class="box-title">热门知识</h3> <div class="box-content row"> [@InfoList nodeId=node.id sort='views desc' limit='6' isIncludeChildren='true';list] [#list list as info] <div class="col-md-4 col-sm-6"> <div class="list-body"> <a href="${info.url}"> <p class="list-title text-left">${info.title}</p> <p class="list-content">${info.description}</p> <span class="list-more">详情+</span> <span class="list-date">浏览次数:${info.views}</span> </a> </div> </div> [/#list] [/@InfoList] </div> </div> </div> 模板嵌套[#include "include_header.html"/]当前位置<div class="body-position"> <i class=" kbsIconfont kbs-home"></i> [#list node.hierarchy as n]<a href="${n.url}">${n.name}</a>[#if n_has_next] > [/#if][/#list] </div>内容分页[@InfoPage nodeId=node.id pageSize='15';pagedList] <ul class="list-unstyled mt10"> [#list pagedList.content as info] <li style="padding:15px 0;border-bottom:1px dotted #ccc;"> [#if info.withImage] <div class="left" style="padding:3px 10px 3px 0;width:22%;"> <a href="${info.url}" target="_blank"><img src="${info.smallImageUrl}" width="138" height="92"/></a> </div> [/#if] <div class="left" style="[#if info.withImage]width:75%;[/#if]"> <div>[@A bean=info class='ff-yh fs18 a c-000' target="_blank"/]</div> <div class="" style="line-height:1.8;padding:2px 0;color:#818181;">${substring(info.description,100,'...')}</div> <div class="" style="padding:2px;color:#a1a1a1;">${info.publishDate?string('yyyy-MM-dd HH:mm:ss')}</div> </div> <div class="clear"></div> </li> [/#list] </ul> <div class="mt20"> [#include "inc_page.html"/] </div> [/@InfoPage] 分页嵌套模板(inc_page.html)[#escape x as (x)!?html] <div class="pager"> [#if page>1]<a href="${paging(1)}" class="page"><i class="kbsIconfont kbs-first"></i></a>[/#if] [#if page>1] <a href="${paging(page-1)}" class="page"><i class="kbsIconfont kbs-prev"></i></a> [/#if] [#assign start=page-4/][#if start<1][#assign start=1/][/#if] [#assign end=start+8/][#if end>pagedList.totalPages][#assign end=pagedList.totalPages/][/#if][#if end<1][#assign end=1/][/#if] [#if end-start<8][#assign start=end-8/][/#if][#if start<1][#assign start=1/][/#if] [#list start..end as p] <a[#if page!=p] href="${paging(p)}"[/#if] class="[#if page!=p]page[#else]page-curr[/#if]">${p}</a> [/#list] [#if page < pagedList.totalPages] <a href="${paging(page+1)}" class="page"><i class="kbsIconfont kbs-next"></i></a> <a href="${paging(pagedList.totalPages)}" class="page" ><i class="kbsIconfont kbs-last"></i></a> [/#if] </div> [/#escape] 超级方便的app自由模版app自由模板,主要是可以传参给模板获取一些指定的内容片段,这就极大的扩展了模板的功能,目前我们用得最多的场景是不同系统间通过app模板获取KBS的内容列表及内容详情等json数据,然后在异构系统中进行个性化展现。app自由模板的官网介绍如下(https://www.ujcms.com/documentation/269.html):整理了部分自由模板内容以作备忘:1、内容列表页(app_newsPage.html)[#-- 栏目内容分页列表数据,用于列表新品快报、公告更多页面分页显示数据 --]{ [@InfoPage node=Param.nodeCode pageSize=Param.pageSize page=Param.pageNum;pagedList] "total":${pagedList.totalElements}, "size":${pagedList.size}, "pages":${pagedList.totalPages}, "current":${pagedList.number + 1}, "records": [ [#list pagedList.content as info] { "id":"${info.id}", "title":"${info.title?js_string}", "publishDate":"${info.publishDate?string('yyyy-MM-dd HH:mm:ss')}", "url":"${info.url?js_string}" }[#if info_has_next],[/#if] [/#list] ] [/@InfoPage] } 2、内容详情页(app_newsDetail.html)[#-- 内容详情接口,用于点击查看内容详情 --][@Info id=Param.articleId;bean] { "id":"${bean.id}", "url":"${bean.url?js_string}", "title":"${bean.title?js_string}", "publishDate":"${bean.publishDate?string('yyyy-MM-dd HH:mm')}", "content":"${bean.text?replace("/uploads/1/","${site.domain}/uploads/1/")?js_string}", "editorType":"${bean.customs.text_editor_type}" } [/@Info] 2、应用资源文件分离改造jspxCMS 默认的部署方式是将war包部署到tomcat下,相关的应用资源文件(模板文件、后台上传的图片及音视频文件、静态化文件等)也都在应用根目录下,功能强大的jspxCMS提供了发布点功能,将上传的资源文件、静态化的html文件生成到指定的目录下,但是该方式功能有限,不能完全满足我们的需求,如索引文件目录、站点模板文件等均不可配置。我们的实际需求是需要将应用采用docker部署并使用java -jar 的方式进行启动,那样就需要将原有跟应用整合在一起的资源文件分离出来,独立部署的其他存储目录中。充分理解该需求及使用场景后,特制定了本改造方案。改造过程如下:1、application.yml 增加分离文件相关根目录配置信息经过前期梳理,考虑到应用实际情况,将分离文件拆分为两个目录:kbs.appDataPath:应用数据(临时文件/日志/缓存/索引等)的存放路径kbs.resourceRootPath:需本地持久化保存应用资源数据(模板数据/上传文件/静态化文件等)的存放路径目录支持%{Parent}、%{Self}这样的相对路径进行占位,默认为应用当前目录下的 appdata 和 KBSData 目录。亦可在启动应用时增加启动参数的方式修改路径,如--kbs.resourceRootPath=c:/workspace/KBSData/kbs: appDataPath: '%{Parent}/appdata/' resourceRootPath: '%{Parent}/KBSData/'2、启动应用时对目录进行初始化阅读源代码后,发现应用在启动时,有调用Constants.loadEnvironment()方法,我们可以利用该方法作为入口,调用我们的目录初始化方法 KbsConfig.init(),在应用启动时初始化相关目录,保障相关资源目录是绝对存在的。KbsConfig 相关核心代码如下:/** * 知识库初始化配置 * @author 朱治龙 * @Date 2021-08-27 10:18:32 */ @Component public class KbsConfig { private static String appDataPathInConfig; private static String resourceRootPathInConfig; /** * 应用数据目录 */ protected static String appDataPath = null; /** * 资源数据目录 */ protected static String resourceDataPath = null; public static void init(Environment env) { appDataPathInConfig = env.getProperty("kbs.appDataPath"); resourceRootPathInConfig = env.getProperty("kbs.resourceRootPath"); initPaths(); } /** * 初始化相关文件路径 */ private static void initPaths() { // 初始化应用数据目录 String applicationRealPath = KbsUtil.getApplicationRealPath(); String tempAppDataPath = applicationRealPath; if (ObjectUtil.isNotEmpty(appDataPathInConfig)) { tempAppDataPath = appDataPathInConfig; tempAppDataPath = KbsUtil.replacePathHolder(tempAppDataPath); } else { tempAppDataPath += "appdata/"; } appDataPath = tempAppDataPath; AppDataPath.initialAppDataPath(); // 初始化资源目录 String tempResourceDataPath = applicationRealPath; if (ObjectUtil.isNotEmpty(resourceRootPathInConfig)) { tempResourceDataPath = resourceRootPathInConfig; tempResourceDataPath = KbsUtil.replacePathHolder(tempResourceDataPath); } else { tempResourceDataPath += "KBSData"; } resourceDataPath = tempResourceDataPath; ResourceDataPath.initialResourcePath(); } /** * 获取应用数据目录 * @return */ public static String getAppDataPath() { if(ObjectUtil.isEmpty(appDataPath)) { initPaths(); } return appDataPath; } /** * 获取应用数据目录 * @return */ public static String getResourceRootPath() { if(ObjectUtil.isEmpty(resourceDataPath)) { initPaths(); } return resourceDataPath; } }3、改造模板、索引文件、上传资源资源文件的代码逻辑相关逻辑主要涉及 jspxCMS 的核心代码,均在阅读相关源码后进行改造,此处就不做代码罗列了。4、系统资源目录中的静态资源文件提供对外访问服务/** * 系统资源目录中的静态资源文件提供对外访问服务 * @author 朱治龙 * @Date 2021-08-27 14:48:36 */ @Configuration public class KbsWebMvcConfigurer implements WebMvcConfigurer { /** * 资源目录相关文件提供后台访问 */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**").addResourceLocations("file:" + ResourceDataPath.getUploadsPath() + "/"); registry.addResourceHandler("/template/**").addResourceLocations("file:" + ResourceDataPath.getTemplatePath() + "/"); } /** * 默认首页跳转到index.html */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("redirect:/index.html"); } }3、后台界面支持并行蓝风格和北龙红风格在之前对 jspxCMS 改造为并行蓝风格时,有将并行蓝风格相关的样式文件提取到paratera-kbs.css中,项目在升级改造过程中后端的同事有整合Spring Cloud 及配置中心组件spring cloud config,可在配置中心中设置当前主题信息,然后应用拿到主题信息后调用不同的样式文件,即可达到同一套后台代码展现不同风格的目的。具体实现过程如下:1、配置中心增加 kbs.theme-mode 配置项值为 blsc 或 paratera,若不配置则默认为 paratera2、全局注入主题风格变量,名称为themeMode在阅读源码后,修改后台拦截器 BackInterceptor 相关的代码逻辑,全局注入themeMode变量,便于在后台jsp文件中拿到该值进行主题相关业务逻辑判断3、修改后台jsp视图文件主要修改代码为:实施成果后台效果1、并行知识库2、北龙知识库前台效果并行知识库1、站点首页2、产品页3、内容详情页4、检索结果页北龙知识库1、站点首页2、产品页3、内容详情页4、检索结果页
2022年09月20日
79 阅读
0 评论
0 点赞
2022-09-15
思源笔记docker私有化部署及使用体验分享
说明考虑到网络问题、数据隐私和安全性,很多人都无法放心将所有笔记保存在 Notion、Wolai 这类纯在线服务上,一旦服务变卦 (比如收费、倒闭等) 都会很被动。所以,如果有一款既拥有各种先进特性,还能自建服务器的开源笔记软件,那就非常完美了!而它就是思源笔记以下是思源笔记官网的介绍,着实让人心动:部署官网提供 Docker 方式,让私有化部署便捷了不少。只需要两条命令即可:# 拉取镜像 docker pull b3log/siyuan # docker 运行 docker run --name siyuan -it -d --restart=always -v /data/siyuan/workspace:/siyuan/workspace -p 6806:6806 -u $(id -u):$(id -g) b3log/siyuan --workspace=/siyuan/workspace或使用 docker-compose 方式部署:services: siyuannote: image: b3log/siyuan container_name: siyuannote privileged: true restart: always # 可修改下方的xxxxxx为自己的访问授权码 command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxxxxx'] user: '1000:1000' ports: - 6806:6806 volumes: - ./data:/siyuan/workspace environment: # A list of time zone identifiers can be found at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ=Asia/Shanghai注意: 镜像中是使用默认创建的普通用户 siyuan(uid 1000/gid 1000)来启动内核进程的,所以在宿主机创建工作空间文件夹时请注意设置该文件夹所属用户组:chown -R 1000:1000 ./data,命令行启动容器时需要带参数 -u 1000:1000。配置 nginx 对外提供服务我使用的腾讯云的服务器,将思源笔记使用docker 运行后,通常使用 nginx 代理对外提供应用服务。nginx 代理配置信息如下server { listen 80; server_name siyuannote.work.zhuzhilong.com; index index.html index.htm default.htm default.html; root /www/wwwroot/siyuannote.work.zhuzhilong.com; location / { proxy_pass http://10.0.12.15:6806; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; add_header X-Cache $upstream_cache_status; #Set Nginx Cache add_header Cache-Control no-cache; } access_log /www/wwwlogs/siyuannote.work.zhuzhilong.com.log; error_log /www/wwwlogs/siyuannote.work.zhuzhilong.com.error.log; }配制好后,重启 nginx 服务即可使用我们nginx配置的域名(siyuannote.work.zhuzhilong.com)访问了。初体验国际化初次访问的时候,默认是英文版的,可以按Alt + P,在打开的对话框中,选择 Appearance -> Language 中选择 简体中文:使用体验Block编辑思源笔记在使用体验上,较常规markdown编辑器,一大特色就是Block编辑模式,按下 /后可快速使用内置 20+ 种类型的块元素和 10+ 种行级元素。表格排版对于一些结构化的数据,难免会使用表格进行布局,而常规的markdown 在使用表格时异常难使用,思源笔记提供右键方式操作表格,也还算灵活:粘贴 Markdown初次使用的时候,直接把 markdown 内容在编辑器中Ctrl + V 粘贴,发现直接变成代码块了(2024-06-28更新:该问题已在试用的最新版本(版本号: v3.0.17)修复)。感觉有些不习惯,这一点语雀做得最好。在快捷键中查看了下 粘贴为纯文本方式 ,可以使用 Ctrl + Shift + V。从快捷键配置界面可以看出思源笔记的快捷键还是很丰富的,而且可以自定义。粘贴剪贴板中的图片经验证可直接粘贴剪贴板中的截图图片上传到服务器中,图片上传后持久化存储在服务器思源笔记工作目录的assets目录中。没有看到可以配置图床的地方。安全配置思源笔记部署后默认是不需要密码即可访问的,我们私有化部署更多的是要控制只有我们自己能访问。可在配置界面的 关于 -> 访问授权码 中 设置授权码 (2024-06-28更新:在试用的最新版本(版本号: v3.0.17)已改为在部署时指定访问授权码,界面中不再提供配置入口):配置后将只能输入授权码解锁后才可访问:功能扩展思源笔记提供「集市」模块,可在线下载主题、模板、图标、模板、挂件,初步使用感觉还是蛮方便的。插件机制给思源笔记扩展了无限可能,还有很多功能可以在后续的使用中逐步体验。尾声Typora 开启强制收费模式后,有短暂的使用过 Mark Text,但是发现 Mark Text 在打开较大 Markdown 文件时比较卡顿。偶然在公众号中看到推荐的思源笔记,通过官网了解后,便有了试用的冲动。使用下来,感觉可以作为后续的主力 Markdown 编辑器使用。Links官网:https://b3log.org/siyuan/思源笔记Docker镜像:https://hub.docker.com/r/b3log/siyuan
2022年09月15日
2,529 阅读
0 评论
0 点赞
2022-09-06
扫码登录项目实践
背景说明随着移动端应用的普及,扫码登录在日常的登录认证过程中已经非常普及了,在提升用户便利的同时,由于减少了账号密码的输入,在一定程度上也起到了增强安全性的目的。随着控制台产品的迭代,PC端界面及功能已趋于稳定,考虑到目前移动终端更便于用户使用,经过公司内部层层审批,控制台移动端的项目顺利立项。作为充分为用户提供便利的移动端应用,扫码登录功能便自然的纳入了起基础功能。登录认证的本质扫码登录本质上也是一种登录认证方式。既然是登录认证,要做的也就两件事情!告诉系统我是谁向系统证明我是谁比如账号密码登录,账号就是告诉系统我是谁, 密码就是向系统证明我是谁;比如手机验证码登录,手机号就是告诉系统我是谁,验证码就是向系统证明我是谁;那么扫码登录是怎么做到这两件事情的呢?扫码登录过程中移动端是已登录状态,这两件事对移动端而言都是明确的,登录过程中的重点是怎么把移动端的登录状态传递给PC端。在这个过程中主要就是通过扫码的二维码了,二维码本质上就是一串具有唯一性的字符串。通过这个字符串便可以将已登录方授权未登录端进行登录了。技术方案根据项目实际情况,参与扫码登录的应用为console-ui、consent、console-biz、app:console-ui :控制台前端,需登录的应用consent :用户认证端console-biz :控制台后端服务app :移动端应用,微信端H5网页实现流程在使用超算云服务控制台应用时,由 console-ui 判断用户是否登录,未登录的情况下将自动跳转到 consent 进行登录操作,本需求将修改consent登录界面,在已有的账号登录基础上增加扫码登录机制,默认采用账号登录方式,当用户切换登录方式为扫码登录时,将调用console-biz提供的接口获取 scanId,使用 QRCode.js 生成携带scanId参数的app端网页认证URL的二维码。用户使用微信扫一扫功能,可直接打开app端的指定页面,在该页面完成用户确认授权操作,consent通过轮询二维码状态接口,当获取到用户在 app端的确认操作后,携带 scanId 参数跳转到 console-ui,在console-ui 调用接口获取用户 token 信息完成登录。整体交互流程如下所示:二维码生命周期码登录的核心是对二维码生命周期的管理,为了更好地理解扫码登录过程,将上图中二维码生命周期相关的核心流程梳理如下:二维码状态数据字典字典项字典值说明未使用1获取scanId时设置初始状态为该值已扫码(未授权)2app端扫码后更新状态为已扫码已授权(登录)3在app授权界面单击“确认授权”已使用4在app端授权界面确认授权,然后在console-ui中获取到用户token后已过期5超过5分钟redis中不存在记录则返回该值取消授权(登录失败)6在app授权界面单击“取消授权”数据存储方案scanId作为整个扫码登录过程的核心,后端需存储scanId整个生命周期的状态值及需交换的用户数据,而每个 scanId 仅有5分钟的有效期,基于 Redis 的过期策略很容易就能实现这个目的,且比关系型数据库在数据存取上有更优异的表现。故本功能模块相关的数据统一存储于 Redis。后端实现方案后端基于现有的console-biz工程添加新的接口用于前端对接,相关接口见“接口设计”章节。前端实现方案在需求中参与前端开发工作的有consent、console-ui、app三端。1、consent:负责登录界面的交互及展示,基于QRCode.js实现二维码生成,轮询获取二维码状态。2、console-ui:在获取到consent授权回调后,通过scanId调用接口获取用户token实现用户在PC端登录。3、app:扫码后直接跳转到app指定的授权页面,进入授权页面后实现。扫码操作的入口有两个:微信扫一扫、app应用首页顶部的扫一扫。使用app应用首页的扫一扫功能需要解析获取二维码URL中的scanId,然后跳转到授权页面进行授权操作。4、单击某功能时使用component组件渲染对应功能的抽屉功能进行显示接口设计后端以 Restful API 方式为前端提供接口,根据项目情况涉及的接口列表如下:获取scanId接口(PC端调用)获取scanId状态接口(PC端轮询调用)app端扫码接口app端授权接口授权成功后获取登录信息接口(PC端调用)效果截图相关功能将在控制台移动端功能上线后,所有并行用户可线上体验。一、PC端截图:1、初始状态:2、刷新时有个 gif加载动图:3、已扫码:4、已失效:5、取消授权:二、移动端截图:1、正常状态:2、已被使用:3、单击取消授权:4、单击确认授权:5、授权操作时二维码已过期:
2022年09月06日
26 阅读
0 评论
0 点赞
2022-09-01
数据库设计挑战问题
缘起聊天内容整理文字如下问题现有如下表:作业记录表T1作业ID作业日期机时记录时间Job0012022/01/0110002022/01/02Job0022022/01/0120002022/01/02作业扣费表T2作业ID作业日期机时扣费扣费时间Job0012022/01/0110001002022/01/02Job0022022/01/0120002002022/01/02在正常情况下,一切可以工作得很好,但是事实并非理想,作业调度器也会出故障,导致作业机时发生变化。例如在 2022/01/03 重新采集作业时 Job001 的机时从 1000 又变回了 500,但系统对 Job001 已完成了 1000 机时的扣费问题:如何设计数据库表在不修改已写入的历史记录的前提下,解决作业机时发生变化的问题?情境说明:机时变化是不可抗力因素,1000是错误的,后来500是正确的,但也可能最终200是正确的最终时间其实不确定,如果非要给个期限,可以限定为一年内变动都是可能发生的设计字段可以随意,但已存在的记录数据不能变化了增加新表,新字段都不受任何限制综合要求如下:前提:可以增加新表,可以在原先这两个表基础上加字段,但是不能修改或者删除这两个表已有的字段目标:以当前机时作为扣费依据,历史多扣或者少扣需要进行补扣或者补偿我的解答综上问题,我的解决思路如下:1、对最关键的作业记录表增加内容Hash字段,内容为 md5(作业ID+作业日期+机时),该字段可作为主键,由于数据在入库前用于计算Hash的内容都是已知的,作为主键字段可以最大程度的利用主键索引提升检索效率。如果这个Hash数据不存在则在下次 作业调度器 同步作业记录时自动计入 T1 表,如果已存在则直接忽略2、T2 表关联 T1 表的 DataHash字段,主要用于判断作业是否已扣费,便于后续扣费任务的执行3、增加 「异常作业退费记录表T3」,用于对之前已扣费的作业进行退费行为记录。4、若出现重复的情况(作业ID跟作业日期相同),将上一次作业(以记录时间排序)已扣费记录进行退费,并记入T3表调整后的表结构记录如下:作业记录表T1DataHash字段内容为:md5(作业ID+作业日期+机时),如:md5(Job001-2022/01/01-1000)作业ID作业日期机时记录时间DataHashJob0012022/01/0110002022/01/0215dc9093ab0febf9aff2810891ef1a64Job0022022/01/0120002022/01/02fa8f51fb2f36087a9b4c6deacf5f1dd2Job0012022/01/015002022/08/304ec0fea71753f4a7585dc4cf4dd460f2作业扣费表T2作业ID作业日期机时扣费扣费时间作业记录DataHashJob0012022/01/0110001002022/01/0215dc9093ab0febf9aff2810891ef1a64Job0022022/01/0120002002022/01/02fa8f51fb2f36087a9b4c6deacf5f1dd2Job0012022/01/01500502022/08/304ec0fea71753f4a7585dc4cf4dd460f2异常作业退费记录表T3作业ID作业日期机时退费退费时间作业记录DataHashJob0012022/01/0110001002022/08/3015dc9093ab0febf9aff2810891ef1a64其他小伙伴的解答胡大神毛老师
2022年09月01日
18 阅读
0 评论
0 点赞
2022-08-31
正则表达式-元字符
特殊单字符空白符范围量词
2022年08月31日
24 阅读
0 评论
0 点赞
2022-08-06
Keyviz – 开源按键可视化工具:实时显示键盘按键[Windows]
介绍Keyviz 是一款开源、免费的按键可视化工具,它可以实时显示用户当前按下的按键,可自定义显示按键风格、样式,非常适合录屏、演示等场合使用。其可定制性让你能制作出任意风格的按键风格:使用很简单,在设置界面的 Style 里选中一个喜欢的、独特的键盘样式以及位置,对于很多截图、录屏、直播用户,甚至可以作为个人标识使用:还可以设置显示全部按键,或者仅显示快捷键,比如 Ctrl + C,但不显示 Shift + 1、A、B、C 等按键。最重要的是宣传图做的好看,那么用户将来截图录屏也会很漂亮。链接官网:https://kutt.appinn.net/UPg2QUGithub:https://github.com/mulaRahul/keyviz
2022年08月06日
322 阅读
0 评论
0 点赞
2022-07-02
使用 Syncthing 同步文件
下载syncthingwget https://github.com/syncthing/syncthing/releases/download/v1.20.2/syncthing-linux-amd64-v1.20.2.tar.gz tar -zxvf syncthing-linux-amd64-v1.20.2.tar.gz本应用解压的路径为 /opt/syncthing-linux-amd64-v1.20.2。下面配置syncthing 自启动。配置自启动根据官方提供的说明(https://docs.syncthing.net/users/autostart.html)有两种方式.第1种为 Using Supervisord,这种还要安装 supervisor,并启动服务,稍显麻烦,这里我们直接采用系统服务的方式。1、拷贝自启动文件到服务目录cp /opt/syncthing-linux-amd64-v1.20.2/etc/linux-systemd/system/syncthing@.service /etc/systemd/system/syncthing@root.service2、修改自启动文件主要修改内容为ExecStart 的内容,修改后的内容示例如下:/opt/syncthing-linux-amd64-v1.20.2/syncthing serve --gui-address=172.17.0.11:8384 --no-restart --logflags=0 3、启用并启动服务systemctl enable syncthing@root.service systemctl start syncthing@root.service
2022年07月02日
88 阅读
0 评论
0 点赞
2022-06-15
前端导出Excel项目实践
背景介绍近期项目有个「导出作业详情」的需求,之前接触大部分导出需求均为后端获取数据生成Excel文件或将Excel文件的下载地址或文件流提供给前端进行下载。在本需求中对下载的文件有如下需求点:命名:用户名作业详单-起止日期格式:xlsx,下载内容CPU与GPU分别显示在两个工作表里技术调研在以前的项目中有=接触过前端解析 Excel 导入数据的功能,使用的是 [xlsx](https://www.npmjs.com/package/xlsx),功能超级强大,便先了解一下这个库是否能满足咱们的需求,经过稍加深入地调研,了解到这个库仅侧重在 Excel 解析,我们的需求是要能根据后端提供的数据动态生成 Excel 文件,可能这个库就不适应了。经过经一步调研,发现一款采用MIT开源授权, Star 数近 10k 的开源库:exceljs,看描述为:读取,操作并写入电子表格数据和样式到 XLSX 和 JSON 文件。这不就正是我所需要的嘛,接下来的问题就是验证工作了,根据我们的需求,主要需要验证的有如下事项:1、是否可支持生成多工作表的Excel文件2、是否支持自定义单元格样式:如背景色、边框、对齐方式、格式化3、测试Excel文件生成效率经过对相关API的深入了解及验证,发现完全能满足我们的需求。其中生成效率,一次性生成40000多条数据,不到15秒即可完成Excel生成工作,也能满足我们的实际要求:技术点梳理详细见代码及相关注释。仅用于说明相关功能点,非完整代码。// 引入 exceljs 依赖 import ExcelJS from 'exceljs' // 创建工作簿 const workbook = new ExcelJS.Workbook() // 在工作簿中添加两个工作表 const cpuSheet = workbook.addWorksheet('CPU作业详单') const gpuSheet = workbook.addWorksheet('GPU作业详单') // 添加表头 const cpuHeaderRow = cpuSheet.addRow(['作业ID', '作业名称', '超算中心', '超算账号', '队列', '运行时长', '核数', '消费核时', '消费金额', '提交时间', '开始时间', '结束时间', '提交账号', '付费账号']) // 设置表头样式 // 行高 cpuHeaderRow.height = 26.5 // 字体 cpuHeaderRow.font = { bold: true } // 设置表头单元格样式 cpuHeaderRow.eachCell((cell, rowNumber) => { // 设置边框 cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } } // 填充背景 cell.fill = { type: 'pattern', pattern: 'darkTrellis', fgColor: { argb: 'F1F1F1FF' }, bgColor: { argb: 'F1F1F1FF' } } // 对齐方式 cell.alignment = { vertical: 'middle', horizontal: cpuLeftAlignCols.includes(rowNumber) ? 'left' : 'center', wrapText: true } }) // 根据内容适当调整部分列宽度 cpuSheet.getColumn(2).width = 30 // 作业名称 cpuSheet.getColumn(10).width = 20 // 提交时间 cpuSheet.getColumn(11).width = 20 // 开始时间 cpuSheet.getColumn(12).width = 20 // 结束时间 cpuSheet.getColumn(13).width = 15 // 提交账号 cpuSheet.getColumn(14).width = 15 // 付费账号 // 生成相关数据行 for (let i = 0; i < cpuJobList.length; i++) { const job = cpuJobList[i] const rowData = [job.jobId, job.jobName, job.cluster, job.user, job.partition, ...] const dataRow = cpuSheet.addRow(rowData) // 设置数据行样式 dataRow.height = 26.5 dataRow.eachCell((cell, rowNumber) => { // 设置单元格边框 cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } } // 设置对齐方式 cell.alignment = { vertical: 'middle', horizontal: cpuLeftAlignCols.includes(rowNumber) ? 'left' : 'center', wrapText: true } // 金额格式化 if (rowNumber === 10) { cell.numFmt = '"¥"#,##.##' } }) } // 保存Excel文件 const buf = await workbook.xlsx.writeBuffer() const fileName = `${userForFileName}作业详单_${dayjs(startDate).format('YYYYMMDD')}-${dayjs(endDate).format('YYYYMMDD')}.xlsx` // 基于 file-saver 实现保存文件到本地 saveAs(new Blob([buf]), fileName)最终整合到应用中的效果扩展打印相关// 设置页面方向 workSheet.pageSetup.orientation = 'portrait' // portrait || landscape // 设置页边距 workSheet.pageSetup.margins = { left: 0.3, right: 0.3, top: 0.2, bottom: 0.2, header: 0.2, footer: 0.2 } workSheet.pageSetup.horizontalCentered = true workSheet.pageSetup.verticalCentered = false // 每页均显示的行 workSheet.pageSetup.printTitlesRow = '1:3' // 指定打印哪些列 workSheet.pageSetup.printTitlesColumn = 'A:G'解析 Excelconst ExcelJS = require('exceljs') const excelfile = './test.xlsx' var workbook = new ExcelJS.Workbook() workbook.xlsx.readFile(excelfile).then(function() { // 获取第一个worksheet var worksheet = workbook.getWorksheet(1) // 编辑worksheet worksheet.eachRow(function(row, rowNumber) { var rowSize = row.cellCount var numValues = row.actualCellCount console.log('单元格数量/实际数量:' + rowSize+'/' + numValues) row.eachCell(function(cell, colNumber) { // cell.type单元格类型:6-公式 ;2-数值;3-字符串 let cellValue = cell.value if(cell.type === 6) { cellValue = cell.result } console.log('cell',rowNumber, colNumber, cellValue, cell.numFmt) }) }) })数值格式化问题前期为满足跟前端列表展现业务逻辑保持一致,金额及数值格式化均使用 Intl.NumberFormat进行格式化,但是该类格式化后的数据是字符串,不便于导出后对数据进行排序及数值比较等操作。由于官方文档的单元格格式化说明信息较简单,Excel里对数值格式化的方式较丰富,如下图:为完整的复原格式化后的内容,可以先在Excel文件中对相关单元格设置好格式后,使用上面的 解析 Excel 章节的代码解析获取单元格的 numFmt数据。下面列举本工程中用到的数值格式化 numFmt 值:数值格式化:#,##0.00_ 货币格式化:"¥"#,##0.00;"¥"-#,##0.00
2022年06月15日
113 阅读
0 评论
0 点赞
2022-06-15
前端库推荐:JSZip
jszip是一个用于创建、读取和编辑.zip文件的JavaScript库,且API的使用也很简单链接官网:https://stuk.github.io/jszip/Github:https://hub.fastgit.xyz/Stuk/jszip示例var zip = new JSZip(); zip.file("Hello.txt", "Hello World\n"); var img = zip.folder("images"); img.file("smile.gif", imgData, {base64: true}); zip.generateAsync({type:"blob"}) .then(function(content) { // see FileSaver.js saveAs(content, "example.zip"); });兼容性OperaFirefoxSafariChromeInternet ExplorerNode.jsYesYesYesYesYesYesTested with the latest versionTested with 3.0 / 3.6 / latest versionTested with the latest versionTested with the latest versionTested with IE 6 / 7 / 8 / 9 / 10Tested with node.js 0.10 / latest version
2022年06月15日
12 阅读
0 评论
0 点赞
2022-06-06
vue-cli工程打包生成gzip相关压缩文件
一、添加依赖yarn add vue-cli-plugin-compression -D二、更新vue.config.js文件添加zlib引用const zlib = require('zlib')module.exports 中添加如下配置信息:pluginOptions: { compression:{ brotli: { filename: '[file].br[query]', algorithm: 'brotliCompress', include: /\.(js|css|html|svg|json)(\?.*)?$/i, compressionOptions: { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11, }, }, minRatio: 0.8, }, gzip: { filename: '[file].gz[query]', algorithm: 'gzip', include: /\.(js|css|html|svg|json)(\?.*)?$/i, minRatio: 0.8, } } }效果打包后生成相应的.br和.gz结尾的文件
2022年06月06日
14 阅读
0 评论
0 点赞
2022-05-28
微软拼音输入法快速当前时间
微软拼音输入法默认可以使用 rq 快速输入 2022年5月28日 和 使用 sj 快速输入 16点20分,可是在写代码过程中我们常使用 yyyy-MM-dd HH:mm:ss 格式的当前日期数据。经过简单搜索后,可以通过添加自定义词库的方式实现,具体操作步骤见下图:最核心的是短语中输入如下数据:%yyyy%-%MM%-%dd% %HH%:%mm%:%ss%
2022年05月28日
33 阅读
0 评论
0 点赞
2022-05-19
快速下载github文件
将github.com修改为 hub.fastgit.xyz下载文件时可以在下载链接前加如下前缀:https://github.91chi.fun//如:https://github.91chi.fun//https://github.com/git-for-windows/git/releases/download/v2.36.1.windows.1/Git-2.36.1-64-bit.exe另一种方案,使用FastGithub:https://github.com/dotnetcore/FastGithub
2022年05月19日
12 阅读
0 评论
0 点赞
2022-05-13
H5与小程序直播入门-学习笔记
链接:https://www.bilibili.com/video/BV1b7411T7t1直播原理HLS协议动态列表:基本上未使用静态列表全量列表:一般用于直播RTMP协议RTMP是Real Time Messaging Protocol (实时消息传输协议)的首字母缩写。该协议基于 TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash、AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。flvHTTP-FLV协议1、可以在一定程度上避免防火墙的干扰(例如,有的机房只允许80端口通过).2、可以很好的兼容HTTP 302跳转,做到灵活调度.3、可以使用HTTPS做加密通道.4、很好的支持移动端(Android, IOS).video详解controls:是否显示控制条controlslist:控制条显示内容autoplay:自动播放poster:封面图loop:循环播放preload:预加载,在不同设备上行为有些不一致volume:音量,通过 js 控制muted:是否静音<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>video 详解</title> </head> <body> <video src="./media/blsc.mp4" id="video" autoplay muted loop preload="auto" controls controlslist="nodownload nofullscreen" width="800" height="600" poster="./images/poster.jpg"></video> <script> var videoEl = document.getElementById('video') // 调整音量 videoEl.volumn = 0.5 // 设置进度,单位为秒 videoEl.currentTime = 60 // 切换视频地址 videoEl.src = './media/paratera.mp4' </script> <video id="sourceDemo" controls poster="./images/poster.jpg"> <source src="./media/blsc.mp4" type="video/mp4"> <source src="./media/paratera.mp4" type="video/mp4"><!-- 第一个错误时使用第二个地址进行播放 --> </video> <script> var videoEl = document.getElementById('sourceDemo') console.log('videoEl.currentSrc', videoEl.currentSrc) </script> </body> </html>video 事件<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>video 事件</title> </head> <body> <video src="./media/blsc.mp4" id="video" autoplay muted preload="auto" controls controlslist="nodownload nofullscreen" width="800" height="600" poster="./images/poster.jpg"></video> <script> var videoEl = document.getElementById('video') // 开始加载视频 video.addEventListener('loadstart', e => { console.log('loadStart', e) }) // 时长变化,不一定发生一次 video.addEventListener('durationchange', e => { console.log('durationchange', e, video.duration) }) // 视频的元数据下载完成 video.addEventListener('loadedmetadata', e => { console.log('loadedmetadata', e) }) // 数据加载完成 video.addEventListener('loadeddata', e => { console.log('loadeddata', e) }) // 播放进度 video.addEventListener('progress', e => { console.log('progress', e) }) // 有帧可以播放 video.addEventListener('canplay', e => { console.log('canplay', e) }) // 可以流畅播放 video.addEventListener('canplaythrough', e => { console.log('canplaythrough', e) }) // 监听播放 video.addEventListener('play', e => { console.log('play', e) }) // 监听暂停 video.addEventListener('pause', e => { console.log('pause', e) }) // seeking,点进度条加载,开始查找 video.addEventListener('seeking', e => { console.log('seeking', e) }) // seeked,下载数据完成后,标记seek结束 video.addEventListener('seeked', e => { console.log('seeked', e) }) // waiting,解码过程中 video.addEventListener('waiting', e => { console.log('waiting', e) }) // playing,暂停到播放状态之间会触发playing video.addEventListener('playing', e => { console.log('playing', e) }) // timeupdate,一般用于更新播放进度条 video.addEventListener('timeupdate', e => { console.log('timeupdate', e) }) // ended:播放结束 video.addEventListener('ended', e => { console.log('ended', e) }) // error:异常捕获,浏览器自带重试机制 video.addEventListener('error', e => { console.log('error', e) }) </script> </body> </html>
2022年05月13日
39 阅读
0 评论
0 点赞
2022-05-13
Android互动直播APP开发入门-学习笔记
链接:https://www.imooc.com/learn/923慕课网上的免费视频,绝对的良心之作,视频录制于2017年左右,虽说是五六年前的课程了,但是在直播大行其道的今天,相关的技术及观点依然不过时直播现状直播流程直播流程之采集直播流程之前处理直播流程之编码直播流程之推流与优化直播流程之服务端直播流程之客户端直播流程之交互系统交互方式:聊天、礼物、连麦、点赞直播流程-工具直播SDK的对比
2022年05月13日
54 阅读
0 评论
0 点赞
2022-05-07
易读错英语单词记录
整理我个人常读错的英语单词static发音:['stætɪk]链接:https://www.iciba.com/word?w=static释义:静态的;静止的;稳定的;静力的;静电的静电;静电干扰deprecated英[ˈdeprəkeɪtɪd]美[ 'dɛprə,ketɪd]链接:http://www.iciba.com/word?w=deprecated释义:不赞成,反对( deprecate的过去式和过去分词 )encrypt英[ɪnˈkrɪpt]美[ɛnkrɪpt]链接:http://www.iciba.com/word?w=encrypt释义:v.加密,将…译成密码vt.& vi. 把…加密(或编码),将…译成密码record英[ˈrekɔːd]美[ˈrekərd]链接:http://www.iciba.com/word?w=record释义n.记录,记载; 唱片; (体育运动或活动的)纪录; 履历; 案底v.记录;记载; 录制; 标明,显示adj.创纪录的parameter英[pəˈræmɪtə(r)]美[pəˈræmɪtər]链接:http://www.iciba.com/word?w=parameter释义n.[数]参数; <物><数>参量; 限制因素; 决定因素execute英[ˈeksɪkjuːt]美[ˈeksɪkjuːt]链接:http://www.iciba.com/word?w=execute释义v.处死; 实施; 完成; 制作decimal英[ˈdesɪml]美[ˈdesɪml]链接:http://www.iciba.com/word?w=decimal释义adj.十进位的; 小数的n.小数unique英[juˈniːk]美[juˈniːk]链接:http://www.iciba.com/word?w=unique释义adj.唯一的; 独特的; 特有的input英[ˈɪnpʊt]美[ˈɪnpʊt]链接:http://www.iciba.com/word?w=input释义n.输入,投入; 输入电路; <电>输入端; 输入的数据vt.把…输入电脑; 自 输入; 输入,给料
2022年05月07日
23 阅读
0 评论
0 点赞
2022-05-05
UI设计-AI基础-学习笔记
以下仅为学习过程中的简单记录图标的定义分类与应用场景线性![线性图表标(/usr/uploads/2022/05/2214274233.png)面性线性填充扁平图标通过色块构成图标,没有渐变、投影等效果手绘图标写实图标图标绘制的标准化流程设计流程确定主题风格 --> 关联性 --> 切入点(受众、行业) --> 草稿(大致形状构想)设计过程中的注意点结构造型:一致性栅格线细节处理色彩搭配后续输出包装总结图标结构设计之布尔运算可尝试使用布尔运算方式绘制手工如下图标:阵列复制技巧先使用 Ctrl+T 自由切换,按住Alt键可结合鼠标移动中心点,对图形进行调整(如位置、大小、角度等)后,可使用 Ctrl+Alt+Shift+T 快速复制可尝试使用阵列复制的方式绘制如下图标:圆角半径JS脚本插件Corner Editor圆角插件,下载地址:Corner Editor.zip下载后,解压将jsx文件拷贝到PS安装目录下的 Presets 目录,重启应用即可通过 文件 -> 脚本 -> Corner Editor 打开插件
2022年05月05日
12 阅读
0 评论
0 点赞
2022-04-21
Java 中加密算法的编程使用-学习笔记
Base64 编码的编程使用密码学综述密码学基本功能1)机密性2)鉴别3)报文完整性4)不可否认性基本模型密码学算法的分类消息编码: Base64消息摘要: MD类、SHA类、MAC对称密码: DES、3DES、 AES非对称密码: RSA、DH密钥交换数字签名: RSASignature、DSASignature密码学五元组明文密文加密算法(公开)解密算法(公开)密钥密钥密钥和密码的巨大区别密钥!=密码Key != Password密钥+规则==密码在密码破解者看来,拿到密钥就等于有了密码!所以,重点在密钥对称密码(传统密码)与非对称密码(公钥密码)对称密码:加解密使用相同密钥的密码体制非对称密码:加解密使用不同的密钥一公钥与私钥Java编程中常用类消息编码 BASE64Encoder、BASE64Decoder消息摘要 MessageDigest对称密码 KeyGenerator、SecretKey、 Cipher非对称密码 KeyPairGenerator、KeyFactory、 KeyPair、PublicKey、 PrivateKey、 Cipher数字签名 SignatureBase64 算法的编程使用Base64编码示例 密文: UGFyYXRlcmE=明文: ParateraBase64算法定义 Base64是一种基于64个字符的编码算法,以任意8位字节序列组合的描述形式,这种形式不易直接识别。经Base64编码后的字符串的字符数是以4为单位的整数倍。Base64 密钥4.Base64 编程使用引入sun.misc.BASE64Decoder.jar1、加密:byte[] data = "Paratera".getBytes(); String result= new BASE64Encoder().encode(data) ;2、解密:byte[] result = new BASE64Decoder().decodeBuffer(data) ;Base64 算法的实际应用使用Telnet 发送邮件:pubLic class Base64Util { public static String enc ryptBase64(byte[] data){ return new BASE64Encoder().encode(data); } public static String dec ryptBase64(String data) throws IOException{ byte[] resultBytes = new BASE64Decoder().decodeBuffer(data); return new String( resultBytes); } } pubLic class SMTPMain { public static void main(String[] args) { //用户名密码 String sender = "cnsmtp01@163.com"; String receiver = "cnsmtp02@163. com"; String password = "computer"; //将用户名和密码进行Base64编码 String userBase64 = Base64Util.encryptBase64(sender.substring(0, sender.index0f("@")).getBytes()); String passBase64 = Base64Util.encryptBase64(password.getBytes()); try { Socket socket = new Socket("smtp.163.com", 25); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); PrintWriter writter = new PrintWriter(outputStream, true); System.out.println(reader.readLine()); // HELO writter.println("HELO Paratera"); System.out.println(reader.readLine()); // AUTH LOGIN >> >Base64 writter.println( "AUTH LOGIN"); System.out.println(reader.readLine()); writter.println(userBase64); System.out.println(reader.readLine()); writter.println(passBase64); System.out.println(reader.readLine()); // Set "MAIL FROM" and "RCPT TO" writter.println("MAIL FROM:<" + sender + ">"); System.out.println(reader.readLine()); writter.println( "RCPT TO:<" + receiver + ">"); System.out.println(reader.readLine()); // Set "DATA" writter.println("DATA"); System.out.println(reader.readLine()); writter.println("SUBJECT:你好并行集团"); writter.println("FROM:" + sender); writter.println("T0:" + receiver); writter.println("Content-Type: text/plain;charset=\"gb2312\""); writter.println(); writter.println("北京并行科技股份有限公司(简称并行科技,PARATERA,股票代码:839493,挂牌新三板)成立于2007年,是国内领先的超算云服务和运营服务提供商,提供超算公有云服务、超算行业云服务、AI云服务、设计仿真云和计算资源建设及运营服务。"); writter.println("."); writter.println(""); System.out.println(reader.readLine()); //发送完毕了, 和服务器拜拜 writter.println("RSET"); System.out.println(reader.readLine()); writter.println("QUIT"); System.out.println(reader.readLine()); } catch (Exception e) { e.printStackTrace(); } } } 消息摘要的编程使用消息摘要概述唯一对应一个消息或文本的固定长度的值,由一个单向Hash加密函数对消息进行作用而产生消息摘要的分类:(1) MD (Message Digest) :消息摘要算法(2) SHA (Secure Hash Algorithm) : 安全散列算法(3) MAC (Message Authentication Code) :消息认证码算法消息摘要的日常应用验证数据完整性(防止在传输途中被篡改)MD算法的编程使用为计算机安全领域广泛使用的一种散列函数,用以提供消息的 完整性 保护MD系列算法(JDK)算法数据长度摘要长度MD2任意128MD5任意128MD算法编程使用核心代码//初始化MessageDigest MessageDigest md5 = MessageDigest.getInstance("MD5"); //更新, data为原始数据 md5.update(data); /生成摘要 byte[] result= md5.digest(); 完整示例代码:// ---------1.BytesToHex.java--------- package net.wljy.crypt.util; /** * 字节转16进制 * * @author paratera * */ public class BytesToHex { public static String fromBytesToHex(byte[] resultBytes) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < resultBytes.length; i++) { if (Integer.toHexString(0xFF & resultBytes[i]).length() == 1) { builder.append("0").append(Integer.toHexString(0xFF & resultBytes[i])); } else { builder.append(Integer.toHexString(0xFF & resultBytes[i])); } } return builder.toString(); } } // ---------2.MessageDigestUtil.java--------- package net.wljy.crypt; import java.io.File; import java.io.FileInputStream; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import net.wljy.crypt.util.BytesToHex; public class MessageDigestUtil { /** * MD5字符串加密 * @param data 待加密字符串 * @return */ public static String encryptMD5(byte [] data) { MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); md5.update(data); byte[] resultBytes = md5.digest(); return BytesToHex.fromBytesToHex(resultBytes); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 获取文件MD5值 * @param filePath 文件路径 * @return */ public static String getMD5ofFile(String filePath) { FileInputStream fis; try { fis = new FileInputStream(new File(filePath)); DigestInputStream dis = new DigestInputStream(fis, MessageDigest.getInstance("MD5")); byte[] buffer = new byte[1024]; int read = dis.read(buffer, 0, 1024); while(read != -1) { read = dis.read(buffer, 0, 1024); } MessageDigest md = dis.getMessageDigest(); return BytesToHex.fromBytesToHex(md.digest()); } catch (Exception e) { e.printStackTrace(); } return null; } } // ---------3.MD5Test.java--------- package net.wljy.crypt; public class MD5Test { public static final String DATA = "并行集团"; public static void main(String[] args) { String md5Str = MessageDigestUtil.encryptMD5(DATA.getBytes()); System.out.println(md5Str); String fileMd5Str = MessageDigestUtil.getMD5ofFile("D:/API接口文档.html"); System.out.println("文件MD5:" + fileMd5Str); } } SHA算法的编程使用安全哈希算法,主要适用于数字签名标准里面定义的数字签名算法SHA算法种类算法数据长度摘要长度SHA-1任意160SHA-256任意256SHA-384任意384SHA-512任意512核心代码//初始化MessageDigest MessageDigest sha = MessageDigest.getInstance("SHA-x"); // SHA-x需替换为具体的算法名称 //更新, data为原始数据 sha.update(data); /生成摘要 byte[] result= sha.digest(); 示例代码: /** * 计算某一数据的hash摘要值 * @param data * @return */ public static String encryptSHA(byte [] data) { try { MessageDigest sha = MessageDigest.getInstance("SHA-256"); sha.update(data); return BytesToHex.fromBytesToHex(sha.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; }HMAC算法的编程使用引言:单一MD或SHA算法缺点? 摘要值容易被篡改!结合了MD5和SHA算法的优势,同时用 密钥 对摘要加密,是一种更为安全的消息摘要算法。HMAC算法的种类算法数据长度摘要长度HmacMD5任意128HmacSHA1任意160HmacSHA256任意256HmacSHA384任意384HmacSHA512任意512如何生成密钥KeyGenerator!核心代码1.构建密钥//初始化KeyGenerator KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5"); //产生密钥 SecretKey secretKey = keyGen.generateKey(); //得到密钥字节数组 byte[] key= secretKey.getEncoded(); 2.执行消息摘要//从字节数组还原密钥 SecretKey secretKey = new SecretKeySpec(key, "HmacMD5"); //实例化Mac Mac mac = Mac.getInstance("HmacMD5"); //用密钥初始化Mac mac.init(secretKey); //执行消息摘要 byte[] result = mac.doFinal(data); 散列函数特征:(1)输入任意长度数据,输出固定长度散列值,计算很容易,过程不可逆(2)对于某数据,其散列值固定(3)两个数据不同,则对应的散列值也不同(4)两个散列值不同,则对应的原始输入数据也不同2.散列函数破解(1)真破解:2005年2月,王小云教授破解了SHA-1 算法,已知数据A和其散列值,找到了另一个数据B与A的散列值相同(2)假破解:根据数据库查询散列值,找到其对应的数据明文,根本在于数据库的查询!对称密码的编程使用对称密码概述1、加密密钥和解密密钥相同,对于大多数对称密码算法,加解密过程互逆2、加解密通信模型3、特点:算法公开、计算量小、加密速度快、加密效率高4、弱点:双方都使用同样密钥,安全性得不到保证5、分组密码工作模式(1) ECB: 电子密码本(2) CBC: 密文链接(3) CFB: 密文反馈(4) OFB:输出反馈(5) CTR: 计数器6、分组密码填充方式(1) NoPadding(2) PKCS5Padding(3) ISO10126Padding7、常用对称密码:(1) DES (Data Encryption Standard)(2) 3DES (Triple DES、DESede)3重DES(3) AES (Advanced Encryption Standard)DES算法的编程使用DES:数据加密标准,是对称加密算法领域中的典型算法特点:密钥偏短(56位)、 生 命周期短JDK实现| 算法 | 密钥长度 | 默认密钥长度 | 工作模式 | 填充方式 |DES5656ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-CFB128、OFB、OFB8-OFB128NoPadding、PKCS5Padding、ISO10126Padding4、示例代码// ---------1.DESUtil.java--------- package net.wljy.crypt.des; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class DESUtil { /** * 生成密钥 * * @return */ public static byte[] initKey() { try { KeyGenerator keyGen = KeyGenerator.getInstance("DES"); keyGen.init(56); SecretKey secretKey = keyGen.generateKey(); return secretKey.getEncoded(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /** * DES加密 * * @param data 要加密的数据 * @param key 密钥 * @return */ public static byte[] encrypt(byte[] data, byte[] key) { SecretKey secretKey = new SecretKeySpec(key, "DES"); try { // DES/ECB/NoPadding --> 加密方式/工作模式/填充方式,没有严格要求的场景只写加密方式即可 Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { e.printStackTrace(); } return null; } /** * DES加密 * * @param data 要解密的数据 * @param key 密钥 * @return */ public static byte[] decrypt(byte[] encryptedData, byte[] key) { SecretKey secretKey = new SecretKeySpec(key, "DES"); try { // DES/ECB/NoPadding --> 加密方式/工作模式/填充方式,没有严格要求的场景只写加密方式即可 Cipher cipher = Cipher.getInstance("DES"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(encryptedData); } catch (Exception e) { e.printStackTrace(); } return null; } } // ---------2.DESTest.java--------- package net.wljy.crypt.des; import net.wljy.crypt.util.BytesToHex; public class DESTest { public static final String DATA = "并行集团"; public static void main(String[] args) { // 获取密钥 byte[] initKey = DESUtil.initKey(); System.out.println("secretKey:" + BytesToHex.fromBytesToHex(initKey)); // 加密数据 byte[] desResultData = DESUtil.encrypt(DATA.getBytes(), initKey); System.out.println("desResultData:" + BytesToHex.fromBytesToHex(desResultData)); // 解密数据 byte[] decryptResultData = DESUtil.decrypt(desResultData, initKey); System.out.println("decryptResultData:" + new String(decryptResultData)); } } 3DES算法的编程使用1、3DES:将密钥长度增至112位或168位,通过增加迭代次数提高安全性2、缺点:处理速度较慢、密钥计算时间较长、加密效率不高3、JDK实现算法密钥长度默认密钥长度工作模式填充方式3DES112、168168ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-CFB128、OFB、OFB8-OFB128NoPadding、PKCS5Padding、ISO10126Padding核心代码1、生成密钥KeyGenerator keyGen = KeyGenerator.getInstance("DESede"); keyGen.init(168); //可指定密钥长度为112或168, 默认为168 SecretKey secretKey = keyGen.generateKey(); 2.加/解密SecretKey secretKey = new SecretKeySpec(key, "DESede"); Cipher cipher = Cipher.getInstance("DESede"); cipher.init(Cipher.ENCRYPT_MODE,secretKey); byte[] cipherByte = cipher.doFinal(data); 4、完整示例代码// ---------1.TripleDESUtil.java--------- package net.wljy.crypt.tripledes; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * 3DES * * @author paratera * */ public class TripleDESUtil { /** * 生成密钥 * * @return */ public static byte[] initKey() { KeyGenerator keyGen; try { keyGen = KeyGenerator.getInstance("DESede"); keyGen.init(168); // 112 168 SecretKey secretKey = keyGen.generateKey(); return secretKey.getEncoded(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /** * 3DES加密 * * @param data 待加密数据 * @param key 密钥 * @return */ public static byte[] encrypt(byte[] data, byte[] key) { try { SecretKey secretKey = new SecretKeySpec(key, "DESede"); Cipher cipher = Cipher.getInstance("DESede"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(data); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 3DES解密 * * @param encrypedData 待解加密数据 * @param key 密钥 * @return */ public static byte[] decrypt(byte[] encrypedData, byte[] key) { try { SecretKey secretKey = new SecretKeySpec(key, "DESede"); Cipher cipher = Cipher.getInstance("DESede"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(encrypedData); } catch (Exception e) { e.printStackTrace(); } return null; } } // ---------2.TripleDESTest.java--------- package net.wljy.crypt.tripledes; import net.wljy.crypt.util.BytesToHex; public class TripleDESTest { public static final String DATA = "并行集团"; public static void main(String[] args) { // 获取密钥 byte[] initKey = TripleDESUtil.initKey(); System.out.println("secretKey:" + BytesToHex.fromBytesToHex(initKey)); // 加密数据 byte[] resultData = TripleDESUtil.encrypt(DATA.getBytes(), initKey); System.out.println("3DESResultData:" + BytesToHex.fromBytesToHex(resultData)); // 解密数据 byte[] decryptResultData = TripleDESUtil.decrypt(resultData, initKey); System.out.println("3DESdecryptResultData:" + new String(decryptResultData)); } } AES算法的编程使用1、AES:高级数据加密标准,能够有效抵御已知的针对DES算法的所有攻击2、特点:密钥建立时间短、灵敏性好、内存需求低、安全性高3、JDK实现算法密钥长度默认密钥长度工作模式填充方式AES128、192、256128ECB、CBC、PCBC、CTR、CTS、CFB、CFB8-CFB128、OFB、OFB8-OFB128NoPadding、PKCS5Padding、ISO10126Padding核心代码1、生成密钥KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128); //默认128,获得无政策权限后可为192或256 SecretKey secretKey = keyGen.generateKey();2、加/解密SecretKey secretKey = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE,secretKey); // ENCRYPT_MODE:加密 || DECRYPT_MODE:解密 byte[] cipherByte = cipher.doFinal(data);政策权限文件keyGen.init(),如果给值为19或256会报错,JDK8及之前的文件可到如下地址下载政策文件:https://www.oracle.com/java/technologies/javase-jce-all-downloads.html。下载后覆盖jdk/jre/lib/security下的两个jar包即可。非对称密码的编程使用非对称密码概述1、对称密码中的密钥配送存在不安全问题2、非对称密码通信模型3、非对称密码的特征(1).需要两个密钥来进行加密和解密,分别为公钥和私钥(2).公钥和私钥相互配对,称为KeyPair4、非对称密码的优缺点(1).优点:相比于对称密码,安全性更高(2).缺点:加解密花费时间长、速度慢5、常用非对称密码:(1).DH密钥交换算法(2).RSA算法(3).EIGamal算法6、非对称密码的作用(1).密钥交换(DH)(2).加密/解密(RSA)(3).数字签名(RSA)DH 算法的编程使用RSA 算法的编程使用数字签名的编程使用0数字签名概述RSASignature 算法的编程使用DSASignature 算法的编程使用
2022年04月21日
17 阅读
0 评论
0 点赞
2022-04-20
Web安全-学习笔记
Web安全,也可以叫做Web应用安全。互联网本来是安全的,自从有了研究安全的人之后,互联网就变的不安全了。HTTP协议与会话管理当我们访问一个网址的时候,这中间发生了什么?输入网址浏览器查找域名的IP地址浏览器给Web服务器发送一个HTTP请求服务端处理请求服务端发回一个HTTP响应Web应用的组成与网页的渲染浏览器特性与安全策略Cookie的安全策略内容安全策略XSSXSS,全称跨站脚本(Cross Site Scripting), 一种注入式攻击方式。XSS成因对于用户输入没有严格控制而直接输出到页面对非预期输入的信任XSS的危害盗取各类用户账号,如机器登录账号、用户网银账号、各类管理员账号窃取数据非法转账挂马……XSS的分类存储型(持久型)反射型(非持久型)http://www.xx.com/search.html?key_ pro="><script>confirm(1501)</script>DOM型其实DOM型也属于反射型的一种,不过比较特殊,所以一般也当做一种单独类其他XSS类别mXSS(突变型XSS)UXSS(通用型XSS)Flash XSSUTF-7 XSSMHTML XSS - 仅IE低版本CSS XSS - 仅IE低版本VBScript XSS - 仅IECSRFCSRF,全称跨站伪造请求(Cross-site request forgery),也称为one click attack/session riding,还可以缩写为XSRF。CSRF与XSS的区别XSS:利用对用户输入的不严谨然后执行JS语句CSRF:通过伪造受信任用户发送请求CSRF可以通过XSS来实现CSRF的防御方法通过验证码进行防御检查请求来源增加请求参数token
2022年04月20日
27 阅读
0 评论
0 点赞
2022-04-13
Javascript设计模式-学习笔记
设计模式概念解读设计模式(Design pattern)是一套被反复使用、思想成熟、经过分类和无数 实战设计经验 的总结的。使用设计模式是为了让系统代码可重用、可扩展、可解耦、更容易被人理解且能保证代码可靠性。设计模式使代码开发真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。只有夯实地基搭好结构,才能盖好坚实的大楼。也是我们迈向高级开发人员必经的一步。单例模式文字解读单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。构造函数模式文字解读构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。在JavaScript里,构造函数通常是认为用来实现实例的,JavaScript没有类的概念,但是有特殊的构造函数。通过new关键字来调用自定义的构造函数,在构造函数内部,this关键字引用的是新创建的对象。拟物化解读代码示例一://1.用于创建特定类型的对象 //2.这样的函数名会被人笑话的 //3.js开发的时候写单引号 //4.js里构造函数比较特殊的地方new //5.其他的语言里比如PHP里人家实现有一个关键字A class //6.zaomen就是构造函数他又充当了类的概念 function zaomen (suo, huawen) { if (!(this instanceof zaomen)) { return new zaomen(...arguments); } this.suo = suo || '普通' this.huawen = huawen || '普通' this.create = function(){ return ' [锁头] '+this.suo +' [ 花纹] '+ this.huawen } } var xiaozhang = new zaomen('指纹锁', '高贵') alert('xiaozhang' + xiaozhang.create()) var xiaoli = zaomen('密码锁', '雕花') alert('xiaoli' + xiaoli.create()) 示例二:结合单例模式var Panpan = { zaomen: function (suo, huawen) { this.suo = suo || '普通' this.huawen = huawen || '普通' this.create = function(){ return ' 盼盼造门:[锁头] '+this.suo +' [ 花纹] '+ this.huawen } } } var Tubaobao = { zaomen: function (suo, huawen) { this.suo = suo || '普通' this.huawen = huawen || '普通' this.create = function(){ return ' 兔宝宝造门:[锁头] '+this.suo +' [ 花纹] '+ this.huawen } } } var xiaozhang = new Panpan.zaomen('指纹锁', '高贵') alert('xiaozhang' + xiaozhang.create()) var xiaoli = new Tubaobao.zaomen('密码锁', '雕花') alert('xiaoli' + xiaoli.create()) 工厂模式概念解读工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂)。这个模式十分有用,尤其是创建对象的流程赋值的时候,比如依赖于很多设置文件等。并且,你会经常在程序里看到工厂方法,用于让子类类定义需要创建的对象类型。使用场景1.对象的构建十分复杂。2.需要依赖具体的环境创建不同实例。3.处理大量具有相同属性的小对象。注意事项:1.不能滥用工厂,有时候仅仅是给代码增加复杂度。代码实战简单工厂//这是一个简单工厂模式 var XMLHttpFactory = function() { } XMLHttpFactory.createXMLHttp = function() { var XMLHttp = null //XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象。 if (window.XMLHttpReqyest){ XMLHttp = new XMLHttpRequest() } else if(window.ActiveXObject) { XMLHttp = new ActiveXOb ject("Microsof+.XMLHTTP") return XMLHttp } } var AjaxHander = function() { var XMLHttp = XMLHttpFactory.createXMLHttp(); /...具体的操作..*/ }抽象工厂//这是一个抽象工厂模式 var XMLHttpFactory = function() { } XMLHttpFactory.prototype = { //如果真的要调用这个无法会抛出一个错误,它不能被实例化,只能用来派生子类 createFactory: function(){ throw new Error('This is an abstract class') } } //派生子类,文章开始处有基础介绍那有讲解继承的模式,不明白可以去参考原理 var XHRHandler = function() { XMLHttpFactory.call(this) } XHRHandler.prototype = new XMLHttpFactory() XHRHandler.prototype.constructor = XHRHandler //重新定义createFactory方法 XHRHandler.prototype.createFactory = function() { var XMLHttp = null if (window.XMLHttpRequest){ XMLHttp = new XMLHttpRequest() }else if(window.ActiveXObject){ XMLHttp = new ActiveXObject('Microsoft.XMLHTTP') } return XMLHttp } 代理模式概念解读代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下:代理模式(Proxy) ,为其他对象提供一种代理以控制对这个对象的访问。代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一-些难以复制的东西。使用场景1.远程代理(一个对象将不同空间的对象进行局部代理)。2.虚拟代理(根据需要创建开销很大的对象如渲染网页暂时用占位代替真图)。3.安全代理(控制真实对象的访问权限)。4.智能指引(调用对象代理处理另外一些事情如垃圾回收机制)。代码实战//代理模式需要3方 //1.买家 function maijiq(argument){ this.name = '小明' } //2.中介卖房 function zhongjie(){ } zhongjie.prototype.maifang = function() { new fangdong(new maijia().maifang('20万') } //3.房东坐等收钱 function fangdong(maijia){ this.mailjia_name = maijia.name this.maifang = function(money){ alert('收到了来自I ' + this.maijia_ name+' ]' + money+'人民币') } } (new zhongjie).maifang() AlloyStic HTML5骨骼动画建造者模式概念解读建造者模式可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。建造者模式实际,就是一个指挥者,-个建造者,一个使用指挥者调用具体建造者工作得出结果的客户。建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。适用场景1.分步创建一一个复杂的对象。2.解耦封装过程和具体创建的组件。3.无需关心组件如何组装。注意事项1.一定要一个稳定的算法进行支持。2.加工工艺是暴露的。代码实战// 1.产出的东西是房子 // 2.baogongtou调用工人进行开工而且他要很清除工人们具体的某一个大项 // 3.工人是盖房子的工人可以建卧室建客厅建厨房 // 4.包工头只是一个接口而已他不干活他只对外说我能盖房子 function Fangzi() { this.woshi = '' this.keting = '' this.chufang = '' } function Baogongtou() { this.gaifangzi = function(gongren1) { gongren1.jian_woshi() gongren1.jian_keting() gongren1.jian_chufang() } } function Gongren() { this.jian_woshi = function() { console.log('卧室盖好了') } this.jian_keting = function() { console.log('客厅建好了') } this.jian_chufang = function() { console.log('厨房建好了') } this.jiaogong = function() { var _fangzi = new Fangzi() _fangzi.woshi = 'ok' _fangzi.keting = 'ok' _fangzi.chufang = 'ok' return _fangzi } } var gongren = new Gongren() var baogongtou = new Baogongtou() baogongtou.gaifangzi(gongren) // 主人要房子 var myfangzi = gongren.jiaogong() console.log(myfangzi)命令模式概念解读命令模式(Command)的定义是:用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行。也就是说该模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。它也可以用来消除调用操作的对象和实现操作的对象之间的耦合。这为各种具体的类的更换带来了极大的灵活性。模式作用1.将函数的封装、请求、调用结合为一体。2.调用具体的函数解耦命令对象与接收对象。3.提高程序模块化的灵活性。注意事项1.不需要接口一致,直接调用函数即可,以免造成浪费。代码实战var lian = {} lian.paobing = function(pao_num) { console.log(pao_num + '门炮开始战斗') } lian.bubing = function(bubing_num) { console.log(bubing_num + '个人开始战斗') } lian.lianzhang = function(mingling) { lian[mingling.type](mingling.num) } lian.lianzhang({ type: 'paobing', num: 100 }) lian.lianzhang({ type: 'bubing', num: 500 })观察者模式 / 发布订阅模式概念解读观察者模式又叫发布订阅模式(Publish/Subscribe) ,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。模式作用1.支持简单的广播通信,自动通知所有已经订阅过的对象。2.页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。3.目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。注意事项:1.监听要在触发之前。代码实战// 提前引入jQuery ~(function() { var $ = {} var o = $({}) $.jianting = function() { o.on.apply(a.arguments) } $.fabu = function() { o.trigger.apply(arguments) } $.qingchu = function() { o.off.apply(o, arguments) } })() $.jianting('/test/ls', function(e, a, b, c) { console.log(a + '||' + b + '||' + c) }) $.jianting('/test/ls', function(a, b, c) { console.log('ok') }) $.fabu('/test/ls', [1, 2, 3])适配器模式概念解读适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转换成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一起工作。模式作用1.使用一个已经存在的对象,但其方法或接口不符合你的要求。2.创建一个可复用的对象,该对象可以与其他不相关或不可见的对象协同工作。3.使用已经存在的一个或多个对象,但是不能进行继承已匹配它的接口。注意事项1.与代理模式的区别,代理模式是不改变原接口适配是原接口不符合规范。代码实战function pp() { this.test = function() { console.log('我是原test') } } pp.prototype.gogo = function() { console.log('我是原go') } function shipeiqi() { var s = new pp var aa = { test: function () { s.test() }, go: function() { s.gogo() } } return aa } var bb = shipeiqi() bb.test() bb.go() 责任链模式概念解读职责链模式是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。链中收到请求的对象要么亲自处理它,要么转发给下一个候选者。提交方并不明确有多少个对象会处理它,任一候选者都可以响应相应的请求,可以在运行时刻决定哪些候选者参与到链中。模式作用1.dom的冒泡有些类似职责链。2.nodejs 当controller中有很多负责操作逻辑的时候拆分中间件。3.解耦发送者和接受者。注意事项1.javascript中的每一次 「.」 是有代价的,要在必要的时候应用。代码实战// 职责链模式 function laoban(xiangmujingli) { if (xiangmujingli) { this.xiangmujingli = xiangmujingli } } laoban.prototype.write = function(code) { this.xiangmujingli.write(code) } function xiangmujingli(coder) { if (coder) { this.coder = coder } } xiangmujingli.prototype.write = function(code) { this.coder.write(code) } function coder() { } coder.prototype.write = function(code) { console.log('coding:' + code) } // 由begin发起,coder结束 var begin = new laoban(new xiangmujingli(new coder())) begin.write('php')迭代器模式概念解读迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该方法中的内部表示。jquery中我们经常会用到一个each函数就是迭代器模式。模式作用1.为遍历不同的集合结构提供一个统一 的接口, 从而支持同样的算法在不同的集合结构上进行操作。2.对于集合内部结果常常变化各异,我们不想暴露其内部结构的话,但又响让客户代码透明底访问其中的元素,这种情况下我们可以使用迭代器模式。注意事项1.一般的迭代,我们至少要有2个方法,hasNext()和Next(), 这样才做做到遍历所有对象。2.遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach里不允许修改item)。代码实战var arr = ['1', '2', '3', 'test', 20080708] var diedai = (function() { var length = arr.length var index = 0 return { hasNext: function() { return index < length }, next: function() { var data = arr[index] index = index + 1 return data }, reset: function() { index = 0 } } })() while(diedai.hasNext()) { console.log(diedai.next()) }外观模式概念解读外观模式(Facade) 为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。模式作用1.在设计初期,应该要有意识地将不同的两个层分离,比如经典的三层结构。2.在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观模式可以提供一个简单的接口,减少他们之间的依赖。3.在维护一个遗留的大型系统时,为系统开发一个外观Facade类,为设计粗糙和高度复杂的遗留代码提供比较清晰的接口,让新系统和Facade对象交互。注意事项外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性。代码实战var stopEvent = function(e){ // 同时阻止时间默认行为和冒泡 e.stopPropagation() e.preventDefault() } // stopEvent 本身就是生产门面 $('#xxxx').click(function(e){ stopEvent(e) }) 策略模式概念解读策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。模式作用1.所有的这些算法都是做相同的事情,只是实现不同。2.以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。3.单独定义算法类,也方便了单元测试。注意事项1.不仅可以封装算法,也可以用来封装几乎任何类型的规则,是要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑是要策略模式来处理各种变化。代码实战var $input = $('#input') var util = { isEmpty: function() { return false }, isTel: function() { return true } } var isEmpty = util.isEmpty($input.val()) var isTel = util.isTel($input.val()) if (!isEmpty && isTel ) { console.log('通过校验') } // OR $.fn.validate $.input.vallidate({ isEmpty: false, isTel: true })中介者模式概念解读中介者模式(Mediator) ,用一个中介对象来封装一系列的对象交互。 中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。模式作用1.软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信。一般,如果系统有很多子模块需要直接沟通,都要创建一个中央控制点让其各模块通过该中央控制点进行交互。中介者模式可以让这些子模块不需要直接沟通,而达到进行解耦的目的。注意事项1.当系统出现了多对多交互复杂的对象群时,先不要急于使用中介者模式,而是要思考一下是不是系统设计有问题。代码实战// 飞机 var feiji = function(name) { this.name = name } feiji.prototype.send = function(msg, to) { console.log(this.name + '发送了信息') tatai.send(msg, to) } feiji.prototype.jieshou = function(msg) { console.log(this.name + '接收到' + msg) } // 塔台 var tatai = { all: {}, zhuce: function(feiji) { this.all[feiji.name] = feiji }, send: function(msg, to) { this.all[to.name].jieshou(msg) } } var feiji1 = new feiji('feiji1') var feiji2 = new feiji('feiji2') tatai.zhuce(feiji1) tatai.zhuce(feiji2) feiji1.send('我马上降落,还有200米', feiji2)原型模式概念解读原型模式(prototype) 是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。对于原型模式,可以利用JavaScript特有的原型继承特性去创建对象的方式,真正的原型继承是作为最新版的ECMAScript5标准提出的,使用0bject.create方 法来创建这样的对象,如Object.create(prototype, optionalDescriptorObjects)模式作用1.原型对象本身就是有效地利用了每个构造器创建的对象注意事项1.注意的依然是浅拷贝和深拷贝的问题,免得出现引用问题。2.现有的文献里查看原型模式的定义,没有针对JavaScript的,你可能发现很多讲解的都是关于类的,但是现实情况是基于原型继承的JavaScript完全避免了类(class)的概念。代码实战// 深拷贝克隆对象。。。 模板方法概念解读模板方法(TemplateMethod) 定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。模板方法导致一种反向的控制结构,这种结构就是传说中的“好莱坞法则”,即“别找找我们,我们找你”,这指的是父类调用一个类的操作,而不是相反。具体体现是面向对象编程编程语言里的抽象类(以及其中的抽象方法),以及继承该抽象类(和抽象方法)的子类。模式作用1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现2.各子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复,不同之处分离为新的操作,最后,用一个钓鱼这些新操作的模板方法来替换这些不同的代码3.控制子类扩展,模板方法只在特定点调用“hook” 操作,这样就允许在这些点进行扩展注意事项1.和策略模式不同,模板方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法。代码实战/***示例一:造人的模板***/ function shangdi() {} shangdi.prototype.zaoren_yanjing = function() { console.log('眼睛') } shangdi.prototype.zaoren_bizi = function() { console.log('鼻子') } shangdi.prototype.zaoren_zuiba = function() { console.log('嘴巴') } shangdi.prototype.zaoren_yanjing = function() { throw new Error('我只是个钩子,需要你自己去探索') } // 小明 function xiaoming() { console.log('小明是上帝的子类') shangdi.call(this) } xiaoming.protype = new shangdi() xiaoming.prototype.aihao = function(){ console.log('小明爱讲笑话') } /***示例二:流程***/ function liucheng(){} liucheng.prototype.start = function() { confirm('您是否要进入游戏?') } liucheng.prototype.loading = function() { confirm('游戏加载中…………') } liucheng.prototype.out = function() { confirm('您是否要离开游戏?') } function xiaojigege(){} xiaojigege.prototype = new liucheng() xiaojigege.start() 装饰者模式概念解读装饰者提供比继承更有弹性的替代方案。装饰者用用于包装同接口的对象,不仅允许你向方法添加行为,而且还可以将方法设置成原始对象调用(例如装饰者的构造函数)。装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。模式作用1.装饰者是-种实现继承的替代方案。当脚本运行时,在子类中增加行为会影响原有类所有的实例,而装饰者却不然。取而代之的是它能给不同对象各自添加新行为。2.添加辅助的额外功能。3.把类(函数)的核心职责和装饰功能区分开了。注意事项1.装饰的类跟被装饰的类,要求拥有相同的访问接口方法(功能)。2.装饰类的要有对被装饰类的引用,用于在装饰类的相应方法,调用相应被装饰类的方法,然后对其进行修饰。3.把每个要装饰的功能放在单独的函数里。代码实战var fangzi = function() {} fangzi.prototype.kongjian = function() { console.log('我是空的房子') } var zhuangshi = function(fangzi) { this.zfangzi = fangzi } zhuangshi.prototype.kongjian = function() { this.zfangzi.kongjian() console.log('我添加了一个家具') } var _fangzi = new fangzi() var _zhuangshi = new zhuangshi(_fangzi) _zhuangshi.kongjian() 组合模式概念解读组合模式(Composite) 将对象组合成树形结构以表示“ 部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。模式作用1.你想表示对象的部分整体层次结构时。2.你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象(方法)注意事项1.该模式经常和装饰者一起使用,它们通常有一个公共的父类(也就是原型)因此装饰必须支持具有add、remove、 getChild操作的 component接口。代码实战var zhengti() {} zhengti.prototype.kafei = function() { throw new Error('不能直接使用') } zhengti.prototype.mianbao = function() { throw new Error('不能直接使用') } function guke() {} guke.prototype.kafei = function() {} guke.prototype.mianbao = function() {} guke.prototype.diancan = function() { this.kafei() this.mianbao() }
2022年04月13日
22 阅读
0 评论
0 点赞
1
...
3
4
5
...
8