-
Notifications
You must be signed in to change notification settings - Fork 2
/
content.json
1 lines (1 loc) · 619 KB
/
content.json
1
[{"title":"深度操作系统deepin 15.11 安装微信新版本","date":"2020-03-12T11:00:00.000Z","path":"2020/03/12/install-wechat-latest-version-on-deepin-15.11/","text":"深度操作系统deepin 15.11 安装微信新版本 微信客户端在深度系统15.11版本太低,不给登录的解决办法 环境信息12345678910111213141516查看命令:apt info deepin.com.wechat微信版本:Package: deepin.com.wechat:i386Version: 2.6.2.31deepin0查看命令:lsb_release -a# 如果没有lsb_release,先安装lsb-coresudo apt install lsb-core深度系统:Distributor ID: DeepinDescription: Deepin 15.11Release: 15.11Codename: stable微信新版本:2.8.0.121 安装步骤 如果已经安装并使用过,可以调过步骤1、2、 先安装深度商店的微信客户端 sudo apt install deepin.com.wechat 启动一次刚刚安装的微信客户端 现在新版本的微信客户端 https://pc.weixin.qq.com/ 使用命令安装新版微信 123# WeChatSetup.exe是安装包的名称# 如果提示WINEPREFIX需要绝对路径,修改成绝对路径env WINEPREFIX=~/.deepinwine/Deepin-WeChat/ deepin-wine WeChatSetup.exe 然后等着图形化的安装界面,一路下一步,就可以了 注意: 问题:测试发现微信不支持双显示器的情况,双显示器情况下,会看不到微信的界面 解决方法:启动微信时,修改显示模式为复制,启动之后,再修改回来 设置 > 显示> 复制 启动微信,看到主界面 设置 > 显示 > 扩展 感谢网友提供的方法:https://bbs.deepin.org/forum.php?mod=viewthread&tid=189623&page=1#pid666765 资料Wine Deepin-wine","tags":[{"name":"linux","slug":"linux","permalink":"https://betgar.github.io/tags/linux/"},{"name":"deepin","slug":"deepin","permalink":"https://betgar.github.io/tags/deepin/"}]},{"title":"wsl环境oh my zsh配置","date":"2019-08-29T11:00:00.000Z","path":"2019/08/29/config-the-oh-my-zsh-in-wsl-windows10/","text":"wsl环境oh my zsh配置 最近折腾vs code的Remote-ssh功能,想用一个漂亮的terminal,就折腾了下oh my zsh,记录下方便以后查看。 修改mirror1234cd /etc/apt/# 备份sources.listcp /etc/apt/sources.list /etc/apt/sources.list_back 切换163镜像12345678910111213141516171819202122cd /etc/apt/# 切换到163sudo vi change_mirror_to_163.sh# copy这些内容到文本,保存退出Codename=$( (lsb_release -a)|awk '{print $2}'|tail -n 1 )echo \"\\deb http://mirrors.163.com/ubuntu/ $Codename main multiverse restricted universedeb http://mirrors.163.com/ubuntu/ $Codename-backports main multiverse restricted universedeb http://mirrors.163.com/ubuntu/ $Codename-proposed main multiverse restricted universedeb http://mirrors.163.com/ubuntu/ $Codename-security main multiverse restricted universedeb http://mirrors.163.com/ubuntu/ $Codename-updates main multiverse restricted universedeb-src http://mirrors.163.com/ubuntu/ $Codename main multiverse restricted universedeb-src http://mirrors.163.com/ubuntu/ $Codename-backports main multiverse restricted universedeb-src http://mirrors.163.com/ubuntu/ $Codename-proposed main multiverse restricted universedeb-src http://mirrors.163.com/ubuntu/ $Codename-security main multiverse restricted universedeb-src http://mirrors.163.com/ubuntu/ $Codename-updates main multiverse restricted universe \">sources.list# 执行上面的脚本sudo sh change_mirror_to_163.sh 切换到aliyun12345678910111213141516171819202122cd /etc/apt/# 切换到aliyunsudo vi change_mirror_to_aliyun.sh# copy这些内容到文本,保存退出Codename=$( (lsb_release -a)|awk '{print $2}'|tail -n 1 )echo \"\\deb http://mirrors.aliyun.com/ubuntu/ $Codename main multiverse restricted universedeb http://mirrors.aliyun.com/ubuntu/ $Codename-backports main multiverse restricted universedeb http://mirrors.aliyun.com/ubuntu/ $Codename-proposed main multiverse restricted universedeb http://mirrors.aliyun.com/ubuntu/ $Codename-security main multiverse restricted universedeb http://mirrors.aliyun.com/ubuntu/ $Codename-updates main multiverse restricted universedeb-src http://mirrors.aliyun.com/ubuntu/ $Codename main multiverse restricted universedeb-src http://mirrors.aliyun.com/ubuntu/ $Codename-backports main multiverse restricted universedeb-src http://mirrors.aliyun.com/ubuntu/ $Codename-proposed main multiverse restricted universedeb-src http://mirrors.aliyun.com/ubuntu/ $Codename-security main multiverse restricted universedeb-src http://mirrors.aliyun.com/ubuntu/ $Codename-updates main multiverse restricted universe \">sources.list# 执行上面的脚本sudo sh change_mirror_to_aliyun.sh 执行更新 可以看到速度明显的改善 1sudo apt update && sudo apt upgrade oh my zsh安装 如果没用安装 zsh,请先安装zsh. 安装zsh https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH 123456789101112131415# 查看本机有哪些shellcat /etc/shells # 显示当前用的shell: echo $SHELL# 测试zshzsh --version# 如果没用安装sudo apt update && sudo apt upgrade# sudo apt update && sudo apt upgrade zsh# 安装zshsudo apt install zsh# 切换shellchsh -s $(which zsh) curl安装 https://github.com/robbyrussell/oh-my-zsh#basic-installation 1sh -c \"$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)\" 配置忽略user@hostname 参考:https://github.com/robbyrussell/oh-my-zsh/issues/2033 1234567891011# Context: user@hostname (who am I and where am I)prompt_context() { if [[ \"$USER\" != \"$DEFAULT_USER\" || -n \"$SSH_CLIENT\" ]]; then prompt_segment black default \"%(!.%{%F{yellow}%}.)$DEFAULT_USER\" fi}# DEFAULT_USER=desired_username# 加载配置文件source ~/.zshrc 配置主题agnoster https://github.com/robbyrussell/oh-my-zsh#selecting-a-theme 1234567vi ~/.zshrc# 修改主题ZSH_THEME=\"agnoster\"# 加载配置文件source ~/.zshrc 主题agnoster需要的字体 https://github.com/powerline/fonts 1sudo apt-get install fonts-powerline 重新开启一个terminal来验证,是否成功切换到zsh 参考资料WSL——Win10的Linux子系统 ubuntu 修改资源镜像","tags":[{"name":"wsl","slug":"wsl","permalink":"https://betgar.github.io/tags/wsl/"},{"name":"zsh","slug":"zsh","permalink":"https://betgar.github.io/tags/zsh/"}]},{"title":"axios.js的实战经验","date":"2019-08-27T13:00:00.000Z","path":"2019/08/27/the-questions-about-axiosjs-are-here/","text":"axios.js的实战经验 总结一下使用Axios.js的遇到的注意事项,如有不对欢迎指正。 本文不是一篇分析源码的文章,如果需要看源码解析看这里axios实例应用及源码剖析 - xhr篇 (走心教程),写的非常不错。 axios.js的流程图 先放张图镇楼,后续的问题需要多看看这张图。 来自走心教程 axios.js版本问题 之前没有注意官网说明,后来升级了版本发现报错,立刻把官网的文档翻了一下,发现Semver章节有描述, 直到v1.0之前,当发布一个minor版本,就代表有breaking changes了。所以升级有危险啊!!! 拦截器interceptor 拦截器是axios.js的核心了,就是有了拦截器做解耦,才能把代码组合的更优雅。 interceptor的返回值类型 当初在写拦截器时发现,官方给的例子,interceptor的返回值既可以是普通的value值,又可以是Promise。当时就疑惑了,有啥区别??? 123456789101112131415161718// 官方例子:// Add a request interceptoraxios.interceptors.request.use(function (config) { // Do something before request is sent return config; }, function (error) { // Do something with request error return Promise.reject(error); });// Add a response interceptoraxios.interceptors.response.use(function (response) { // Do something with response data return response; }, function (error) { // Do something with response error return Promise.reject(error); }); 具体细节看源码分析走心篇 1234567891011121314151617181920// 源码里面就有一行代码promise = promise.then(chain.shift(), chain.shift());// 其实把上面例子翻译一下就是这样的// Add a request interceptorpromise = promise.then(function (config) { // Do something before request is sent return config; }, function (error) { // Do something with request error return Promise.reject(error); });// Add a response interceptorpromise = promise.then(function (response) { // Do something with response data return response; }, function (error) { // Do something with response error return Promise.reject(error); }); 是不是瞬间就明白了拦截器的return值,其实可以是普通的value值,或者Promise对象。 返回Promise就有很多想法了,我们可以在拦截器interceptor中做异步操作,终于可以为所欲为了 :)。 interceptor的执行顺序 这个问题呢,是我在使用过程中纠结的一个问题,我想做一个功能,就是当Content-Type: application/x-www-form-urlencoded;charset=utf-8时候,自动把data使用qs.stringify(后来知道这个功能其实使用transformer更合适)。 123456789101112131415161718// axios/lib/core/Axios.js// 核心逻辑就是这段var chain = [dispatchRequest, undefined]; // 正中间的是发送请求的拦截器var promise = Promise.resolve(config);// request 拦截器unshift,添加到数组的头部this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected);});// response 拦截器 push到数组尾部this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected);});while (chain.length) { // 循环执行拦截器 promise = promise.then(chain.shift(), chain.shift());} 上述代码其实就是做了两件事情: 把拦截器放入数组chain中 this.interceptors.request拦截器是从chain的头部放入;this.interceptors.request其实也是数组,所以request数组中最后放入的interceptor却在chain数组的最前面,和axios.interceptors.request.use时候的顺序相反。 this.interceptors.response拦截器是放入chain的尾部,所以interceptor顺序和添加axios.interceptors.response.use时候的顺序相同 循环执行chain中的拦截器 request拦截器,最后添加的,最先执行 response拦截器,按照添加的顺序执行 注意:如果你的interceptors 之间顺序有先后依赖关系,需要特别注意。 transformer的使用 transformRequest和transformResponse都属于Axios.js的transformer,官方宣传的Automatic transforms for JSON data就是通过transformResponse实现的。官方文档写的太简单了,没用写要注意的问题。 内置的transformRequest和transformResponse 默认处理JSON123456789101112131415161718192021222324252627282930313233343536373839// axios/lib/defaults.jsvar defaults = { adapter: getDefaultAdapter(), transformRequest: [function transformRequest(data, headers) { normalizeHeaderName(headers, 'Content-Type'); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } if (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); } return data; }], transformResponse: [function transformResponse(data) { /*eslint no-param-reassign:0*/ if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) { /* Ignore */ } } return data; }],}; 123456789// transformRequestif (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString();}if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data);} 如果data是URLSearchParams类型,axios把Content-Type修改为application/x-www-form-urlencoded;charset=utf-8,且序列化data 如果data是Object,axios把Content-Type修改为application/json;charset=utf-8,且序列化data transformResponse默认尝试解析JSON 内置transformRequest和transformResponse被覆盖 内置的transformRequest和transformResponse,很容易被覆盖,如果你不需要内置的那就很好办,如果你需要就需要注意下。 1234567import axios from 'axios';const defaults = { // 需要把默认的添加回来 transformRequest: [].concat(axios.defaults.transformRequest, (data, headers) => { // do something return data; })}; 自动序列化data参数且修改Content-Type 注意URLSearchParams兼容性问题,如果兼容IE 11,使用qs处理下。 如果data是URLSearchParams类型,axios把Content-Type修改为application/x-www-form-urlencoded;charset=utf-8,且序列化data 如果data是Object,axios把Content-Type修改为application/json;charset=utf-8,且序列化data transformer和interceptor的区别 transformer和interceptor都能在请求过程中发挥作用,有啥区别呢? 区别: transformer和interceptor的执行的时机不同,看上图 transformer主要针对data(虽然也能直接改变header,但是不建议) transformer只能同步,而interceptor可以执行异步操作 12345678module.exports = function transformData(data, headers, fns) { /*eslint no-param-reassign:0*/ utils.forEach(fns, function transform(fn) { data = fn(data, headers); }); return data;}; baseUrl配置 axios内部判断config.url,如果是绝对路径开始就不添加baseUrl,否则就添加 12345// axios/lib/core/dispatchRequest.js// Support baseURL configif (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url);} 二进制数据注意 如果你的transformer和interceptor对data有变形或者使用,一定要先判断二进制,不然容易导致报错。推荐的做法就是,使用单独的axios实例处理二进制数据。多个axios实例之间,注意共享headers部分(这个坑,我已经踩进去了)。 总结 以上是本人使用axios.js过程中遇到的疑惑,然后通过查看源码的总结,如果对你有帮助,我会感到很荣幸。 axios.js相关资源axios-retryaxios-cache-adapter","tags":[{"name":"axios","slug":"axios","permalink":"https://betgar.github.io/tags/axios/"}]},{"title":"TypeScript类型声明书写","date":"2019-08-26T11:00:00.000Z","path":"2019/08/26/how-to-declare-typescript-types/","text":"TypeScript类型声明书写 本文总结一下TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题。如果你遇到类型声明不会写的时候,多看看lodash的声明,因为lodash对数据进行各种变形操作,所以你能遇到的,都有参考示例。 基本类型12345678910// 变量const num: number = 1;const str: string = 'str';const bool: boolean = true;const nulls: null = null;const undefine: undefined = undefined;const symbols: Symbol = Symbol('symbal');const any: any = 'any types'; // typescript的any类型,相当于什么类型约束都没有 数组1234567// 数组: 推荐使用T[]这种写法const nums: number[] = [1, 2, 3, 4];// 不推荐:Array<T>泛型写法,因为在JSX中不兼容,所以为了统一都使用T[]这种类型const strs: Array<string> = ['s', 't', 'r'];const dates: Date[] = [new Date(), new Date()]; 数组的concat方法,返回类型为never[]问题1234567// 数组concat方法的never问题// 提示: Type 'string' is not assignable to type 'never'.const arrNever: string[] = [].concat(['s']);// 主要问题是:[]数组,ts无法根据上下文判断数组内部元素的类型// @see https://github.com/Microsoft/TypeScript/issues/10479const fixArrNever: string[] = ([] as string[]).concat(['s']); 接口接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明: 而且接口可以用来声明:函数,类,对象等数据类型 123456789interface Name { first: string; second: string;}let username: Name = { first: 'John', second: 'Doe'}; any、null、undefined、void类型123456789101112// 特殊类型const any: any = 'any types'; // typescript的any类型,相当于什么类型都没写let nobody: any = 'nobody, but you';nobody = 123;let nulls: number = null;let bool: boolean = undefined;// voidfunction printUsername (name: string): void { console.log(name);} 联合类型 联合类型在option bags模式场景非常实用,使用 | 来做标记 123456function options(opts: { types?: string; tag: string | number;}): void { } 交叉类型 最典型的使用场景就是继承和mixin,或者copy等操作 1234// 交叉类型:如果以后遇到此种类型声明不会写,直接看Object.assign声明写法function $extend<T, U>(first: T, second: U): T & U { return Object.assign(first, second); // 示意而已} 元组 tuple 元组很少使用 123456789101112let nameNumber: [string, number];// OknameNumber = ['Jenny', 221345];// Error// nameNumber = ['Jenny', '221345'];let tuple: [string, number];nameNumber = ['Jenny', 322134];const [usernameStr, uselessNum] = nameNumber; type的作用 type用来创建新的类型,也可以重命名(别名)已有的类型,建议使用type创建简单类型,无嵌套的或者一层嵌套的类型,其它复杂的类型都应该使用interface, 结合implements ,extends实现。 123456789type StrOrNum = string | number;// 使用let sample: StrOrNum;sample = 123;sample = '123';// 会检查类型sample = true; // Error 实践中遇到的问题第三方库没有提供声明d.ts文件 如果第三方库没有提供声明文件,第一时间去微软官方的仓库https://github.com/borisyankov/DefinitelyTyped 查找,或者在npmjs.com上搜索@types/依赖的模块名大部分情况都可以找到。 手动添加声明文件 声明文件一般都是统一放置在types文件夹下 12// 例子: types/axios.d.tsdeclare module 'axios'; // 这里的axios声明为any类型 全局变量 例如一些库直接把在window上添加的全局变量 1234// globals.d.ts// 例子:jQuery,现实中jQuery是有.d.tsdeclare const jQuery: any;declare const $: typeof jQuery; 非JavaScript资源 在前端工程中,import很多非js资源,例如:css, html, 图片,vue, 这种ts无法识别的资源时,就需要告诉ts,怎么识别这些导入的资源的类型。 12345678910// 看看vue怎么处理的:shims-vue.d.tsdeclare module '*.vue' { import Vue from 'vue'; export default Vue;}// htmldeclare module '*.html';// cssdeclare module '*.css'; 强制类型转换 有时候遇到需要强制类型转换,尤其是对联合类型或者可选属性时。 12345// 第一种:使用<>括号const convertArrType: string[] = <Array<string>>[].concat(['s']);// 第二种:使用as关键字const fixArrNever: string[] = ([] as string[]).concat(['s']); 建议使用第二种,因为兼容JSX,第一种官方也不推荐了,虽然它是合法的。 可选属性和默认属性 API中提供的参数很多都有默认值,或者属性可选,怎么书写呢? 1234567891011121314151617class Socket {}// 联合类型export type SocketType = 'WebSocket' | 'SockJs';export interface SocketOptions { type: SocketType; protocols?: string | string[]; // 可选 pingMessage: string | (() => string); // 联合类型,可以为string或者函数 pongMessage: string | (() => string);}// 默认值export function eventHandler = ( evt: CloseEvent | MessageEvent | Event, socket: Socket, type = 'WebSocket' // 默认值) => any; 独立函数怎么声明类型 刚开始我也很纠结这个问题,我就是一个独立的函数,怎么声明类型呢?尤其是写事件处理函数的API时。 123456789101112131415class Socket {}// 函数的声明方式export type SocketEventHandler = ( evt: CloseEvent | MessageEvent | Event, socket: Socket) => any;const eventHandler: SocketEventHandler = (evt, socket) => {}// 可选参数和rest参数let baz = (x = 1) => {};let foo = (x: number, y: number) => {};let bar = (x?: number, y?: number) => {};let bas = (...args: number[]) => {}; 索引属性类型声明 JavaScript中的对象都可以使用字符串索引直接取属性或者调用方法,TypeScript中也有相应的类型声明方法。 1234567891011type Hello = { hello: 'world'; // key只是一个形式属性名(类似形参一样) [key: string]: string;};const greeting: Hello = { hi: 'morning'}console.log(greeting['hi']) 动态添加的属性声明 有的时候我们只声明了一个基本的类型结构,然后后续有扩展的情况 ,尤其时二次封装时的options。 1234567891011121314151617181920interface AxiosOptions {}type AjaxOptions = { axiosOptions: AxiosOptions; // 额外扩展的放入到单独的属性节点下 extraOptions: { [prop: string]: any }; };type AjaxOptions1 = { axiosOptions?: AxiosOptions; // 不要这样写,因为axiosOptions拼写错误时,ts不会提示 // 尽量把后续扩展的属性,移动到独立的属性节点下 [prop: string]: any};const ajaxOptions: AjaxOptions1 = { axiosOptions1: {}; // 本意是axiosOptions,但是ts不会提示} !的使用 ! 标识符告诉ts编译器,声明的变量没有问题,再运行期不会报错。 12345678910111213141516class BaseSelect extends Vue { options: string[]; // 这里会提示没有在constructor中初始化 created() { this.options = ['inited'] }}class BaseSelect extends Vue { options!: string[]; // 使用 ! 告诉编译器,我知道自己在做什么 created() { this.options = ['inited'] }} this的使用 对于独立使用的函数,可以声明指定的调用上下文 12345678910class Handler { info: string; // 声明指定的this上下文 onClickBad(this: Handler, e: Event) { // oops, used this here. using this callback would crash at runtime this.info = e.message; }}let h = new Handler();uiElement.addClickListener(h.onClickBad); // error! 声明合并(扩展Vue声明) 来看看使用场景,扩展vue,在vue上添加全局的属性。 12345678910111213// vue的声明在 vue/types/vue.d.tsdeclare module 'vue/types/vue' { // 相当于Vue.$eventBus interface Vue { $eventBus: Vue; } // 相当于在Vue.prototype.$eventBus interface VueConstructor { $eventBus: Vue; }} 总结 TypeScript声明还有很多高级的用法,目前我也没有用到那么多,在我纠结不会写声明的时候,我就会看看别人的声明文件时怎么写的。 注意:尽量不要把解构和声明写在一起,可读性极差。 12345678910class Node { onNodeCheck(checkedKeys: any, { // 解构 checked, checkedNodes, node, event, } : { // 声明 node: any; [key: string]: any; } ) { }}","tags":[{"name":"typescript","slug":"typescript","permalink":"https://betgar.github.io/tags/typescript/"}]},{"title":"在线工具收集","date":"2019-08-13T10:00:00.000Z","path":"2019/08/13/online-tools-collection/","text":"在线工具收集 腾讯开发者手册 https://cloud.tencent.com/developer/devdocs 印记中文 https://docschina.org/ learn git https://learngitbranching.js.org/ Babel在线编译 https://babeljs.io/repl polyfill online https://polyfill.io/v3/ https://polyfill.alicdn.com/polyfill.min.js browserlist https://browserl.ist/ gitignore https://www.gitignore.io/ webpack config https://www.nkgr.app/webpack-config-generator/ https://doug2k1.github.io/webpack-generator/ Type Search: typescript的类型文件搜索 https://github.com/DefinitelyTyped/DefinitelyTyped https://microsoft.github.io/TypeSearch/ TypeScript starter https://www.tslang.cn/samples/index.html https://www.typescriptlang.org/play/index.html wapm(webassembly package manager) https://wapm.io/ WasmExplorer(wa在线编译器) http://mbebenita.github.io/WasmExplorer/ draw.io https://www.draw.io/ sql.js http://kripken.github.io/sql.js/examples/GUI/ uTools https://u.tools/ online tools http://tool.oschina.net/ jsnice: 可以反编译混淆过的代码 http://jsnice.org/ Online JavaScript Beautifier https://beautifier.io/ 在线工具 https://tool.lu/index.html CSS, JavaScript 压缩, 美化, 加密, 解密 https://tool.css-js.com/ cssdb https://cssdb.org/ https://preset-env.cssdb.org/features css playground https://preset-env.cssdb.org/playground stylelint https://stylelint.io/demo cssnano https://cssnano.co/guides/optimisations/ node green https://node.green/ ecmascript-compat-table http://kangax.github.io/compat-table/es6/ DOM lint https://kangax.github.io/domlint/ es-checker http://ruanyf.github.io/es-checker/index.cn.html htmlhint https://htmlhint.io/ htmllint http://htmllint.github.io/ redis http://try.redis.io/ mdown nice https://mdnice.com/ m2c (markdown to confluence) https://zhuyi731.github.io/m2c-webpage/ https://chunpu.github.io/markdown2confluence/browser/ npm package markdown2confluence-cws npm trends https://www.npmtrends.com px2em https://vasilis.nl/nerd/code/emcalc/ es6 http://es6-features.org globtester http://www.globtester.com/","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"常用的npm的packages mirror配置","date":"2019-08-12T10:00:00.000Z","path":"2019/08/12/npm-packages-mirror-config/","text":"常用的npm的packages mirror配置 chrome driver https://www.npmjs.com/package/chromedriver 12345# 安装指定地址npm install chromedriver --chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver# 直接设置`.npmrc`变量npm config set chromedriver_cdnurl \"https://npm.taobao.org/mirrors/chromedriver\" 12# 添加环境变量 CHROMEDRIVER_CDNURLCHROMEDRIVER_CDNURL=https://npm.taobao.org/mirrors/chromedriver npm install chromedriver electron https://npm.taobao.org/mirrors/electron/ 1234npm config set electron_mirror https://npm.taobao.org/mirrors/electron/# 或者ELECTRON_MIRROR=\"https://npm.taobao.org/mirrors/electron/\" npm i electron phantomjs https://github.com/Medium/phantomjs 1234567npm install phantomjs-prebuilt --phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/# .npmrc 方式npm config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/# PATHPHANTOMJS_CDNURL=https://npm.taobao.org/mirrors/phantomjs/ node-sass https://github.com/sass/node-sass 123npm install -g mirror-config-china --registry=http://registry.npm.taobao.org# .npmrcnpm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/","tags":[{"name":"npm","slug":"npm","permalink":"https://betgar.github.io/tags/npm/"}]},{"title":"Polyfill 方案的过去、现在和未来","date":"2019-08-04T11:00:00.000Z","path":"2019/08/04/polyfill-passed-current-future/","text":"Polyfill 方案的过去、现在和未来 作者:sorrycc 日期:2019-01-29 01:31:04 原文地址: https://github.com/sorrycc/blog/issues/80 blog: https://gitissue.com/issues/5c4fe57daba98d5c7d6f4e2c 任何一个小知识点,深挖下去,也是非常有意思的。 什么是补丁? A polyfill, or polyfiller, is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively. Flattening the API landscape if you will. 我们希望浏览器提供一些特性,但是没有,然后我们自己写一段代码来实现他,那这段代码就是补丁。 比如 IE11 不支持 Promise,而我们又需要在项目里用到,写了这样的代码: 123456<script> Promise.resolve('bar') .then(function(foo) { document.write(foo); });</script> 这时在 IE 下运行就会报错了, [ img](https://camo.githubusercontent.com/348c3453fc18218b9a60e6eb67821c83e1ec8bdf/68747470733a2f2f67772e616c697061796f626a656374732e636f6d2f7a6f732f726d73706f7274616c2f6b766d4e755778747758636e764f574f6c6f4d752e706e67) 然后在此之前加上补丁, 1234567<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script><script> Promise.resolve('bar') .then(function(foo) { document.write(foo); });</script> 刷新浏览器,就可以正常运行了, [ img](https://camo.githubusercontent.com/d8aae6399751c7cc33c634397162e0d3dfd3f31e/68747470733a2f2f67772e616c697061796f626a656374732e636f6d2f7a6f732f726d73706f7274616c2f6c6b7a4e70546878667a7565506a51736f586a792e706e67) 过去shim + sham如果你是一个 3 年陈 + 的前端,应该会有听说过 shim、sham、es5-shim 和 es6-shim 等等现在看起来很古老的补丁方式。 那么,shim 和 sham 是啥?又有什么区别? shim 是能用的补丁 sham 顾名思义,是假的意思,所以 sham 是一些假的方法,只能使用保证不出错,但不能用。至于为啥会有 sham,因为有些方法的低端浏览器里根本实现不了 babel-polyfill.js在 shim 和 sham 之后,还有一种补丁方式是引入包含所有语言层补丁的 babel-polyfill.js。比如: 1<script src=\"https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.js\"></script> 然后就 es6、es7 特性随便写了。 但缺点是,babel-polyfill 包含所有补丁,不管浏览器是否支持,也不管你的项目是否有用到,都全量引了,所以如果你的用户全都不差流量和带宽(比如内部应用),尽可以用这种方式。 现在现在还没有银弹,各种方案百花齐放。 @babel/preset-env + useBuiltins: entry + targetsbabel-polyfill 包含所有补丁,那我只需要支持某些浏览器的某些版本,是否有办法只包含这些浏览器的补丁?这就是 @babel/preset-env + useBuiltins: entry + targets 配置的方案。 我们先在入口文件里引入 @babel/polyfill, 1import '@babel/polyfill'; 然后配置 .babelrc,添加 preset @babel/preset-env,并设置 useBuiltIns 和 targets, 12345678{ \"presets\": [ [\"@babel/env\", { useBuiltIns: 'entry', targets: { chrome: 62 } }] ]} useBuiltIns: entry 的含义是找到入口文件里引入的 @babel/polyfill,并替换为 targets 浏览器/环境需要的补丁列表。 替换后的内容,比如: 123import \"core-js/modules/es7.string.pad-start\";import \"core-js/modules/es7.string.pad-end\";... 这样就只会引入 chrome@62 及以上所需要的补丁,什么 Promise 之类的都不会再打包引入。 是不是很好用? 😄 有什么问题? 🤔 细细想想,其实还有不少问题, 特性列表是按浏览器整理的,那怎么知道哪些特性我用了,哪些没有用到,没有用到的部分也引入了是不是也是冗余?@babel/preset-env 有提供 exclude 的配置,如果我配置了 exclude,后面是否得小心翼翼地确保不要用到 exclude 掉的特性 补丁是打包到静态文件的,如果我配置 targets 为 chrome: 62, ie: 9,那意味着 chrome 62 也得载入 ie 9 相关的补丁,这也是一份冗余 我们是基于 core-js 打的补丁,所以只会包含 ecmascript 规范里的内容,其他比如说 dom 里的补丁,就不在此列,应该如何处理? 手动引入传统的手动打补丁方案虽然低效,但直观有用。有些非常在乎性能的场景,比如我们公司的部分无线 H5 业务,他们宁可牺牲效率也要追求性能。所以他们的补丁方案是手动引入 core-js/modules 下的文件,缺啥加啥就好。 注意: core-js 目前用的是 v2 版本,不是 v3-beta 补丁用的是 core-js/modules,而不是 core-js/library。为啥?二者又有啥区别呢? 在线补丁,比如:polyfill.io前面的手动引入解决的是特性列表的问题,有了特性列表,要做到按需下载,就需要用到在线的补丁服务了。目前最流行的应该就是 polyfill.io,提供的是 cdn 服务,有些站点在用,例如 https://spectrum.chat/。另外,polyfill.io 还开源了 polyfill-service 供我们自己搭建使用。 使用上,比如: 1<script src=\"https://polyfill.io/v3/polyfill.min.js?features=default%2CPromise\"></script> 然后在 Chrome@71 下的输出是: 1/* Disable minification (remove `.min` from URL path) for more info */ 啥都没有,因为 Promsie 特性 Chrome@71 已经支持了。 未来关于补丁方案的未来,我觉得按需特性探测 + 在线补丁才是终极方案。 按需特性探测保证特性的最小集;在线补丁做按需下载。 按需特性探测可以用 @babel/preset-env 配上 targets 以及试验阶段的 useBuiltIns: usage,保障特性集的最小化。之所以说是未来,因为 JavaScript 的动态性,语法探测不太可能探测出所有特性,但上了 TypeScript 之后可能会好一些。另外,要注意一个前提是 node_modules 也需要走 babel 编译,不然 node_modules 下用到的特性会探测不出来。 在线补丁可以用类似前面介绍的 https://polyfill.io/ 提供的方案,让浏览器只下载必要的补丁,通常大公司用的话会部署一份到自己的 cdn 上。(阿里好像有团队部署了,但一时间想不起地址了。) FAQ组件应该包含补丁吗?比如 dva 里用了 Promise,是否应该把 Promise 打在 dva 的产出里?不应该。 比如项目了依赖了 a 和 b,a 和 b 都包含 Promise 的补丁,就会有冗余。所以组件不应该包含补丁,补丁应该由项目决定。 组件不包含补丁?那需要处理啥?通常不需要做特殊处理,但是有些语言特性的实现会需要引入额外的 helper 方法。 比如: 1console.log({ ...a }); 编译后是: 12345function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }console.log(_objectSpread({}, a)); 然后我们会有很多文件,每个文件都引入一遍 helper 方法,会有很多冗余。所以我们通常会使用 @babel/plugin-transform-runtime 来复用这些 helper 方法。 在 .babelrc 里配置: 12345{ \"plugins\": [ \"@babel/transform-runtime\" ]} 编译后是: 1234var _interopRequireDefault = require(\"@babel/runtime/helpers/interopRequireDefault\");var _objectSpread2 = _interopRequireDefault(require(\"@babel/runtime/helpers/objectSpread\"));console.log((0, _objectSpread2.default)({}, a)); 所以,组件编译只要确保没有冗余的 helper 方法就好了。 core-js/library or core-js/modules?core-js 提供了两种补丁方式。 core-js/library,通过 helper 方法的方式提供 core-js/module,通过覆盖全局变量的方式提供 举个例子, 12import '@babel/polyfill';Promise.resolve('foo'); .babelrc 配: 12345678910{ \"presets\": [ [\"@babel/env\", { \"useBuiltIns\": \"entry\", \"targets\": { \"ie\": 9 } }] ]} 编译结果是: 12345require(\"core-js/modules/es6.promise\");require(\"core-js/modules/es7.promise.finally\");// 此处省略数十个其他补丁...Promise.resolve('foo'); 然后把文件内容换成: 12// import '@babel/polyfill';Promise.resolve('foo'); .babelrc 配: 1234567{ \"plugins\": [ [\"@babel/transform-runtime\", { \"corejs\": 2 }] ]} 编译结果是: 1234var _interopRequireDefault = require(\"@babel/runtime-corejs2/helpers/interopRequireDefault\");var _promise = _interopRequireDefault(require(\"@babel/runtime-corejs2/core-js/promise\"));_promise.default.resolve('foo'); 然后 @babel/runtime-corejs2/core-js/promise 的内容是: 1module.exports = require(\"core-js/library/fn/promise\"); 目前推荐是用 core-js/modules,因为 node_modules 不走 babel 编译,所以 core-js/library 的方式无法为依赖库提供补丁。 非 core-js 里的特性,如何打补丁?手动引入,比如 Intl.js、URL 等。但是得小心有些规范后续加入 ecmascript 之后可能的冗余,比如 URL。 参考 http://2ality.com/2011/12/shim-vs-polyfill.html https://babeljs.io/docs/en/next/babel-preset-env.html https://github.com/zloirock/core-js/tree/v2","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"}]},{"title":"一步一步配置Typescript工程","date":"2019-08-01T05:00:00.000Z","path":"2019/08/01/the-steps-of-configuration-typescript/","text":"一步一步配置Typescript工程 这里列举一下本工程配置的步骤,有兴趣可以了解。 README模板选择 README模板 LICENSE模板选择 CHOOSE LICENSE 项目初始化 初始化project 12345# 初始化仓库git init# 初始化package信息npm init .gitignore gitignore在线生成 .editorconfig EditorConfig网站 .browserlistrc目标环境在线生成及查看浏览器覆盖情况 Babel安装和配置 Babel的相关文档链接 babel presets文档 babel-polyfill文档 babel config文档 babel proposal文档 安装 安装Babel 1npm i --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-typescript 安装proposal 1npm i --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread 安装polyfill @babel/polyfill在babel v7.4.0 之后为deprecated 123# 如果需要polyfill# 注意:core-js版本, babel 7.4.0之后@babel/polyfill过时,默认依赖core-js@2版本,也可以指定3npm i @babel/polyfill core-js 1234567// 如果之前在entry这么引入polyfill// beforeimport \"@babel/polyfill\";// 现在替换为这种方式(core-js@3)import \"core-js/stable\";import \"regenerator-runtime/runtime\"; Babel配置 仅供参考,你可以自己自定义 12345678910111213141516171819202122232425262728// babel.config.js// targets推荐使用.browserslistrc替换const presets = [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, }, ], [ '@babel/preset-typescript', ],];const plugins = [ '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-object-rest-spread',];module.exports = { presets, plugins, exclude: [ 'src/**/*.spec.(js|ts)', 'src/**/*.test.(js|ts)', ], comments: false,}; eslint配置安装eslint并初始化 因为TypeScript官方团队选择支持eslint,所以以后还是选择eslint玩吧。12345# 安装npm i -D eslint# 生成默认的配置,因为格式化是ts,默认先不选代码风格npx eslint --init 安装TypeScript解析器和插件 typescript-eslint因为eslint的解析器不认识TypeScript,所以这里需要替换掉默认的parser12# @typescript-eslint/parsernpm i --save-dev typescript @typescript-eslint/parser 代码风格(typescript-eslint + Prettier + airbnb)@typescript-eslint/eslint-plugin文档 @typescript-eslint/eslint-plugin支持的规则 建议以后选择eslint-config-alloy 12# 安装@typescript-eslint/eslint-pluginnpm i --save-dev @typescript-eslint/eslint-plugin Prettier一起使用(可选)1npm i --save-dev eslint-config-prettier airbnb一起使用(可选) eslint-config-airbnb文档 12# 注意:官方文档要求npm 5+npx install-peerdeps --dev eslint-config-airbnb eslint的最终的配置12345678910111213141516171819202122232425262728// .eslintrc.jsmodule.exports = { parser: '@typescript-eslint/parser', // 解析器 plugins: ['@typescript-eslint'], extends: [ 'airbnb', 'plugin:@typescript-eslint/recommended', 'prettier', 'prettier/@typescript-eslint', ], env: { browser: true, es6: true, node: true, }, globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly', }, parserOptions: { ecmaVersion: 2018, sourceType: 'module', }, rules: { '@typescript-eslint/no-explicit-any': ['off'], '@typescript-eslint/no-var-requires': ['off'] },}; 因为eslint默认识别.js结尾的文件,这里需要指定下文件后缀eslint之后是文件夹, –ext参数才生效,如果是具体的正则表达式则匹配–ext被忽略123456// package.json{ \"scripts\":{ \"lint\": \"eslint src --ext .js,.ts,.tsx,.jsx\" }} TypeScript配置 编译器的options配置 tsconfig.json初始化1npx tsc --init --declaration --allowSyntheticDefaultImports --target esnext --outDir dist 添加scripts命令12345678910// package.json{ \"scripts\": { \"checktypes\": \"tsc --noEmit\", \"checktypes:watch\": \"npm run checktype -- --watch\", \"build\": \"npm run build:types && npm run build:js\", \"build:types\": \"tsc --emitDeclarationOnly\", \"build:js\": \"npx babel src --out-dir dist --source-maps inline --extensions .ts,.tsx\" }} package.json指定声明文件入口1234{ // 如果不指定默认查找project根目录的index.d.ts \"types\": \"dist/index.d\"} webpack安装webpack1npm install --save-dev webpack webpack-cli babel-loader 配置webpack1234567891011121314151617181920212223// webpack.config.jsconst path = require('path');module.exports = { mode: \"production\", entry: './src/index', output: { path: path.resolve(__dirname, 'dist'), filename: 'app.bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.json'] }, module: { rules: [ { test: /\\.(ts|js)x?$/, exclude: /node_modules/, loader: 'babel-loader' } ] }}; 123456// package.json{ \"scripts\":{ \"bundle\": \"webpack\" }} 约定式提交Conventional Commits commit message style校验 commitlint安装 1npm install --save-dev @commitlint/config-conventional @commitlint/cli commitlint配置 1echo \"module.exports = {extends: ['@commitlint/config-conventional']}\" > commitlint.config.js husky和lint-staged安装 12# 安装npm i husky lint-staged --save-dev husky配置 123456789// package.json 配置{\"husky\": { \"hooks\": { \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\", // 配合commitlint使用 \"pre-commit\": \"lint-staged\" // 配置lint-staged } }} lint-staged配置 123456789// package.json{ \"lint-staged\": { \"src/**/*.{js,ts,tsx,jsx}\": [ \"eslint src --fix --ext .ts,.js,.tsx,.jsx\", // npm run lint \"git add\" ] }} TODO 完成了大部分工程涉及的工具的配置,还有测试相关没有进行配置,以后有时间继续。 [ ] jest [ ] jest + typescript","tags":[{"name":"typescript","slug":"typescript","permalink":"https://betgar.github.io/tags/typescript/"}]},{"title":"Babel 6 松散模式","date":"2019-07-30T11:00:00.000Z","path":"2019/07/30/babel6-loose-mode/","text":"Babel 6 松散模式 英文原文:https://2ality.com/2015/12/babel6-loose-mode.html 作者:minwe 中文:https://csspod.com/babel6-loose-mode/ 想必每个写 JavaScript 的都或多或少了解过 ES5 引入的严格模式,Babel 6 在把 ES6+ 代码转换为 ES5 时,也提供了「松散模式」(loose mode)的选项。Babel 的松散模式和 ES5 没有任何关系,这里提 ES5 严格模式只是在概念上作一个比对,方便理解 Babel 的松散模式。 两种模式一些 Babel 插件有两种工作模式: 标准模式:转换时尽可能遵循、接近 ES6 语义。 松散模式:转换为简单的 ES5 实现。 松散模式的优、缺点显而易见: 优点:转换出来的代码更简洁,没有为了接近 ES6 而添加的繁杂逻辑,文件更小,运行速度更快,兼容性更好。 缺点:以后直接使用原生 ES6 时可能会遇到问题。需要留意。 一般而言,不建议使用松散模式,不过也可以综合考虑项目的复杂程度,结合实际做选择。 配置方式可以直接使用 babel-preset-es2015-loose: 12345{ \"presets\": [ \"es2015-loose\" ],} 具体到某个插件时,可以设置 loose 参数为 true: 123456{ \"plugins\": [ [\"transform-es2015-classes\", {loose: true}], \"transform-es2015-object-super\" ]} 转换结果比较以下面的代码为例: 123456789class Person { constructor(name) { this.name = name; } sayHi() { return `Hi, this is ${this.name}`; }} .babelrc 配置为标准模式时: 12345{ \"presets\": [ \"es2015\" ]} 转换以后的代码为: 12345678910111213141516171819202122\"use strict\";var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }var Person = function () { function Person(name) { _classCallCheck(this, Person); this.name = name; } _createClass(Person, [{ key: \"sayHi\", value: function sayHi() { return \"Hi, this is \" + this.name; } }]); return Person;}(); 使用 loose preset 后: 12345{ \"presets\": [ \"es2015-loose\" ]} 转换结果为: 1234567891011121314151617\"use strict\";function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }var Person = function () { function Person(name) { _classCallCheck(this, Person); this.name = name; } Person.prototype.sayHi = function sayHi() { return \"Hi, this is \" + this.name; }; return Person;}(); 确实更像熟悉的 ES5 代码。 该如何选择,自己看着办咯。 其它https://fecoding.cn/2016/08/10/the-loose-mode-babel-6/","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"}]},{"title":"node-glob-syntax","date":"2019-07-30T10:00:00.000Z","path":"2019/07/30/node-glob-syntax/","text":"Node glob语法 原文地址:https://github.com/isLishude/blog/issues/63 本文 由 Ivan Yan 翻译,“署名-非商用-相同方式共享”,意见反馈。 通过星号等 shell 所用的模式匹配文件。 这是一个 JavaScript 版本的 glob 实现。使用 minimatch 执行匹配操作。 译注:早期 Unix (第 1-6 版,1969-1975)的命令行解释器依赖独立程序 /etc/glob 展开参数中的通配符。这个程序会展开通配符并把展开后的文件列表传给命令。它的名字是 “global command” 的简称。后来这个功能由工具函数 glob() 提供,被 shell 等程序使用。(译自 WikiPedia#Origin)。) 用法用 npm 安装: 1npm i glob 12345678var glob = require(\"glob\")// options 可选glob(\"**/*.js\", options, function (er, files) { // files 是一个文件名数组。 // 如果设置了选项 `nonull` 并且没有找到匹配,则 files 是 [\"**/*.js\"] // er 是一个错误对象或 null。}) Glob 基础“Globs” 是像这样的模式:用在命令行中的 ls *.js, 用在 .gitignore 文件中的 build/*。 在解析路径中的模式前,先展开大括号部分。大括号部分以 { 开始,以 } 结束,里面是一个逗号分隔列表。大括号部分可以包含斜杠,例如 a{/b/c,bcd} 将展开为 a/b/c 与 abcd。 译注:比如路径 “/foo/bar”, 其中 “foo” 与 “bar” 是路径片段(path portion)。 下面这些字符在路径片段中有特别的意义: * 匹配路径片段中零个或多个字符。 ? 匹配一个字符。 [...] 匹配一个字符集合,类似于正则表达式的字符集合。如果第一个字符是 ! 或 ^ 那么它匹配一个不在这个字符集合内的字符。 !(pattern|pattern|pattern) 匹配不匹配模式的文件。 ?(pattern|pattern|pattern) 匹配这些模式零次或一次。 +(pattern|pattern|pattern) 匹配这些模式一次或多次。 *(a|b|c) 匹配这些模式零次或多次。 @(pattern|pat*|pat?erN) 匹配这些模式一次。 ** 即 globstar 模式,如果这是单独的一个路径片断,则匹配零级或多级目录,但不会搜索符号链接目录。 点号如果文件或目录路径片段的第一个字符是点号(.),那么它将不匹配任何 glob,除非 glob 相应的路径片段的第一个字符也是 .。 例如,a/.*/c 匹配 a/.b/c,但是 a/*/c 不匹配,因为 * 第一个字符不是 .。 可以设置选项 dot:true,将 . 当作普通的字符。 译注:点文件(dot file),名字以 . 开始,在 Unix 下是隐藏文件。即使使用 globstar 模式,a/**/c 也不会匹配 a/.b/c。 匹配基本名如果设置选项 matchBase:true,并且模式不包含斜杠,那么将搜索目录树下任意地方的匹配基本名(basename)的文件。例如 *.js 匹配 test/simple/basic.js。 空集如果没有找到匹配的文件,那么返回一个空的数组。这跟 shell 不同,shell 会返回模式。例如: 12$ echo a*s*d*fa*s*d*f 想与 shell 一致,设置选项 nonull:true。 其它参考 man sh man bash (搜索 “Pattern Matching”) man 3 fnmatch man 5 gitignore minimatch 文档 glob.hasMagic(pattern, [options])如果模式包含特殊的字符则返回 true,否则返回 false。 注意选项会影响结果。如果设置了选项 noext:true,则 +(a|b) 不会视为魔法模式。如果模式包含大括号展开式,比如 a/{b/c,x/y},则认为是魔法的,除非设置了选项 nobrace:true。 glob(pattern, [options], cb) pattern {String} 待匹配的模式 options {Object} cb {Function} err {Error | null} matches {Array<String>} 匹配模式的文件名 进行一个异步的 glob 搜索。 glob.sync(pattern, [options]) pattern {String} 待匹配的模式 options {Object} return: {Array<String>} 匹配模式的文件名 进行一个同步的 glob 搜索。 Class: glob.Glob实例化 glob.Glob 类,创建一个 Glob 对象。 12var Glob = require("glob").Globvar mg = new Glob(pattern, options, cb) 这是一个 EventEmitter 对象,立刻开始遍历文件系统搜索匹配。 new glob.Glob(pattern, [options], [cb]) pattern {String} 待匹配的模式 options {Object} cb {Function} 当遇到错误或找到匹配时调用 err {Error | null} matches {Array<String>} 匹配模式的文件名 注意,如果设置了选项 sync,则匹配将立即添加到 g.found。 属性 minimatch glob 所用的 minimatch 对象。 options 传入的选项。 aborted 布尔值,当调用 abort() 时设为 true。取消之后不能继续 glob 搜索,不过可以通过重用 statCache 避免重复调用 syscall。 cache 缓存。每个字段都可以取下面的值: false - 路径不存在 true - 路径存在 'FILE' - 路径存在,并且不是目录 'DIR' - 路径存在,并且是目录 [file, entries, ...] - 路径存在, 并且是目录,数组值是 fs.readdir 的结果 statCache 缓存 fs.stat 的结果,阻止多次读取同一路径的信息。 symlinks 记录哪些路径是符号链接,与 ** 解析相关。 realpathCache 可选对象,传给 fs.realpath,以减少不必要的 syscall。它保存在 Glob 实例上,可以重用。 事件 end 当结束搜索匹配时触发此事件,包含所有的匹配。如果设置了选项 nonull,并且没有找到匹配,则 matches 包含原来的模式。匹配经过排序,除非设置了选项 nosort。 match 每当找到一个匹配时以这个匹配触发此事件,匹配没有去重,也没有解析为真实路径。 error 当遇到一个异常时, 或者在设置了 options.strict 的情况下遇到 fs 错误时触发此事件。 abort 当调用 abort() 时触发此事件。 方法 pause 暂停搜索。 resume 继续搜索。 abort 取消搜索。 选项所有可以传给 Minimatch 的选项也可以传给 Glob,选项会改变匹配行为。有些选项是新加的,有些选项是 glob 特定选项。 所有选项默认是 false, 除非特别说明。 所有选项也会添加给 Glob 对象。 如果运行多个 glob 操作,可以将一个 Glob 对象作为 options 参数传递给后面的操作,以简化一些 stat 和 readdir 的调用。在最新的版本里,你可以传入共享的 symlinks, statCache, realpathCache, cache 选项,这样并行的 glob 操作将因为共享文件系统的信息而提速。 cwd String,搜索的工作目录,默认为 process.cwd()。 root String,以 / 开始的模式的挂载目录,默认为 path.resolve(options.cwd, "/") (Unix 系统下为 /,Windows 系统下为 C:\\ 或其它磁盘根目录。) dot 在常规匹配与 globstar 匹配中包含点文件。注意,. 在模式片断中始终匹配点文件。 nomount 以 / 开始的模式默认挂载到 root 选项设置的目录上,因而返回一个合法的文件系统路径。设置此选项禁止此行为。 mark 给匹配的目录添加一个 / 字符。注意这会调用 stat。 nosort 不排序结果。 stat stat 所有的结果。这多少会降低性能,完全没必要,除非认为 readdir 不能作为文件存在的可靠指示。 silent 当读取目录时遇到一个不常见的错误,将打印一条警告到 stderr。设置此选项可取消打印。 strict 当读取目录时遇到一个不常见的错误,进程将继续搜索其它匹配。设置此选项可抛出错误。 cache Object, 见上文。传入之前生成的缓存对象可以节省一些 fs 调用。 statCache Object, 匹配结果的文件系统信息的缓存,用来阻止不必要的 stat 调用。通常不需要设置此选项。不过如果知道文件系统在不同的 glob() 调用之间不会变化,可以将一个 glob() 调用的 statCache 传给另一个调用的选项(见下面“竞态条件”)。 symlinks Object, 已知的符号链接的缓存。可以传入一个之前生成的 symlinks 对象,在匹配 ** 时节省 lstat 调用。 sync 废弃,可以用 glob.sync(pattern, opts)。 nounique 在有些情况下,大括号展开式会导致在结果里面同一文件出现多次。本实现默认阻止结果里面出现重复。此选项禁止此行为。 nonull 不返回空集,返回一个包含模式的集合,这是 glob(3) 的默认行为。 debug 启用 minimatch 和 glob 调试。 nobrace 不展开 {a,b} 和 {1..3} 这样的集合。 noglobstar 不支持 “globstar” 模式,这时 ** 不匹配多级文件名,像普通的 * 一样对待。 noext 不支持 “extglob” 模式,比如 +(a|b)。 nocase 匹配不区分大小写。注意:在不区分大小写的系统里,默认匹配没有特殊字符的模式,因为 stat 和 readdir 不会抛出异常。 matchBase 如果模式不包含斜杠则匹配基名字。例如 *.js 将视为 **/*.js,匹配所有目录下的 js 文件。 nodir 不匹配目录,只匹配文件。注意如果只匹配目录,简单地在模式的末尾放一个 / 即可。 ignore 添加一个模式或一个 glob 模式数组,用来排除匹配。注意:ignore 模式始终认为 dot:true,不管其它的配置是怎样的。 follow 在展开 ** 时追踪符号链接目录。注意这可能导致大量重复的引用(循环链接)。 realpath 在所有的结果上调用 fs.realpath,在不能解析符号链接的情况下,返回匹配文件的全路径,不过它常常是一个损坏的符号链接。 absolute 设为 true 时始终得到匹配文件的绝对地址。不同于 realpath,这同时影响 match 事件的返回值。 与其它 fnmatch/glob 实现的比较严格地兼容现实规范是值得追求的目标,不过 node-glob 与其它实现之间存在差异,并且是有意的。 默认支持 **,除非设置了选项 noglobstar。这也是 bsdglob 和 bash 4.3 的方式。只有当 ** 是单独的一个路径片段时它才有这种特殊意义。例如 a/**/b 匹配 a/x/y/b,但是 a/**b 不会。 注意,** 不会搜索符号链接目录,尽管它们可能匹配模式的其它片断。这可以防止无限循环、重复等。 如果转义的模式没有找到匹配,并且设置了选项 nonull,则 glob 原样返回模式,而不是转义后的模式。例如 glob.match([], "\\\\*a\\\\?") 返回 "\\\\*a\\\\?" 而不是 "*a?"。默认的行为类似于在 bash 里设置 nullglob 选项,除了 bash 不会解析转义的模式。 如果没有禁止展开大括号,则在解析 glob 的其它模式之前先展开它。例如 +(a|{b),c)},在 bash 或 zsh 下面是无效的,在这儿会先展开为 +(a|b) 和 +(a|c),再检查这两个模式的有效性。既然它们是有效的,则进行匹配。 注释与排除在之前的版本中,如果模式以 # 开始则它是一个注释。标记为注释。如果模式以 ! 开始则它是一个排除模式。 v5 已经废弃了选项 nonegate 和 nocomment。v6 则删除了这两个选项。 如果想排除某些文件,可以使用选项 ignore 。 Windows请在 glob 表达式里只使用斜杠。 译注:斜杠(forward-slashe “/“)是顺时针方向,反斜杠(backward-slashe “”)是逆时针方向。 尽管 Windows 可以用 / 或 \\ 作为路径分隔符,但是本实现只使用 /。在 glob 表达式里必须只使用斜杠。反斜杠始终视为转义符,而不是路径分隔符。 绝对路径模式比如 /foo/*,匹配结果挂载到选项 root 设置的目录上(使用 path.join())。 在 Windows 下,在默认的情况下,/foo/* 可以匹配到 C:\\foo\\bar.txt(译注:此时 cwd 在 C 盘下)。 竞态条件Glob 搜索本质上容易受竞态条件(race conditions)的影响,因为它建立在目录遍历等上面。 因此,当 glob 搜索某个文件时它是存在的,然后在返回结果时它可能被删除或被修改。 作为内部实现的一部分,为了降低系统开销,此实现缓存了所有的 stat 和 readdir 的结果。但是,这也导致它更加容易受竞态条件的影响,特别是在多个 glob 调用之间重用 cache 或 statCache 对象时。 在面对快速的变化时,建议用户不要将 glob 结果作为文件系统状态的担保。对于绝大多数的操作,这绝不会是一个问题。 贡献对程序行为的任何改变(包含补丁)必须同时提交测试。 测试失败的或降低性能的补丁将被拒绝。 1234567891011# to run testsnpm test# to re-generate test fixturesnpm run test-regen# to benchmark against bash/zshnpm run bench# to profile javascriptnpm run prof","tags":[{"name":"node","slug":"node","permalink":"https://betgar.github.io/tags/node/"}]},{"title":"TypeScript 和 Babel 7","date":"2019-07-29T14:00:00.000Z","path":"2019/07/29/typescript-and-babel-7/","text":"TypeScript 和 Babel 7 作者:Daniel 日期:August 27th, 2018 原文:https://devblogs.microsoft.com/typescript/typescript-and-babel-7/ 今天我们很高兴为Babel用户宣布一些特别的东西。一年前,我们开始着手发现用户在使用TypeScript时遇到的最大困难,我们发现Babel用户的一个共同问题是尝试设置好TypeScript 太难了。 原因通常各不相同,但对于许多开发人员来说,重写已经正在使用的构建配置可能是一项艰巨的任务。Babel是一个出色的工具,拥有一个充满活力的生态系统,通过将最新的JavaScript特性转换为旧的运行时和浏览器,为数百万开发人员提供服 但它不进行类型检查,我们的团队认为可以将这种体验带到另一个层次。 虽然TypeScript本身可以同时执行这两项操作,但我们希望在不强制用户从Babel切换的情况下更轻松地获得该体验。 这就是为什么在过去的一年里我们与Babel团队合作,今天我们很高兴联合宣布Babel 7现在提供TypeScript支持! 我怎么使用它?如果你已经在使用Babel并且你从未尝试过TypeScript,那么现在是你的机会,因为它比以往更容易。 至少,您需要安装TypeScript插件。 1npm install --save-dev @babel/preset-typescript 您可能还想获得TypeScript支持的其他ECMAScript特性: 1npm install --save-dev @babel/preset-typescript @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread 确保你的.babelrc中的presets和plugins配置正确: 12345678910{ \"presets\": [ \"@babel/env\", \"@babel/preset-typescript\" ], \"plugins\": [ \"@babel/proposal-class-properties\", \"@babel/proposal-object-rest-spread\" ]} 一个使用@babel/cli编译的样例,你仅需要执行: 1babel ./src --out-dir lib --extensions \".ts,.tsx\" 你的文件现在构建并生成到了lib文件夹中。 使用TypeScript检查类型,需要创建一个tsconfig.json文件: 123456789101112131415161718192021{ \"compilerOptions\": { // Target latest version of ECMAScript. \"target\": \"esnext\", // Search under node_modules for non-relative imports. \"moduleResolution\": \"node\", // Process & infer types from .js files. \"allowJs\": true, // Don't emit; allow Babel to transform files. \"noEmit\": true, // Enable strictest settings like strictNullChecks & noImplicitAny. \"strict\": true, // Disallow features that require cross-file information for emit. \"isolatedModules\": true, // Import non-ES modules as default imports. \"esModuleInterop\": true }, \"include\": [ \"src\" ]} 然后执行tsc,仅仅就是这样!tsc将会对你的.ts和.tsx进行类型检查 随意添加–watch标志到任一工具,以便在任何变化时获得即时反馈。 您可以在此示例存储库中查看如何设置更复杂的构建,该存储库与Webpack等工具集成。 您也可以在Babel的在线REPL上使用TypeScript预设。 这对我意味着什么?使用TypeScript编译器仍然是构建TypeScript的首选方法。 虽然Babel可以接管编译/转换 - 执行诸如擦除类型和重写最新的ECMAScript功能以在较旧的运行时中工作 - 它没有内置类型检查,并且仍然需要使用TypeScript来实现这一点。 因此,即使Babel构建成功,您也可能需要使用TypeScript介入以捕获类型错误。 出于这个原因,我们认为tsc和compiler pipeline周围的工具仍然可以为大多数项目提供最好集成和一致的体验。 因此,如果您已经在使用TypeScript,那么这对您来说可能没什么变化。 但是,如果您已经在使用Babel,或者对Babel生态系统感兴趣,并希望获得TypeScript的好处,例如捕获拼写错误,错误检查以及您可能在Visual Studio和Visual Studio等中看到的编辑体验 代码,这是给你的! 注意事项正如我们上面提到的,用户应该注意的第一件事是Babel不会对TypeScript代码执行类型检查;它只会转换你的代码,无论是否存在类型错误,它都会编译。虽然这意味着Babel不会像读取.d.ts文件那样做并确保你的类型兼容,但可能你会想要一些工具来做到这一点,所以你仍然需要TypeScript。这可以在后台作为单独的tsc –watch任务完成,也可以是构建中的lint / CI步骤的一部分。幸运的是,通过正确的编辑器支持,您甚至可以在保存之前发现大多数错误。 其次,有些结构Babel 7没有正确的编译。具体来说, namespaces 括号风格的类型断言/强制转换语法,当启用JSX时可以忽略它们。(即,如果打开JSX支持,写入 x即使在.ts文件中也不起作用,但您可以将x as Foo)。 跨多个声明的枚举(即枚举合并) 遗留式导入/导出语法(即import foo = require(…)和export = foo) 这些遗漏主要基于Babel的单文件发射架构中的技术限制。我们相信大多数用户会发现这种体验是完全可以接受的。要确保TypeScript可以调出其中的一些遗漏,您应该确保TypeScript使用–isolatedModules标志。 接下来做什么?您可以在发布博客文章中阅读Babel方面的详细信息。 我们很高兴我们有机会与Babel团队的人们合作,如Henry Zhu,Andrew Levine,Logan Smyth,Daniel Tschinder,James Henry,Diogo Franco,Ivan Babak,NicolòRibaudo,Brian Ng和Vladimir Kurchatkin。 我们甚至有机会加速Babylon,Babel的解析器,并帮助调整James Henry在typescript-eslint-parser上的工作,该解析器现在支持Prettier’s TypeScript。 如果我们遗漏没有提到你,我们很抱歉,但我们很感激,我们感谢所有人们共同投入的帮助! 我们的团队将为TypeScript插件的未来更新做出贡献,我们期待为所有TypeScript用户带来出色的体验。 展望未来,我们很乐意听到您对Babel中这种新TypeScript支持的反馈,以及我们如何使其更易于使用。 在Twitter上@typescriptlang或下面的评论中给我们一个声音。 快乐的黑客! 资源 Babel+TypeScript以及其它工具的配置参考 TypeScript-Babel-Starter react-webpack-typescript-babel webpack-typescript-babel TypeScript With Babel: A Beautiful Marriage","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"},{"name":"typescript","slug":"typescript","permalink":"https://betgar.github.io/tags/typescript/"}]},{"title":"babel的配置的默认套路","date":"2019-07-15T05:00:00.000Z","path":"2019/07/15/babel-plugin-preset-default-configration/","text":"babel的配置的默认套路 我是一个标题党,这篇文章介绍一点babel的默认配置套路,总结了一点遇到的项目管理问题。 babel的bug刺激了我 现象:项目编译没有任何问题,打开页面才报错 问题现象console报错:_objectSpread is not defined 原因:根据输出的错误,我猜测是babel的问题,因为_objectSpread就是使用了新的对象展开的语法...obj,编译之后才出现的。当然这里我也调试了编译后打代码。【如果你想看编译打包后代码,介绍一篇文章给你: webpack编译vue项目生成的代码探索】 分析:通过git log快速分析package.json的变更,发现只更新过babel相关。所以实锤是babel问题。 分析git log时,我们当时做的commit message style统一的作用就发挥出来了,所有的工具链相关变更,统一都是chore开头,所以才能做到快速定位。顺便看看我们项目的commit message commit message style 场景重现:babel repl demo链接 真实原因:对象展开的语法,babel编译之后注入了_objectSpread或者_objectSpread2函数来处理,维护的小伙伴没有把所有的分支都覆盖。修复这个issues的小伙伴也挺幽默,估计也是被折腾的够呛,说了一句玩笑话I’ll now go hiding myself somewhere and I won’t touch an helper for a few months 😆 而且此bug是babel v7.5.4修复的,而且在低版本的@babel/core使用了高版本的@babel/plugin-proposal-object-rest-spread才能重现。 _objectSpread官方issues相关链接: ReferenceError: _objectSpread is not defined after update Missing helpers only throw once Fix _objectSpread2 for real 找的问题的根源了,新问题来了,我怎么更新@babel/core呢? 因为不是项目直接依赖的@babel/core,而是其它工具依赖,我更新了也不一定起作用。 介绍npm两个非常有用的命令: 查看项目中安装的package版本和依赖关系 123npm ls @babel/core`-- @vue/[email protected] `-- @babel/[email protected] 知道了依赖@babel/core是@vue/cli-plugin-babel,那升级@vue/cli-plugin-babel就行了。 查看npm仓库的package版本和依赖 1npm show @babel/core 推荐一个npm-check工具,专门用来更新依赖。 可以执行npm-check -u来进行交互式选择更新,而且还列出了官方文档,可以直接看看release log之后再决定是否更新。 babel的plugin和preset配置默认套路 本来我就比较懒,一直在计划学习的路上,基本都没落实行动。这次项目出现问题,我不得不去了解了一下,babel配置的那些套路。Babel的v7版本所有包都重命名了,所以还是要做到了解。 babel v7 官方升级文档链接 包含的内容太多,仔细过一遍才行,很多配置或者包名称命名规则都修改了。 babel v7.4支持core-js@3 babel v7.4支持core-js@3,尤其是对@babel/polyfill和@babel/preset-env又带来了很多改变。 新的corejs配置项 corejs配置项链接options corejs 当项目中的babel升级到v7.4+时会出现下面的警告: WARNING: We noticed you’re using the useBuiltIns option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the corejs option. 1234567891011121314151617181920// 使用core-js@3的babel.config.jsmodule.exports = function (api) { api.cache(true); const presets = [ [\"@babel/preset-env\", { \"useBuiltIns\": \"usage\", \"corejs\":3, // 指定版本 \"targets\":{ \"browsers\":[\"> 1%\", \"last 2 versions\", \"not ie <= 8\"] } } ] ]; return { presets, // plugins };} 注意:把相应的core-js安装到项目依赖中,npm i core-js@2 或者npm i core-js@3 @babel/polyfill不支持从core-js2升级到core-js3,所以v7.4开始@babel/polyfill变成过时(deprecated) 如果项目中使用core-js@3,则应该修改导入配置 1import \"@babel/polyfill\"; 替换为: 12import \"core-js/stable\";import \"regenerator-runtime/runtime\"; 直接安装依赖到项目: 1npm i --save core-js regenerator-runtime 因为babel和core-js绑定的很紧密,所以推荐看一下core-js作者的文章:core-js-3-babel-and-a-look-into-the-future详细介绍了core-js@3版本带来的改变,以及解决的问题。 Plugin配置套路 syntax plugins允许babel去parse特定的类型的syntax而不是去转换变形(transform) 注意:transform plugins会自动启用syntax plugins。 因此,如果已经使用了相应的transform plugins,则无需指定syntax plugins。 Plugin 路径如果 plugin 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules 中 123{ \"plugins\": [\"babel-plugin-myPlugin\"]} 还可以指定的相对/绝对路径。 123{ \"plugins\": [\"./node_modules/asdf/plugin\"]} Plugin 简写(官方非常不推荐)如果包的名称以 babel-plugin- 为前缀,可以使用简写: 123456{ \"plugins\": [ \"myPlugin\", \"babel-plugin-myPlugin\" // equivalent ]} 这也适用于 scoped 包: 123456{ \"plugins\": [ \"@org/babel-plugin-name\", \"@org/name\" // equivalent ]} Plugin执行顺序 如果两个transforms都访问“程序”节点,则transforms将以plugin或preset顺序运行。 plugin在preset之前运行。 plugin执行顺序是第一个到最后一个。 preset顺序相反(从最后到第一个)。 123{ \"plugins\": [\"transform-decorators-legacy\", \"transform-class-properties\"]} 先执行transform-decorators-legacy后transform-class-properties Plugin 选项 plugins和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。 对于不指定选项,这些都是等同的: 123{ \"plugins\": [\"pluginA\", [\"pluginA\"], [\"pluginA\", {}]]} 要指定选项,请使用选项名称作为 key 传递对象。 1234567891011{ \"plugins\": [ [ \"transform-async-to-module-method\", { \"module\": \"bluebird\", \"method\": \"coroutine\" } ] ]} preset配置套路 preset是一个插件数组 123456789module.exports = function() { return { plugins: [ \"pluginA\", \"pluginB\", \"pluginC\", ] };} Presets 可以包含其他的 presets 以及带有选项的插件。 123456789module.exports = () => ({ presets: [ require(\"@babel/preset-env\"), ], plugins: [ [require(\"@babel/plugin-proposal-class-properties\"), { loose: true }], require(\"@babel/plugin-proposal-object-rest-spread\"), ],}); Preset 路径如果 preset 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules 中 123{ \"presets\": [\"babel-preset-myPreset\"]} 还可以指定 presets 的相对/绝对路径。 123{ \"presets\": [\"./myProject/myPreset\"]} Preset 简写(官方非常不推荐)如果包的名称以 babel-preset- 为前缀,可以使用简写: 123456{ \"presets\": [ \"myPreset\", \"babel-preset-myPreset\" // 等同 ]} 这也适用于 scoped 包: 123456{ \"presets\": [ \"@org/babel-preset-name\", \"@org/name\" // 等同 ]} 1234567891011// vue-cli生成的babel.config.jsmodule.exports = { presets: [ [ '@vue/app', // 对应 @vue/babel-preset-app,当时害的我找来找去都没找到包 { useBuiltIns: 'entry', corejs: 2, // @see https://babeljs.io/docs/en/babel-preset-env#corejs }, ],}; Preset执行顺序相反 Preset 的顺序是相反的(从最后一个到第一个). 1234567{ \"presets\": [ \"a\", \"b\", \"c\" ]} 将会按照以下顺序运行:c, b, 然后 a。 Preset 选项 插件和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。 对于不指定选项,这些都是等同的: 1234567{ \"presets\": [ \"presetA\", [\"presetA\"], [\"presetA\", {}], ]} 要指定选项,请使用选项名称作为 key 传递对象。 12345678{ \"presets\": [ [\"@babel/preset-env\", { \"loose\": true, \"modules\": false }] ]} 总结 前端的知识链太长,技术更新越来越快。前端的投资风险还是很大,所以谨慎选择框架,分配一点时间投资在业界公认的工具上也是不错的,例如:webpack, babel, vs code, scss等。这些都是现代前端工程的构建工具,所以生命周期会比某一个框架更长远。 参考babel-plugin-handbook babel-user-handbook 强制jest使用新的babel options corejs Babel 7使用总结 core-js-3-babel-and-a-look-into-the-future babel-preset-env官方仓库 npm 常用命令详解 A Beginner’s Guide to npm — the Node Package Manager","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"}]},{"title":"babel的配置的默认套路","date":"2019-07-15T05:00:00.000Z","path":"2019/07/15/learn-babel-guide/","text":"babel的配置的默认套路 我是一个标题党,这篇文章介绍一点babel的默认配置套路,总结了一点遇到的项目管理问题。 babel的bug刺激了我 现象:项目编译没有任何问题,打开页面才报错 问题现象console报错:_objectSpread is not defined 原因:根据输出的错误,我猜测是babel的问题,因为_objectSpread就是使用了新的对象展开的语法...obj,编译之后才出现的。当然这里我也调试了编译后打代码。【如果你想看编译打包后代码,介绍一篇文章给你: webpack编译vue项目生成的代码探索】 分析:通过git log快速分析package.json的变更,发现只更新过babel相关。所以实锤是babel问题。 分析git log时,我们当时做的commit message style统一的作用就发挥出来了,所有的工具链相关变更,统一都是chore开头,所以才能做到快速定位。顺便看看我们项目的commit message commit message style 场景重现:babel repl demo链接 真实原因:对象展开的语法,babel编译之后注入了_objectSpread或者_objectSpread2函数来处理,维护的小伙伴没有把所有的分支都覆盖。修复这个issues的小伙伴也挺幽默,估计也是被折腾的够呛,说了一句玩笑话I’ll now go hiding myself somewhere and I won’t touch an helper for a few months 😆 而且此bug是babel v7.5.4修复的,而且在低版本的@babel/core使用了高版本的@babel/plugin-proposal-object-rest-spread才能重现。 _objectSpread官方issues相关链接: ReferenceError: _objectSpread is not defined after update Missing helpers only throw once Fix _objectSpread2 for real 找的问题的根源了,新问题来了,我怎么更新@babel/core呢? 因为不是项目直接依赖的@babel/core,而是其它工具依赖,我更新了也不一定起作用。 介绍npm两个非常有用的命令: 查看项目中安装的package版本和依赖关系 123npm ls @babel/core`-- @vue/[email protected] `-- @babel/[email protected] 知道了依赖@babel/core是@vue/cli-plugin-babel,那升级@vue/cli-plugin-babel就行了。 查看npm仓库的package版本和依赖 1npm show @babel/core 推荐一个npm-check工具,专门用来更新依赖。 可以执行npm-check -u来进行交互式选择更新,而且还列出了官方文档,可以直接看看release log之后再决定是否更新。 babel的plugin和preset配置默认套路 本来我就比较懒,一直在计划学习的路上,基本都没落实行动。这次项目出现问题,我不得不去了解了一下,babel配置的那些套路。Babel的v7版本所有包都重命名了,所以还是要做到了解。 babel v7 官方升级文档链接 包含的内容太多,仔细过一遍才行,很多配置或者包名称命名规则都修改了。 babel v7.4支持core-js@3 babel v7.4支持core-js@3,尤其是对@babel/polyfill和@babel/preset-env又带来了很多改变。 新的corejs配置项 corejs配置项链接options corejs 当项目中的babel升级到v7.4+时会出现下面的警告: WARNING: We noticed you’re using the useBuiltIns option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the corejs option. 1234567891011121314151617181920// 使用core-js@3的babel.config.jsmodule.exports = function (api) { api.cache(true); const presets = [ [\"@babel/preset-env\", { \"useBuiltIns\": \"usage\", \"corejs\":3, // 指定版本 \"targets\":{ \"browsers\":[\"> 1%\", \"last 2 versions\", \"not ie <= 8\"] } } ] ]; return { presets, // plugins };} 注意:把相应的core-js安装到项目依赖中,npm i core-js@2 或者npm i core-js@3 @babel/polyfill不支持从core-js2升级到core-js3,所以v7.4开始@babel/polyfill变成过时(deprecated) 如果项目中使用core-js@3,则应该修改导入配置 1import \"@babel/polyfill\"; 替换为: 12import \"core-js/stable\";import \"regenerator-runtime/runtime\"; 直接安装依赖到项目: 1npm i --save core-js regenerator-runtime 因为babel和core-js绑定的很紧密,所以推荐看一下core-js作者的文章:core-js-3-babel-and-a-look-into-the-future详细介绍了core-js@3版本带来的改变,以及解决的问题。 Plugin配置套路 syntax plugins允许babel去parse特定的类型的syntax而不是去转换变形(transform) 注意:transform plugins会自动启用syntax plugins。 因此,如果已经使用了相应的transform plugins,则无需指定syntax plugins。 Plugin 路径如果 plugin 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules 中 123{ \"plugins\": [\"babel-plugin-myPlugin\"]} 还可以指定的相对/绝对路径。 123{ \"plugins\": [\"./node_modules/asdf/plugin\"]} Plugin 简写(官方非常不推荐)如果包的名称以 babel-plugin- 为前缀,可以使用简写: 123456{ \"plugins\": [ \"myPlugin\", \"babel-plugin-myPlugin\" // equivalent ]} 这也适用于 scoped 包: 123456{ \"plugins\": [ \"@org/babel-plugin-name\", \"@org/name\" // equivalent ]} Plugin执行顺序 如果两个transforms都访问“程序”节点,则transforms将以plugin或preset顺序运行。 plugin在preset之前运行。 plugin执行顺序是第一个到最后一个。 preset顺序相反(从最后到第一个)。 123{ \"plugins\": [\"transform-decorators-legacy\", \"transform-class-properties\"]} 先执行transform-decorators-legacy后transform-class-properties Plugin 选项 plugins和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。 对于不指定选项,这些都是等同的: 123{ \"plugins\": [\"pluginA\", [\"pluginA\"], [\"pluginA\", {}]]} 要指定选项,请使用选项名称作为 key 传递对象。 1234567891011{ \"plugins\": [ [ \"transform-async-to-module-method\", { \"module\": \"bluebird\", \"method\": \"coroutine\" } ] ]} preset配置套路 preset是一个插件数组 123456789module.exports = function() { return { plugins: [ \"pluginA\", \"pluginB\", \"pluginC\", ] };} Presets 可以包含其他的 presets 以及带有选项的插件。 123456789module.exports = () => ({ presets: [ require(\"@babel/preset-env\"), ], plugins: [ [require(\"@babel/plugin-proposal-class-properties\"), { loose: true }], require(\"@babel/plugin-proposal-object-rest-spread\"), ],}); Preset 路径如果 preset 在 npm 上,你可以传入预设名称,babel 将检查它是否已安装在 node_modules 中 123{ \"presets\": [\"babel-preset-myPreset\"]} 还可以指定 presets 的相对/绝对路径。 123{ \"presets\": [\"./myProject/myPreset\"]} Preset 简写(官方非常不推荐)如果包的名称以 babel-preset- 为前缀,可以使用简写: 123456{ \"presets\": [ \"myPreset\", \"babel-preset-myPreset\" // 等同 ]} 这也适用于 scoped 包: 123456{ \"presets\": [ \"@org/babel-preset-name\", \"@org/name\" // 等同 ]} 1234567891011// vue-cli生成的babel.config.jsmodule.exports = { presets: [ [ '@vue/app', // 对应 @vue/babel-preset-app,当时害的我找来找去都没找到包 { useBuiltIns: 'entry', corejs: 2, // @see https://babeljs.io/docs/en/babel-preset-env#corejs }, ],}; Preset执行顺序相反 Preset 的顺序是相反的(从最后一个到第一个). 1234567{ \"presets\": [ \"a\", \"b\", \"c\" ]} 将会按照以下顺序运行:c, b, 然后 a。 Preset 选项 插件和 presets 都可以通过将名称和选项对象放在配置中的数组中来指定选项。 对于不指定选项,这些都是等同的: 1234567{ \"presets\": [ \"presetA\", [\"presetA\"], [\"presetA\", {}], ]} 要指定选项,请使用选项名称作为 key 传递对象。 12345678{ \"presets\": [ [\"@babel/preset-env\", { \"loose\": true, \"modules\": false }] ]} 总结 前端的知识链太长,技术更新越来越快。前端的投资风险还是很大,所以谨慎选择框架,分配一点时间投资在业界公认的工具上也是不错的,例如:webpack, babel, vs code, scss等。这些都是现代前端工程的构建工具,所以生命周期会比某一个框架更长远。 参考babel-plugin-handbook babel-user-handbook 强制jest使用新的babel options corejs Babel 7使用总结 core-js-3-babel-and-a-look-into-the-future babel-preset-env官方仓库 npm 常用命令详解 A Beginner’s Guide to npm — the Node Package Manager","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"}]},{"title":"前端开发VSCode插件推荐","date":"2019-07-10T11:00:00.000Z","path":"2019/07/10/the-vscode-plugins/","text":"前端开发VSCode插件推荐 VSCode社区的繁荣发展,插件社区也是热闹非凡,插件多了选择就多了,这里推荐一下我常用的插件。我用Vue开发,所以一些插件和Vue相关。 插件VSCode相关 one dark pro主题 https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme GlassIt-VSC(毛玻璃透明效果) https://marketplace.visualstudio.com/items?itemName=s-nlf-fh.glassit vscode-icons https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons local history ( vscode拥有本地恢复的某一时刻能力) https://marketplace.visualstudio.com/items?itemName=xyz.local-history code相关 codeif(变量起名) https://marketplace.visualstudio.com/items?itemName=unbug.codelf search node_modules https://marketplace.visualstudio.com/items?itemName=jasonnutter.search-node-modules path-intellisense https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense vscode-element-helper https://marketplace.visualstudio.com/items?itemName=ElemeFE.vscode-element-helper ant-design-vue-helper https://marketplace.visualstudio.com/items?itemName=ant-design-vue.vscode-ant-design-vue-helper font awesome icons https://marketplace.visualstudio.com/items?itemName=idleberg.icon-fonts auto-close-tag https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag auto-rename-tag https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag 自动导入(import) (TypeScript & TSX) https://marketplace.visualstudio.com/items?itemName=steoates.autoimport 成对的括号自动添加颜色(bracket-pair-colorizer) https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer 颜色高亮显示 https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight npm-intellisense https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense debugger 系列 debugger for chrome https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome debugger for edge(Edge HTML & edge Chromium) https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-edge debugger for firefox https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-firefox-debug 配置文件系列 .env配置文件 编辑以.env结尾的文件. https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv 123456789// setting.json添加这么一句,所有以.env开头的全都匹配// vue下的环境配置文件命名推荐:// .env.production .env.development // .env.alpha .env.beta .env.rc{ \"files.associations\": { \".env*\": \"dotenv\" }} .editorconfig配置文件插件 https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig 代码格式化 eslint https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint htmlHint https://marketplace.visualstudio.com/items?itemName=mkaufman.HTMLHint 123456// setting.json{ \"files.associations\": { \"*.vue\": \"html\", // 我没试过,因为有vetur }} prettier https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode Beautify(prettier 选择其一) https://marketplace.visualstudio.com/items?itemName=HookyQR.beautify git相关 Git History https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory gitlens https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens Vue相关 vue-peek https://marketplace.visualstudio.com/items?itemName=dariofuzinato.vue-peek vetur https://marketplace.visualstudio.com/items?itemName=octref.vetur 辅助工具 setting sync (墙裂推荐) 如果你有多台电脑,想在不同电脑之间同步VSCode配置,就用它吧。 注意: 使用gist 时记得使用private,防止私有token泄露。 https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync todo tree https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree live server https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer jira plugin https://marketplace.visualstudio.com/items?itemName=gioboa.jira-plugin powershell https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell","tags":[{"name":"vscode","slug":"vscode","permalink":"https://betgar.github.io/tags/vscode/"}]},{"title":"前端小纠结--Event Bus模块约定","date":"2019-07-08T11:30:00.000Z","path":"2019/07/08/the-event-layer-design/","text":"前端小纠结–Event Bus模块约定 本文只是给一个针对某一类问题的处理思路。 背景标题有点大,本文产生的背景是,因为在封装Axios的时候,对于服务端的异常或者需要根据业务上异常code,进行不同的处理逻辑。但是基于Axios做的封装作为跨项目、跨框架模块使用所以不能和具体的router或者store等模块耦合,所以使用Event Bus,在整个Web范围中来解耦各个组件。 为什么使用Event Bus? Event Bus有特殊的使用场景,不止在view组件之间的通信使用;Event Bus设计作为整个SPA应用的事件(消息)投递层使用。 模块通信 解决模块之间的通信问题,view组件层面,父子组件、兄弟组件通信都可以使用event bus处理。 模块解耦 storage change事件,cookie change事件,view组件的事件等,全部转换 使用Event Bus来订阅和发布,这样就统一了整个应用不同模块之间的通信接口问题。 父子页面通信 window.postMessage + Event Bus 多页面通信 storage change + Event Bus Event Bus模块封装Event Bus接口设计 参考jQuery和Vue 方法: on, off, once, publish(可选trigger/dispatch/emit) 如果你使用vue框架,就使用Vue对象或者使用dynamic-vue-bus包(其实也是包装了下Vue,实现自动destroy handler)。 如果搞一个框架无关的,兼容性好的就基于PubSubJS 封装。 BusEvent元数据模型设计 参考DOM中的Event对象 123456789101112// 服务端返回的元数据模型(responeBody)export interface ResponseResult<T = any> { message: string; code: number; data: T;}export interface BusEvent<T = any> extends ResponseResult<T> { type: string;}export type BusEventHandler = (data: BusEvent) => any; 这里的message是参考Error来设计的,因为当我们程序异常或者业务上异常时,就可以统一直接使用new Error()进行处理。 封装实现 项目中实现选择一种即可。 基于Vue封装实现1234567891011121314151617181920212223242526272829303132333435import EventBus from 'dynamic-vue-bus';import { isFunction } from 'lodash-es';// 基于Vue实现export const VueBus = { originBus: EventBus, on(topic: string | string[], handler: BusEventHandler): string[] { // @ts-ignore this.originBus.$on(topic, handler) return []; }, off(topic: string | string[], handler?: any) { const length = arguments.length; switch (length) { case 1: this.originBus.$off(topic); break; case 2: this.originBus.$off(topic, handler); break; default: this.originBus.$off(); } }, once(topic: string, handler: BusEventHandler) { // @ts-ignore return this.originBus.$once(topic, handler); }, publish(topic: string, data: BusEvent) { return this.originBus.$emit(topic, data); },}; 使用: 12345678910111213141516171819202122232425function wsHandler(evt: BusEvent) { console.log(evt);}const topic = 'ws.100021';VueBus.on(topic, wsHandler);// VueBus.once(topic, wsHandler);VueBus.publish(topic, { code: 100021, type: 'ws', message: '', data: { result: [{ id: 1, name: 'junna' }] }});// VueBus.off();VueBus.off(topic);// VueBus.off(topic, wsHandler);// 使用原生的Vueconst eventBus = PubSubBus.originBus; 基于PubSubJS实现 以下代码并没测试 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647export const PubSubBus = { originBus: PubSub, on(topic: string | string[], handler: BusEventHandler): string[] { if (!topic) { return []; } // @ts-ignore let events: string[] = [].concat(topic); return events.map(evt => PubSub.subscribe(evt, () => (topic: string, data: BusEvent) => handler(data) ) ); }, /** * 不兼容这种模式:PubSubBus.off('ws.001', handler); * 因为PubSubJS使用token来off这种操作 */ off(tokenOrHandler?: () => void | string | string[]) { let length = arguments.length, evts: string[], listener; if (length === 0) { PubSub.clearAllSubscriptions(); } else { // PubSubBus.off(handler); if (isFunction(tokenOrHandler)) { PubSub.unsubscribe(listener); } else { // @ts-ignore evts = [].concat(tokenOrHandler); evts.forEach(evt => PubSub.unsubscribe(evt)); } } }, once(topic: string, handler: BusEventHandler) { // @ts-ignore return PubSub.subscribeOnce(topic, handler); }, publish(topic: string, data: BusEvent, sync = false) { return sync ? PubSub.publishSync(topic, data) : PubSub.publish(topic, data); },}; 使用: 1234567891011121314151617181920212223242526function wsHandler(evt: BusEvent) { console.log(evt);}const topic = 'ws.100021';const tokens: string[] = PubSubBus.on(topic, wsHandler);// PubSubBus.once(topic, wsHandler);PubSubBus.publish(topic, { code: 100021, type: 'ws', message: '', data: { result: [{ id: 1, name: 'junna' }] }});// PubSubBus.off();PubSubBus.off(topic);// PubSubBus.off(tokens[0]); // 等价于VueBus.off(topic, wsHandler);// PubSubBus.off(wsHandler); // 移除多个topic使用的同一个handler.// 使用原生的PubSubJSconst PubSub = PubSubBus.originBus; Event相关约定BusEvent#type类型约定 事件类型的约定参考: ws: WebSocket事件 storage: 储存事件, sessionStorage, localStorage cs: cache storage ap: application cache cookie: cookie事件 biz: 业务事件 system: 系统事件 ui: 界面 cmp: components事件 BusEvent#code范围约定 code范围约定只是参考,因为这个约定需要和服务端小伙伴,甚至系统设计时规划决定。(瞎写) ws: 10000~19999 storage: 20000~29999, sessionStorage: 22000~22999, localStorage: 23000~23999 cs: 24000~24999 ap: 25000~25999 cookie: 26000~26999 biz: 30000~31000 system: 40000~41000 ui: 42000~42999 cmp: 43000~44999 统一处理EventBus事件 有了约定,就可以统一发布相关的事件 123456789101112131415161718192021222324// 模拟WebSocket的消息const data = [{ code: 100021, type: 'ws', message: '', data: { result: [{ id: 1, name: 'junna' }] }},{ code: 100022, type: 'ws', message: '', data: { result: [{ id: 1, name: 'junna' }] }}];data.forEach(event => PubSubBus.publish(`${event.type}.${event.code}`), event)); 总结 通过对Event Bus的统一封装,对外提供统一的接口,统一整个SPA事件系统(非DOM层面),完成了模块之间的解耦。 缺点: 基于Vue封装实现的不支持namespace 基于Vue封装实现和PubSubJS接口参数和返回值有差异,所以选择一种即可 参考global-event-bus PubSubJS dynamic-vue-bus 让在Vue中使用的EventBus也有生命周期 Web Storage Support Test Working with quota on mobile browsers Browser Storage Abuser BrowserStorageAbuser github","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"},{"name":"vue","slug":"vue","permalink":"https://betgar.github.io/tags/vue/"}]},{"title":"看过去前端技术流行趋势,布局未来前端技术","date":"2019-07-04T12:30:00.000Z","path":"2019/07/04/look-at-past-front-end-trends-and-prepare-for-future-front-end-technologies/","text":"看过去前端技术流行趋势,布局未来前端技术 标题党,为啥?因为只是记录一下,提供前端技术流行趋势的网站资料来源。给准备进入前端领域或者刚如入门的前端小伙伴,一点大方向上的指引。 前端技术趋势资料 JavaScript社区的趋势(方方面面) https://stateofjs.com/ CSS社区的技术趋势(方方面面) https://stateofcss.com/ 每年JavaScript的明星项目 https://risingstars.js.org/ JavaScript项目的受欢迎程度(方方面面) https://bestofjs.org/ Web开发者路线图谱(Front-end, DevOps, Back-end) https://github.com/kamranahmedse/developer-roadmap 前端开发手册(指导性,每年一本) https://github.com/FrontendMasters/front-end-handbook-2019 CSS in js http://michelebertoli.github.io/css-in-js/ 未来的机会和方向(吹牛皮) 从 比较热门浏览器的API来看未来的趋势。 hot-browser-api 渐进式的Web Apps Web组件化 可视化(WebGL, 动画, 音频,实时通信, 语音, VR) 加上今年的5G开通,对于整个行业和生态链都是机会和挑战。你敢预测以后的Web或者浏览器功能有多强大吗?告诉你个消息,腾讯直播在用微信小程序做直播,很流畅。 如果很多公众号博主直接进入直播平台,那是自带流量,你说是不是机会? 如果有能力多关注这些领域: 流媒体(直播,短视频) 可视化 VR/AR IoT 图形学 Vue生态相关资源和工具列举 列举一下Vue生态的相关学习和进阶资源 Vue(jsx & render & async component) vue-cli3 & Webpack & Rollup vue-router vuex vue-i18n Sass & less CSS Modules & PostCSS lodash & moment.js (day.js) element-ui ant-design-vue nuxt.js vuepress TypeScript Babel vue-test-utils Jest & Mocha cypress 附:新手向:Vue 2.0 的建议学习顺序 吹牛皮完毕。","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"重新认识Cookie的限制","date":"2019-07-03T00:00:00.000Z","path":"2019/07/03/ie11-lost-cookie-when-domain-is-localhost/","text":"重新认识Cookie的限制 背景是在使用vue-cli3开发模式启动的服务,登录成功服务端返回了信息,并且设置了cookie,但是在IE11下却无法找到服务端设置的cookie信息,自己代码中添加的cookie反而成功了。又是潜在的坑? vue-cli3使用development模式启动的配置 vue.config.js配置 12345678910111213141516module.exports = { devServer: { proxy: { '/api': { target: 'api.com', // 开发环境 changeOrigin: true, pathRewrite: { '^/api': '/api', }, cookieDomainRewrite: { 'api.com': 'localhost', }, }, }, },}; 原因 原因就是Set-Cookie中的Domain=localhost的问题,因为规范规定domain必须是一个至少含有两个dot的,但是还不能是ip。其实呢,IE没错,错在自己不知道规范和套路。 解决方案 修改vue.config的配置,重定向给null, false, ‘’, 或者localhost.cn (etc/hosts文件里面配置下) 12345678910111213141516module.exports = { devServer: { proxy: { '/api': { target: 'api.com', // 开发环境 changeOrigin: true, pathRewrite: { '^/api': '/api', }, cookieDomainRewrite: { 'api.com': null, // '' 或 null 或 false都行 }, }, }, },}; 重新认识cookie的限制 虽然cookie浏览器都支持,但是还是有一堆的潜规则。 cookie的属性 cookie属性值和属性名称编码 所有在 cookie 名称或 cookie 值中不允许的特殊字符都使用百分比编码对每个字符的 UTF-8 十六进制等效项进行编码。Cookie 名称或 Cookie 值中允许并且仍然编码的唯一字符是百分比字符,它将转义以将百分比输入解释为文本。 IE下cookie的path不能包含文件名http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspxQ8: Are there any limits to the HTML DOM document.cookie property? 由于基础 WinINET InternetGetCookie 实现中存在一个模糊的 bug,IE 的文档.cookie 如果使用包含文件名的路径属性设置,则不会返回 Cookie。 1Set-Cookie: HTTPSet-PathCookie=PASS;path=/check.htm 这个cookie不会出现在document.cookie中,但是HTTP requests还是会附带上。 为什么我的 Cookie 被删除?https://github.com/js-cookie/js-cookie/wiki/Frequently-Asked-Questions#why-are-my-cookies-being-deleted 它们可能太大,或者同一域中的 Cookie 太多。 根据RFC 6265,这是浏览器为 Cookie 实现的规范: 实际的用户代理实现对它们可以存储的 Cookie 的数量和大小有限制。通用用户代理应提供以下每个最低功能: 每个 Cookie 至少 4096 字节(以 Cookie 的名称、值和属性的长度之和来衡量)。 每个域最多 50 个 Cookie。 总共最多3000个cookie。 不要超过 RFC 中规定的强制限制,这意味着浏览器可能会由于此大小限制而截断 Cookie。 设置两个同名的 Cookie 吗?https://github.com/js-cookie/js-cookie/issues/226 不可以,会有意想不到效果。 cookie不会被删除的情况 这个问题我真的遇到了,当时还很奇怪,又纠结了。 会话期Cookiehttps://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies#%E4%BC%9A%E8%AF%9D%E6%9C%9FCookie会话期Cookie是最简单的Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。会话期Cookie不需要指定过期时间(Expires)或者有效期(Max-Age)。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期Cookie也会被保留下来,就好像浏览器从来没有关闭一样。 注意 :如果你这样设置,会话期cookie也不会删除,所以前端一定要做好判断,这个坑我又踩了 Continue where you left off 持久性Cookie和关闭浏览器便失效的会话期Cookie不同,持久性Cookie可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。 cookie的Secure和HttpOnly属性 标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用Cookie的 Secure 标记。HttpOnly 标记的Cookie,只能在HTTP request headers里面,JavaScript不能获取。 判断当前页面是否启用cookie navigator.cookieEnabled 返回一个布尔值,来表示当前页面是否启用了 cookie。本属性为只读属性。 Cookie SameSite属性 如果所有现代浏览器默认SameSite=Strict,嘿嘿一堆应用会出问题。别人的文章Chrome默认启用Cookie Samesite属性 cookie-samesite-attribute 微软你有毛病,IE11还区分Windows版本,SameSite功能部分支持。 总结 没想到普遍支持的cookie,还有如此多的限制,恨自己的经验不足。 参考internet-explorer-11-wont-set-cookies-on-a-site understanding-session-lifetime/ cookie_spec cookies-on-localhost-with-explicit-domain cookie-domain MDN_HTTP_Access_control_CORS 阮一峰–跨域资源共享 CORS 详解 cors-tutorials preflight-request 前端常见跨域方案 Spring mvc解决跨域请求:Response to preflight request doesn’t pass access control check 什么是浏览器跨域,如何解决跨域问题,什么是 CORS?从入门到精通 Chrome默认启用Cookie Samesite属性 node-http-proxy#options HTTP Cookie and Set-Cookie header fields js-cookieFrequently-Asked-Questions#why-are-my-cookies-being-deletedInternet Explorer Cookie Internals (FAQ)Chrome doesn’t delete session cookies【译】Cookie的SameSite属性","tags":[{"name":"cookie","slug":"cookie","permalink":"https://betgar.github.io/tags/cookie/"},{"name":"ie","slug":"ie","permalink":"https://betgar.github.io/tags/ie/"}]},{"title":"自带“歧视性”的IE11 input事件","date":"2019-07-02T12:30:00.000Z","path":"2019/07/02/the-weird-input-event-in-ie11/","text":"自带“歧视性”的IE11 input事件 第一次碰到自带的歧视的bug,IE 11泥潭不解释。 起因 ant-design-vue的RangePicker在IE11下calendar panel无法正常展开,看图。 RangePicker 具体的相关讨论看参考链接中的ant-design-vue部分。 因为我知道IE和firefox、chrome下时间处理有问题(es5之前),以为是Date的问题,所以我的调试方向就走偏了,顺便介绍下Date.parse的行为,new Date(‘字符串’),内部使用Date.parse解析。 Date.parse行为诡异 参考链接部分有大量的Date知识总结,多多浏览。 Date.parse在ES5之前不同浏览器,行为差异很大。现在我只能重现一下IE下的,Chrome和firefox版本都太高,已经修正这个问题。如果想看以前的兼容性问题,看这篇博客javascript-and-dates-what-mess 12345// 控制台new Date('2019-07-02')// Tue Jul 02 2019 08:00:00 GMT+0800 (中国标准时间)new Date('2019/07/02')// Tue Jul 02 2019 00:00:00 GMT+0800 (中国标准时间) 看到区别了吗?time部分,一个从0点开始,一个从8点开始,为啥呢?看看MDN怎么说吧。 MDN文档不推荐在ES5之前使用Date.parse方法,因为字符串的解析完全取决于实现。直到至今,不同宿主在如何解析日期字符串上仍存在许多差异,因此最好还是手动解析日期字符串(在需要适应不同格式时库能起到很大帮助)。 注意:如果你要兼容IE,对日期处理,请使用专门的类库,例如:moment.js, day.js之类。 Date#toString() 顺便提一下,Date#toString()竟然有规范,看这里Date.toString,所以从Date字符串中解析提取一些字符可以放心大胆的用。以后推荐使用Intl对象是 ECMAScript 国际化 API 的一个命名空间,它提供了精确的字符串对比、数字格式化,和日期时间格式化 12345678910111213141516/** * 提取GMT时区 * @param date * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toString#Description * @returns ''或者GMT,或者GMT+0000或者GMT-0000;其中0000代表GMT偏移量,是四位前两位表示hours小时,后两位表示minuts分钟. */function extractTimezoneOffsetWithGMT(date) { if (!date) { return ''; } const dateTimeStr = date.toString(); // Thu May 23 2019 14:52:15 GMT+0800 (中国标准时间) const gmtIndexStart = dateTimeStr.indexOf('GMT'); const timezoneEnd = dateTimeStr.indexOf(' ', gmtIndexStart); return dateTimeStr.substring(gmtIndexStart, timezoneEnd);} placeholder和input事件 和ant-design-vue维护者沟通之后,得知这个bug在ant-design-vue的英文语言模式下不会有问题,维护的小伙伴,推测语言环境不同,有可能导致编译的代码不同(也有道理,现在js都上编译器,你不知道编译后代码有多大变化,当时已经提到了placeholder,我没注意)。 思路:英文版和中文版,最大的不同应该是语言文件,涉及到RangePicker只有时间格式和placeholder,时间格式已经怀疑过,因为底层使用moment库处理,所以没问题。所以只能看看placeholder兼容性。 placeholder兼容性 caniuse-placeholder 注意:看下Known issues的tab页签,看到第二条,我震惊了,为啥当placeholder为中文时会触发input事件??? input事件的兼容性 caniuse-input-event 重现IE11当placeholder=”中文”时,触发input事件 12345678910111213141516171819202122232425262728<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"> <title>placeholder</title></head><body> <div id=\"content\"> <input type=\"text\" placeholder=\"english\" id=\"en\"> <input type=\"text\" placeholder=\"中文\" id=\"cn\"> </div> <script> window.onload = function () { var enInput = document.getElementById('en'); var cnInput = document.getElementById('cn'); enInput.addEventListener('input', function (evt) { console.log('en inpu'); }); cnInput.addEventListener('input', function (evt) { console.log('cn input'); }); } </script></body></html> placeholder-fire-input-event 临时处理方案 知道了原因就很容易处理了,只要input的placeholder=””就搞定了,虽然不好看,总比有bug好。 12<!-- 重置placeholder,因为默认有语言文件,placeholder默认有文字的 --> <a-range-picker @change=\"onChange\" :placeholder=\"['', '']\"/> 到这里才找到真正造成RangePicker问题的原因,反馈给了ant-desing-vue维护小伙伴,他们下次修复上线。 总结解决问题的思路很重要,很重要,以后如果碰到IE下的问题,第一时间https://caniuse.com/网站看兼容性,被IE坑怕了,兼容IE,就是在浪费生命。 参考Date.parse 阮一峰 JavaScript标准参考教程 Date对象 javascript-and-dates-what-mess JS原生Date类型方法的一些冷知识 wxyyxc1992–时间与日期 IE11日历选择框有值的情况无法打开选择框ie11下组件DatePicker 日期选择框不显示且控制台报错IE下点击RangePicker会闪一下关闭,openChange执行3次","tags":[{"name":"ie","slug":"ie","permalink":"https://betgar.github.io/tags/ie/"},{"name":"input","slug":"input","permalink":"https://betgar.github.io/tags/input/"}]},{"title":"前端小纠结--IE11下SVG元素默认focusable=true","date":"2019-07-02T04:00:00.000Z","path":"2019/07/02/svg-elements-default-focusable-in-ie11/","text":"IE11下SVG元素默认focusable=true 本来我也不太关注一些交互的问题,但是在处理IE11下的svg能够focus(就是tab时候能够获取焦点)时候,发现原来tabindex也是大坑。每一次兼容性问题背后,都是有相关问题的好几个标准,一个标准还有好几个版本,不同的时期的浏览器,实现的又有差异,只要有一个浏览器和其它浏览器行为不同,就会被定义为你有bug。PS: 最近搞兼容性好累。 tabindex值的范围 使用之前先看看这篇文章dont-use-tabindex-greater-than-0 tabindex 的最大值不应超过 32767。如果没有指定,它的默认值为 -1。 还有一个好玩的z-index属性,真可怕。 具体取决于整数的值: tabindex=负值 (通常是tabindex=“-1”),表示元素是可聚焦的,但是不能通过键盘导航来访问到该元素,用JS做页面小组件内部键盘导航的时候非常有用。 tabindex="0" ,表示元素是可聚焦的,并且可以通过键盘导航来聚焦到该元素,它的相对顺序是当前处于的DOM结构来决定的。 tabindex=正值,表示元素是可聚焦的,并且可以通过键盘导航来访问到该元素;它的相对顺序按照tabindex 的数值递增而滞后获焦。如果多个元素拥有相同的 tabindex,它们的相对顺序按照他们在当前DOM中的先后顺序决定。 根据键盘序列导航的顺序,值为 0 、非法值、或者没有 tabindex 值的元素应该放置在 tabindex 值为正值的元素后面。 element的tabbable和focusable 元素还分为focusable和非focusable,如果使用了tabindex就可以改变相关的行为,看参考链接的资料,这里做一下记录,方便以后回顾。 focusable和tabbable的相关解释 不翻译了,直接上链接what-is-focusable focusable的兼容性表格 兼容性表格,看着兼容性表格还是比较喜人。 focusable-table IE11下SVG元素默认可以focusable IE11下有一个奇怪的现象,就是svg默认可以获取焦点(focus),尤其是在登录界面,input前后有prefix和suffix时,尤其别扭。官方一直到EdgeHTML时代的某个版本才修复,看这里microsoft-edge Support tabindex in SVG, don’t make every focusable by default 问题的原因 IE 11下的svg元素默认是focusable的,也就是说其它浏览器svg元素默认是不能获取焦点的,这就造成了,当input有prefix和suffix的svg时,tab进行表单输入切换就很麻烦,被认为是bug。 例如: 123456<form> <svg></svg> <input type=\"text\"/> <svg></svg> <input type=\"text\"/></form> 解决方案 解决方案也很简单 1<svg focusable=\"false\"><svg> 如果全局很多svg就很难受了,所以推荐使用ally.js解决。 ant-design组件库 因为工作中使用的是ant-design-vue所以顺便了解了下,发现 ant-design的已经解决了这个事情看ant-design-icons Set svg default focusable=false 这里。 参考ally.js - making accessibility simpler focusable table focusable-test how-to-prevent-svg-elements-from-gaining-focus-with-tabs-in-ie11 disable-onfocus-event-for-svg-element microsoft-edge Support tabindex in SVG, don’t make every focusable by default Focus, tabIndex and behavior of browsers ant-design-icons Set svg default focusable=false tabindex attributes","tags":[{"name":"ie","slug":"ie","permalink":"https://betgar.github.io/tags/ie/"},{"name":"focus","slug":"focus","permalink":"https://betgar.github.io/tags/focus/"}]},{"title":"前端小纠结--Browser&WebView调试工具和文档整理","date":"2019-06-29T01:20:00.000Z","path":"2019/06/29/the-browser-debugger-documents-and-tools-collect/","text":"前端小纠结–Browser&WebView调试工具和文档整理 为什么又这篇文章呢?因为最近在做IE 11的兼容性测试,の有生之年还会做IE 适配,我没想到。 期间遇到了一个小问题,没想到竟然没搞定。这么简单,我竟然搞不定…..我对自己的经验和技术水平产生了怀疑,所以决定整理一下,浏览器调试方面的工具和资料。 调试协议(devtools-protocol) 首先说我知道的,远程调试协议(devtools-protocol)很重要,因为只要浏览器或者webview实现了调试协议,我们不管用什么工具都可以方便的调试,好像目前这方面没有标准, 如果有大家告诉我,chrome-devtools-protocol做的最好。 chrome-devtools-protocol WICG-devtools-protocol remotedebug 官网提供了很多不同浏览器调试协议的Protocol Adaptors和integrations,可以在这里看到兼容性compatibility VS Code debugger plugins 因为VS Code的流行,所以诞生了一个针对调试的debugger for xxx系列的插件。很多插件还是Microsoft自己开发的,微软YES. Debugger-for-edge 支持EdgeHTML和Chromium版本 Debugger-for-chrome Debugger for Firefox ios-webkit-adapter-Safari RemoteDebug出品,微软在vscode-ios-web-debug上推荐. vscode官方列表 有一些已经过时,并且有推荐的替代的地址。 VS Code Chrome Debugger VS Code iOS Web Debugger VS Code PhantomJS Debugger VS Code Node Debugger VS Code Edge Debugger VS Code NWjs Debugger VS Code Cordova VS Code NativeScript debugger Visual Studio 2017 Update 2 Node.js Debugger ChromeChrome没啥介绍的调试工具官方做的最好,没有之一。 Firefox firebug 以前做的非常好的工具,以至于最后直接集成到firefox内部。 IE 介绍一个IE 11版本的,其它低于IE 11的我推荐你使用google chrome frame,虽然谷歌已经在2013停止维护了,但是对于低于IE 11的还是有很大的帮助的。微软官方提供的IEDiagnosticsAdapter,虽然现在已经停止维护了,但是它最后更新在2015年,IE 11好像是2013年发布的(当时我在上大学…..),实现了chrome-devtools-protocol v1.1,我特意看了一下现在协议发展到v1.3,所以可以使用chrome devtools调试IE,本来很大希望,后来发现就是鸡肋,哎,聊胜于无了。 低于IE 11版本 using-firebug-to-inspect-element-in-internet-explorer debugbar IEDiagnosticsAdapter 官方的介绍工具的博客文章:introducing-the-ie-diagnostics-adapter-for-third-party-developer-tools 但是我亲自试了一下发现效果没这么好,查看DOM元素已经没有互动效果了,难道我的Chrome版本太高了???Chrome v75,可能Chrome版本太高,devtools协议版本较高,有机会找个低版本Chrome尝试下。 测试的结果 VS Code调试IE 11尝试 我思考,既然IE Diagnostics Adapter实现了devtools protocol v1.1,做了代理转发,debugger for chrome也是实现的devtools protocol ,我是否可以使用VS Code的debug模式,开启chrome attach模式试一下呢? 思路:VS Code –>debugger for chrome –> attach port 9222 –> IE Diagnostics Adapter –> IE 11 结论:VS Code 通过debugger for chrome的attach模式,成功链接。但是就成功一次,其余的尝试导致IE Diagnostics Adapter一直奔溃。虽然失败了,但是我还是很高兴,因为至少说明我的思路是正确的,解决问题的时候,思路(方向)最重要,要是方向搞错了,那可要浪费很多精力咯。:) 总结 折腾了大半天,还是有点收获,最少收获了解决问题的思路和VS Code的debugger系列。希望这篇文章对还在和IE 11互相折磨的同行有点帮助。 关注公众号,发现更多精彩内容。 参考chrome-devtools-protocol remotedebug vscode-chrome-debug-core WICG-devtools-protocol vscode-chrome-debug-core IEDiagnosticsAdapter attach-vs-code-debugger-to-internet-explorer ie-developer how-to-attach-vs-code-debugger-to-internet-explorer-11","tags":[{"name":"vue","slug":"vue","permalink":"https://betgar.github.io/tags/vue/"},{"name":"debug","slug":"debug","permalink":"https://betgar.github.io/tags/debug/"}]},{"title":"vue-devtools在IE11中的使用","date":"2019-06-27T11:00:00.000Z","path":"2019/06/27/vue-remote-devtools/","text":"vue-devtools在IE11中的使用 IE下的调试一直都很头痛,本来vue官方没有提供ie下的开发工具插件,后来还是不死心去官网看了下,结果有了意外的收获。 vue官方独立的vue-devtools应用程序,可用于调试任何Vue应用程序,无论环境如何。 现在,您可以调试在移动浏览器,Safari,本机脚本等中打开的应用程序,而不仅仅是桌面chrome或firefox。 查看IE下Vue组件状态 文档中已经提到了怎么在开发环境使用了,现在使用vue-cli3进行根据环境变量来差异化编译index.html模板。参考HTML和静态文件章节) index.html文件添加标签 最简单的修改: 1<script src=\"http://localhost:8089\"></script> 为了实现差异化编译(development生成,production不生成调试script)可以这样做: 1234567891011<!-- /public/index.html --><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\"> <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\"> <% if (VUE_APP_REMOTE_DEBUG_ENABLE === 'true') { %> <script src=\"<%= VUE_APP_REMOTE_DEBUG_HOST + ':' + VUE_APP_REMOTE_DEBUG_PORT%>\"> </script> <% } %> <title>debug</title> </head> 1234# 环境变量文件 .envVUE_APP_REMOTE_DEBUG_ENABLE=falseVUE_APP_REMOTE_DEBUG_HOST=http://localhostVUE_APP_REMOTE_DEBUG_PORT=8089 12# .env.developmentVUE_APP_REMOTE_DEBUG_ENABLE=true 效果图: vue-devtools查看IE 11下Vue组件的状态 原理猜想 工具原理的猜想是使用WebSocket链接,把vue component的状态序列化发送到host:8089端口,本地启动的devtools在8089进行实时的解析显示。 以下是官方README翻译,可以略过 💿安装全局安装包: 1npm install -g @vue/devtools 或在本地作为项目依赖: 1npm install --save-dev @vue/devtools 🚀用法使用全局包在全局安装软件包后,运行: 1vue-devtools 然后加: 或者,如果要远程调试设备: window . VUE_DEVTOOLS_HOST =’‘//默认值:localhost window . VUE_DEVTOOLS_PORT =’‘//默认值:8098 到您应用的部分。 (在部署到生产之前不要忘记将其删除!) 通常如下所示:192.168.x.x. 作为依赖安装将程序包安装为项目依赖项后,运行: 1./node_modules/.bin/vue-devtools 您还可以使用全局vue-devtools来启动应用程序,但您可能需要检查本地版本是否与此方案中的全局版本匹配,以避免任何不兼容性。 然后直接在您的应用中导入它: 12import devtools from '@vue/devtools'// import Vue from 'vue' 确保在Vue之前导入devtools,否则它可能无法按预期工作。 并连接到主机: 123if (process.env.NODE_ENV === 'development') { devtools.connect(/* host, port */)} host - 是一个可选参数,它告诉应用程序devtools中间件服务器正在运行的位置,如果你在计算机上调试app,则不必设置它(默认为http:// localhost),但是如果要调试您的移动设备上的应用,您可能想要传递本地IP(例如192.168.1.12)。 port - 是一个可选参数,它告诉应用程序devtools中间件服务器正在运行的端口。如果使用代理服务器,则可能需要将其设置为null,以便端口不会添加到连接URL。 常问问题: 如何更改正在运行devtools服务器端口? 您可以在运行之前通过设置环境变量来更改它: 1PORT=8000 vue-devtools 然后在您的应用程序中,您将必须设置: 1window.__ VUE_DEVTOOLS_PORT__=8000 或者使用新端口更新connect方法: 1devtools.connect(/ * host * /,8000) 如何远程检查(inspect)服务器上的页面? 为此,您可以使用`ngrok`代理。您可以在这里下载。 一旦你启动`vue-devtools`运行: 1ngrok http 8098 然后相应地更新您的主机和端口: 1devtools.connect('example.ngrok.io',null) 确保将port设置为null或false,因为ngrok主机已经代理到我们在第一个命令中定义的正确端口。 如何检查通过HTTPS提供的页面? 为此,您还可以使用ngrok,因为它会自动将https请求代理到http。请查看问题2以获取说明。 参考调试工具官方仓库","tags":[{"name":"vue","slug":"vue","permalink":"https://betgar.github.io/tags/vue/"},{"name":"debug","slug":"debug","permalink":"https://betgar.github.io/tags/debug/"}]},{"title":"VS Code调试配置分享","date":"2019-06-26T04:00:00.000Z","path":"2019/06/26/vscode-debug-configuration/","text":"VS Code调试配置分享 这不是一篇科普文,只是一篇浏览器调试配置的分享(主要对准对准vs code调试) 调试配置 使用vs code插件debugger-for-chrome和debugger-for-edge(EdgeHTML&Chromium)调试之前,会自动在工程下添加.vscode文件夹,里面的launch.json就是配置调试参数的位置。 launch.json 很多的配置参数,具体看参考章节的nodejs-debugging和debugging-protocol。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 \"version\": \"0.2.0\", \"configurations\": [ { \"type\": \"chrome\", \"request\": \"launch\", \"name\": \"vuejs: launch chrome\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}/src\", \"userDataDir\": false, \"breakOnLoad\": true, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"chrome\", \"request\": \"attach\", \"name\": \"vuejs: attach chrome\", \"webRoot\": \"${workspaceFolder}/src\", \"port\": 9222, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"edge\", \"request\": \"launch\", \"name\": \"vuejs: launch EdgeHTML\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}/src\", \"userDataDir\": false, \"breakOnLoad\": true, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"edge\", \"request\": \"attach\", \"name\": \"vuejs: attach EdgeHTML\", \"webRoot\": \"${workspaceFolder}/src\", \"port\": 2015, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"edge\", \"request\": \"attach\", \"version\": \"dev\", // dev, beta, or canary \"name\": \"vuejs: attach Edge(Chromium)\", \"webRoot\": \"${workspaceFolder}/src\", \"port\": 9223, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"edge\", \"request\": \"launch\", \"version\": \"dev\", \"name\": \"vuejs: launch Edge(Chromium)\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}/src\", \"userDataDir\": false, \"breakOnLoad\": true, \"sourceMapPathOverrides\": { \"webpack:///./src/*\": \"${webRoot}/*\" } }, { \"type\": \"firefox\", \"request\": \"launch\", \"name\": \"vuejs: firefox\", \"url\": \"http://localhost:8080\", \"webRoot\": \"${workspaceFolder}/src\", \"pathMappings\": [{ \"url\": \"webpack:///src/\", \"path\": \"${webRoot}/\" }] } ]} launch模式 launch模式就是重新打开一个浏览器实例(不是tab)。 attach模式 attach模式,是附加到现在已经打开的浏览器调试端口上,所以需要你在已经打开的浏览器中访问launch.json中配置的网站地址。 attach模式的tab窗口选择 chrome配置 windows chrome配置远程调试端口 mac 终端执行: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222 Linux 终端执行: google-chrome --remote-debugging-port=9222 Edge(Chromium) windows 命令行:msedge.exe --remote-debugging-port=2015 mac和Liunx现在官方没说怎么调试….. Microsoft Edge (EdgeHTML) windows 命令行: microsoftedge.exe --devtools-server-port=2015 竟然不能快捷方式配置,不科学。 个人建议 个人建议平时开发,把端口配置到快捷方式,使用attach模式可以共享使用已经安装的浏览器插件。 具体还有很多参数配置,可以参考官方文档。 参考debugger-for-chrome debugger-for-edge vscode-chrome-debug-github vscode-recipes devtools-protocol-chromium devtools-protocol-edge nodejs-debugging","tags":[{"name":"vscode","slug":"vscode","permalink":"https://betgar.github.io/tags/vscode/"}]},{"title":"前端小纠结--common模块设计","date":"2019-06-20T10:00:00.000Z","path":"2019/06/20/frontend-little-tangle-common-module-design/","text":"前端小纠结–common模块设计承接上一篇前端小纠结–Vue项目代码组织和风格约定 common模块的单独拿出来说一下,因为common模块设计的好,可以跨项目使用,甚至在部署成自己的私有的package。 common目标:主要是为了在第三方库之上做一层简单的封装,预置配置,做一层隔离,也可以在调用第三方组件时做一些埋点统一处理一些逻辑。 设计原则 不能有任何的业务逻辑 不能耦合工程的业务状态 不能依赖组件库,可以依赖独立的UI组件(例如: nprogress) 可以依赖vue框架,不能依赖router实例和vuex实例组件(如果依赖就说明耦合业务) common参考结构 这个是我使用的common层的目录结构,仅供参考。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162src/common├── ajax│ ├── Ajax.ts│ ├── AjaxConfig.ts│ ├── AjaxUtils.ts│ ├── interceptors│ │ ├── BackendInterceptor.ts│ │ ├── CacheInterceptor.ts│ │ ├── Interceptors.ts│ │ └── LoadingInterceptor.ts│ └── transformer│ ├── RequestTransformer.ts│ ├── ResponseTransformer.ts│ └── Transformers.ts├── axios│ ├── axios.ts│ └── index.d.ts├── cookie│ └── Cookies.ts├── datetime│ └── Datetime.ts├── day│ └── day.ts├── downloader│ └── Downloader.ts├── event-bus│ ├── EventBus.ts│ └── index.d.ts├── http│ └── Http.ts├── index.ts├── localforage│ └── Localforage.ts├── logger│ └── Logger.ts├── numberal│ └── numberal.ts├── pager│ └── Pager.ts├── patterns│ ├── numeric.ts│ ├── patterns.ts│ └── strings.ts├── storage│ ├── index.d.ts│ └── Storage.ts├── text│ ├── formatter│ │ └── formatter.ts│ └── Text.ts├── token│ └── Token.ts├── utils│ └── Utils.ts└── validation ├── email │ └── EmailValidator.ts ├── numeric │ └── NumericValidator.ts ├── phone │ └── PhoneValidator.ts └── Validation.ts 封装实践示例示例:根据运行环境调整组件配置参数 logger模块简单封装,根据部署环境调整日志级别。 1234567891011121314151617181920import Logger from 'js-logger';Logger.useDefaults();// 可以发送到日志服务器,自己搭建个sentry日志服务// https://sentry.io/for/javascript/Logger.setHandler(function (messages, context) { // Send messages to a custom logging endpoint for analysis. // TODO: Add some security? (nah, you worry too much! :P) jQuery.post('/logs', { message: messages[0], level: context.level });});// 调整日志级别if (process.env.NODE_ENV === 'production') { Logger.setLevel(Logger.ERROR);} else { Logger.setLevel(Logger.DEBUG);}export default Logger; 示例:按需添加依赖 示例按需添加:很多组件内部也分成多个子组件,而且都是可以按需添加 1234567891011121314// storejsconst store: StoreJsAPI = require('store/dist/store.modern.min');const expirePlugin = require('store/plugins/expire');const eventsPlugin = require('store/plugins/events');const updatePlugin = require('store/plugins/update');// const observePlugin = require('store/plugins/observe');// 添加插件.store.addPlugin(expirePlugin);store.addPlugin(eventsPlugin);store.addPlugin(updatePlugin);// store.addPlugin(observePlugin);export default store; 示例:装饰原有接口,增强功能 cookies和storage这两个模块也做一些简单封装,可以做到,对写在客户端可见的数据进行编码加密,增强安全性,提高解析门槛。 进行统一封装,自动加解密 封装clear和remove方法,添加白名单 当退出网站或者关闭网页时,进行清理时可以排除白名单属性。 示例: 123456789101112131415161718import Cookies, { CookieAttributes } from 'js-cookie';const NodeRSA = require('node-rsa');const rsaKey = new NodeRSA({b: 512});const cookieGet = Cookies.get;const cookieSet = Cookies.set;// @ts-ignoreCookies.get = function (key: string): string | undefined { return rsaKey.decrypt(cookieGet(key), 'base64');}Cookies.set = function (name: string, value: any, options?: CookieAttributes) { cookieSet(name, rsaKey.encrypt(value, 'base64'));}export const cookies = Cookies;","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"前端小纠结--Vue项目代码组织和风格约定","date":"2019-06-12T10:00:00.000Z","path":"2019/06/12/frontend-little-tangle-vue-project-code-organization-conventional-style-guideline/","text":"前端小纠结–Vue项目代码组织和风格约定 风格约定但不限于代码风格,还有一些其他的默认约定。 代码组织和分层 代码组织是一个仁者见仁,智者见智的话题,没有银弹。不过不管怎么变化,指导思想还是不变的高内聚,低耦合。 强烈推荐两篇文章,能够拓宽你的视野,带你走向新高度。 用 Feature First 的方式管理前端项目复杂度 代码组织的优雅,模块化才能够做好。 分层 按照职能的不同进行不同维度进行层级划分,层级划分之后,进行进一步的模块划分(原则上,每一个文件夹都是一个模块) 文件夹和文件命名 选择适合自己的风格。 文件夹和文件都使用kebab-case kebab-case重度使用者可以选择这种。 文件夹使用kebab-case, 文件使用Pascal Case 建议使用这种。 12文件夹:event-bus文件:EventBus.ts 例外 index文件不受上述约束 工具自动生成的文件(自己考虑是否受约束) 模块化原则 模块化代码首先要做到代码的分层、隔离、抽象。 不同模块完成不同的职能,不同职能之间相互协作。 每个模块保持一个入口和出口 对于外部模块来说,尽量保证一个入口 对于内部子模块来说,尽量保证一个出口 如果按照文件夹作为模块界限,每个文件夹下都有一个出口(可以默认为index文件) 模块的入口名称默认index或者文件夹名字的文件 123456// 例如: group文件夹group/|---index.ts // A. 默认作为入口|---group.ts // B. 也可以默认作为入口二者任选其一就好,A方案应该是大家默认的方案;B方案,检索代码的时候更方便 模块内部分层 模块内部还可以有base, common, components, helper,utils,filter,config等层级(词穷了…..) import和export原则 import导入,指定到文件 指定到文件能够提高编译打包的速度 12// 指定到index文件import { Logger } from './common/index'; common模块 独立文章说明。 router模块 router模块的风格约定。 模块结构123456789router├── helper│ ├── ImportRoute.ts│ └── RouteGenerator.ts├── modules│ ├── AboutRoutes.ts│ └── HomeRoutes.ts├── router.ts└── Routes.ts helper: 帮助工具方法 modules: 不同的业务模块 router: vue-router初始化的地方,也是模块入口 Routes: RouteConfig的出口,其它模块都从这里获取route配置,从而达到解耦的目的,尤其是不同的views里面的路由跳转,使用Routes配置达到解耦的目的。 例子: 123456import { HomeRoute } from 'Routes';// 跳转,这样没有硬编码任何的route信息,全部都是从Routes配置来,达到解耦的目的。this.$router.push({ name: HomeRoute.name}) modules子模块 文件名:views下文件夹名(模块名) + Routes结尾 modules下的文件,最好和views下文件夹一一对应,方便维护(对模块切分有较高的理解) 123456789例如:views├── group│ ├── xxx1.vue│ └── xxx2.vue├── report│ ├── xxx3.vue│ └── xxx4.vue├── Home.vue modules对应的就是 123456例如:router├── modules│ ├── GroupRoutes.ts| ├── ReportRoutes.ts│ └── HomeRoutes.ts 导出模块配置: 123456789// HomeRoutes.tsexport const HomeRoute = { path: '/', name: 'HomeRoute', component: 'Home',};// 必须导出一个数组,因为是一个模块的配置信息,可能有多个配置,还可以进行配置层级关系export default [HomeRoute]; 注意: 这只是个思路,具体的操作还要灵活运用。 RouteConfig风格约定 RouteConfig变量名 vue文件名+Route结尾 1234567// 文件Home.vue// 变量名HomeRouteexport const HomeRoute = { path: '/', name: 'HomeRoute', component: 'Home',}; name属性 和RouteConfig变量名保持一致 component属性 如果异步加载,component需要使用相对于views的path格式,因为在ImportRoute中统一处理。 12345// ImportRoute.ts统一处理export function importRoute(file: string) { // @see https://github.com/webpack/webpack/issues/1949 return () => import(/* webpackChunkName: \"chunk-[request][index]\" */ '@/views/' + file + '.vue');} path属性 没有最佳实践,最好使用restful风格约束 如果使用route.query等之类的参数传递,面包屑导航很难处理。 meta属性 没有最佳实践,多数情况下有这么几个属性 1234567891011121314151617181920212223export const HomeRoute = { path: '/', name: 'HomeRoute', component: 'Home', // component: () => import('@/views/Home.vue') meta: { // title的值可以为`i18n`的语言文件key,方便做国际化 title: '首页', // 作为menu.title和breadcrumb.title备选 icon: '', // icon的class,作为menu.icon和breadcrumb.icon备选 menu: { title: '首页', visible: true, icon: '', // icon的class }, breadcrumb: { title: '路径名', visible: false, // 有的时候不需要在面包屑上渲染 icon: '', // icon的class }, auth: { roles: [1, 2, 3] } }}; props属性 路由组件传参,更多高级用法,请查看例子 使用props方式把components和$route解耦,这样components既可以单独使用,也可以当作子组件使用,而且方便测试。 特殊场景可以不使用props,例如本来就不是通用的组件,是需要组合在一起使用的父子组件,是可以和route耦合的。 如果 props 被设置为 true,route.params 将会被设置为组件属性。 123456789// 函数式(动态)const router = new VueRouter({ routes: [{ path: '/search', component: SearchUser, // route是SearchUser内部的this.$route props: (route) => ({ query: route.query.q }) }]}) 12345678// 静态const router = new VueRouter({ routes: [{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }]}) store模块 vue应用的状态模块 模块结构 参考官方文件结构 官方购物车例子 123456789store├── StoreTypes.ts # actions mutations getters类型├── Actions.ts # 根级别的 action├── Mutations.ts # 根级别的 Mutations├── Getters.ts # 根级别的 getters├── modules # 模块│ ├── xxxStore.ts # 子模块│ ├── SystemStore.ts # SystemStore子模块├── index.ts # 入口 index: 作为入口 StoreTypes作为类型的常量文件 参考使用常量替代-mutation-事件类型 所以mutation和action的类型全部使用常量,方便其它模块和store模块的解耦。 modules子模块 文件名:模块名 + Store结尾 modules下的业务store,最好和views下文件夹对应,方便维护 1234# 例子: LocaleStore.ts # i18n模块LoginStore.ts # 登录模块UserStore.ts # 用户模块 Store module约定 参考Store Module 主要约定module内部代码结构大致如下: SystemStore为例: 需要使用前缀的地方使用模块名作为前缀 例子中模块名为System 约定声明大致顺序如下:除state部分外其他都是可选 state声明(例如:systemState) getter types声明(可选,灵活运用) getters声明(可选, 灵活运用) mutation types 声明(可选) mutations声明(可选) action types声明(可选) actions声明(可选) store options导出 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// 例子: SystemStore.ts// 声明stateconst systemState: SystemState = { initialized: false,};// 声明GetterTypes export const SystemGetterTypes = { IS_SYSTEM_INITIALIZED: 'IS_SYSTEM_INITIALIZED',};// 声明gettersconst getters = { [SystemGetterTypes.IS_SYSTEM_INITIALIZED](state: SystemState, getters: any, rootState: any){ return state.initialized; }};// 声明MutationTypesexport const SystemMutationTypes = { SYSTEM_SET_INITIALIZED: 'SYSTEM_SET_INITIALIZED',};// 声明mutationsconst mutations: MutationTree<SystemState> = { [SystemMutationTypes.SYSTEM_SET_INITIALIZED]: ( state: SystemState, payload: boolean ) => { state.initialized = payload; logger.log('system-store.initialized: ' + payload); },};// 声明ActionTypesexport const SystemActionTypes = { SYSTEM_UPDATE_INITIALIZED: 'SYSTEM_UPDATE_INITIALIZED', SYSTEM_RESET: 'SYSTEM_RESET', SYSTEM_INIT: 'SYSTEM_INIT',};// 声明actionsconst actions = { [SystemActionTypes.SYSTEM_UPDATE_INITIALIZED]: ( { commit }: ActionContext<SystemState, any>, initialized: boolean ) => { commit(SystemMutationTypes.SYSTEM_SET_INITIALIZED, initialized); }, [SystemActionTypes.SYSTEM_RESET]: ({ commit, dispatch, }: ActionContext<SystemState, any>) => { // 清空所有使用store储存的数据. }, [SystemActionTypes.SYSTEM_INIT]: ( { commit, dispatch }: ActionContext<SystemState, any>, payload: { user: UserModel; userCookie: UserCookie; } ) => { // 初始化数据 },};// 导出storeOptionsconst storeOptions = { state: systemState, getters, mutations, actions,};export default storeOptions; 注意:其中的action types, mutation types和 getter types 不强制要求,在es6环境中使用官方的mapState,mapGetters, mapActions, mapMutations工具函数更方便。 api模块 api模块是接口服务层,主要做一些对参数的转换处理,同时解耦其它业务层。 模块结构 模块的大致参考结构 123456api├── api.ts # 入口└── modules # 子模块 ├── DictionaryService.ts # 具体的业务模块 ├── GroupService.ts ├── HistoryService.ts service module约定 文件名:模块名 + Service结尾 modules下的业务service,和views下文件夹对应,方便维护 如果单个service文件对应的业务模块接口太多,可以使用文件夹来进一步分割。 1234567891011//例子:LoginService// 声明url,导出url方便mock模块使用export const GET_SIGN_IN = '/api/login';// 声明service函数export function signIn(data: {account: string; pass: string}) { return ajax({ url: GET_SIGN_IN, data, method: 'post' });} URL前缀约定: 查询:使用GET作为前缀(特殊情况例外) 更新:使用UPDATE 新增:使用ADD 删除:使用DELETE 其它:EXPORT,IMPORT,UPLOAD,DOWNLOAD等 service 函数名前缀约定: 对于单个实体可以考虑使用get, add, update, delete作为前缀 i18n模块 主要是建议下代码的分割的约定。 模块结构12345678910i18n├── i18n.ts├── index.d.ts└── locales ├── en_US.js ├── modules │ └── actions │ ├── en_US.js │ └── zh_CN.js └── zh_CN.js 语言文件组织 语言模块的组织,主要按照模块(文件夹层次)来组织,能够最小的减少冲突可能性。 语言文件约定: 方言文件放置在locals文件夹下 文件以方言编码命名 层级组织的例子: 123456789101112131415views├── account│ ├── Account.vue│ ├── locales│ │ ├── en_US.js│ │ └── zh_CN.js│ └── XXX.vue├── feedback│ ├── locales│ │ ├── en_US.js│ │ └── zh_CN.js│ └── Suggestion.vue├── locales│ │ ├── en_US.js│ │ └── zh_CN.js 123456789// views/feedback/locales/zh_CN.jsexport const feedbackModule = { // 语言模块属性:模块名+module结尾 // 可选的方式就是:严格按照文件夹层次来构造属性层级(缺点取属性值时太长,后期可以使用__dirname自动生成) label: '您的意见:', textarea: { placeholder: '请写下您的意见与建议, 500字符以内' } }; 123456789101112// views/locales/zh_CN.jsimport feedbackModule from '../feedback/locales/zh_CN.js';export default { // 如果模块众多建议加上views // views: { // ...feedbackModule, // } // 如果模块少,直接 feedbackModule,}; 123456// i18n/locales/zh_CN.jsimport viewsLocales from '../../views/locales/zh_CN';export default { ...viewsLocales} mock模块 mock模块主要做些数据模拟的工作 模块结构 子模块的大致参考结构 1234mock├── mock.js # 入口└── modules # 子模块 ├── LoginMock.js # 具体的业务模块 mock module约定 文件名:模块名 + mock结尾 modules下的mock文件,和api模块下文件对应,方便维护 方法约定: mock模块方法和service请求方法对应 例如:LoginService的signIn mock模块对应也叫signIn mock module需要提供setup方法 setup方法供外部统一调用,作为mock module的开关 例子: 123456789101112131415161718// LoginMock.jsimport Mock from 'mockjs';import { genSuccessResult, genFailResult } from './mock-utils';import { GET_SIGN_IN } from '../api/api';import Cookies from 'js-cookie';const signIn = (data) => { Cookies.set('SESSIONID', 'mock SESSIONID'); return genSuccessResult({ msg: '登陆成功', });} export default { setup() { Mock.mock(GET_SIGN_IN, 'post', signIn); }} 123456// mock.jsimport loginMocker from './modules/login';export const start = function() { loginMocker.setup();}; 12345// main.jsimport mocker from './mock/mock'if (process.env.NODE_ENV === 'development') { mocker.start();} 其它模块 其它模块如filters,directives,test目前没有总结,以后补充。","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"windows 10 锁屏壁纸保存方法","date":"2019-06-12T01:00:00.000Z","path":"2019/06/12/save-the-windows-spotlight/","text":"分享必应壁纸windows 10 锁屏壁纸保存方法 windows 锁屏壁纸功能叫做spotlight,其实壁纸都下载在本地电脑上,只需要拷贝出来就行了。 脚本 保存为 spotlight.ps1 ,使用右键powershell运行 在个人文件夹的Pictures下有两个Horizontal和Vertical保存着图片 12345678910111213141516171819add-type -AssemblyName System.DrawingNew-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\" -ItemType directory -Force;New-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\\CopyAssets\" -ItemType directory -Force;New-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\\Horizontal\" -ItemType directory -Force;New-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\\Vertical\" -ItemType directory -Force;foreach($file in (Get-Item \"$($env:LOCALAPPDATA)\\Packages\\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\\LocalState\\Assets\\*\")){ if ((Get-Item $file).length -lt 100kb) { continue } Copy-Item $file.FullName \"$($env:USERPROFILE)\\Pictures\\Spotlight\\CopyAssets\\$($file.Name).jpg\";}foreach($newfile in (Get-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\\CopyAssets\\*\")){ $image = New-Object -comObject WIA.ImageFile; $image.LoadFile($newfile.FullName); if($image.Width.ToString() -eq \"1920\"){ Move-Item $newfile.FullName \"$($env:USERPROFILE)\\Pictures\\Spotlight\\Horizontal\" -Force; } elseif($image.Width.ToString() -eq \"1080\"){ Move-Item $newfile.FullName \"$($env:USERPROFILE)\\Pictures\\Spotlight\\Vertical\" -Force; }}Remove-Item \"$($env:USERPROFILE)\\Pictures\\Spotlight\\CopyAssets\\*\"; 必应缤纷桌面 微软出品必应缤纷桌面 收集必应壁纸的网站","tags":[{"name":"windows","slug":"windows","permalink":"https://betgar.github.io/tags/windows/"}]},{"title":"nvs使用的注意事项","date":"2019-06-05T16:00:00.000Z","path":"2019/06/06/nvs-usage-considerations/","text":"nvs使用的注意事项 在使用nvs之前一直使用的是nvm windows这款工具,用着也挺爽的,前不久看到科普文,所以也想试试,试了之后在windows上翻车了,所以这里记录一下。先看科普文:使用 nvs 管理本地 Node.js 版本 windows下安装 推荐cmd手动安装步骤 git bash环境配置1234# ~/.bashrc 添加下面的环境配置# 根据你安装的路径选择$ProgramData 或者 $LOCALAPPDATAexport NVS_HOME=$LOCALAPPDATA/nvs. $NVS_HOME/nvs.sh git bash中nvs报错找不到命令 nvs v1.5.1使用MSI或者choco install nvs安装之后可以正常使用,但是会缺失nvs.sh,如果你在git bash下使用nvs会出现找不到命令,后续版本会修复这个问题。推荐cmd手动安装步骤则没有这个问题。 nvs和npm命令不生效 安装之后在新的终端窗口中nvs和npm不生效时,注意注销重新登录,或者重新启动一次系统。 注意: 尤其是第一次使用nvs link之后,发现node和npm不会生效,最好注销重新登录。 共用npm全局模块 首先不建议大家跨版本公用全局模块,除非你知道你在干什么。 科普文:使用 nvs 管理本地 Node.js 版本 这里有介绍怎么设置。 123# 使用git bash创建mkdir -p ~/.npm-globalnpm config set prefix ~/.npm-global 123456# .bashrc中添加全局模块的路径# 注意~/.npm-global/ 不是~/.npm-global/bin# windows 10下没有bin文件夹,所以最好检查下。。。echo \"export PATH=~/.npm-global/:$PATH\" >> ~/.bashrcsource ~/.bashrc 注意: (nvs v1.5.1) 注意检查全局模块的配置路径,是否正确(坑) cmd和power shell下添加全局模块环境变量 通过以上的配置在git bash下,全局安装的npm模块工作的很正常,但是在cmd或者power shell会发现找不到命令,尝试过添加到系统变量,但是依然不生效,最后提个issues,官方给了解决方案。 12# 设置完如果不生效,新开一个命令行窗口尝试(如果不行,注销重新登录)setx PATH \"%PATH%;%USERPROFILE%\\.npm-global\" 设置之后:cmder, cmd , powershell, cygwin通通生效了 注意: nvs use 是改变当前shell窗口的node版本,不是全局环境(nvm的区别) nvs link是改变全局环境的node版本 参考科普文:使用 nvs 管理本地 Node.js 版本 从 nvm 迁移到 nvs cmder using .bashrc VS Code Support - Node Version Switcher xizhibei Node Version Manager Windows: when use npm prefix share global modules fails setx ss64 setx","tags":[{"name":"node","slug":"node","permalink":"https://betgar.github.io/tags/node/"},{"name":"nvs","slug":"nvs","permalink":"https://betgar.github.io/tags/nvs/"}]},{"title":"前端小纠结--集成gitflow和standard-version使用","date":"2019-05-21T00:00:00.000Z","path":"2019/05/21/frontend-little-tangle-integrate-git-flow-and-standard-version/","text":"前端小纠结–集成gitflow和standard-version使用 请看上一篇前端小纠结–约定式提交和自动生成changelog git flow工作流对比 Git工作流指南 git flow 介绍 git flow源自这篇文章a-successful-git-branching-model,大神!!!。 关于git flow的使用,请看以下链接,非常多的文章,写的非常好。 如何正确的使用git flow git-flow 备忘清单 git-branching-model 如图 git-flow-nvie git flow常用的分支 以下部分内容来自如何正确的使用git flow博客和gitflow-examples develop : 这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支 release/* : 当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支 master : 也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改 hotfix/* 当我们在生产环境(master)发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release feature/* : 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release git Flow如何工作 所有在Master分支上的Commit应该Tag git-workflow-release-cycle-1historical feature分支 分支名 feature/* Feature分支做完后,必须合并回Develop分支, 合并完分支后一般会删点这个Feature分支 git-workflow-release-cycle-2feature feature-branch release分支 分支名 release/* Release分支基于Develop分支创建,打完Release分之后,我们可以在这个Release分支上测试,修改Bug等。同时,其它开发人员可以基于开发新的Feature (记住:一旦打了Release分支之后不要从Develop分支上合并新的改动到Release分支) 发布Release分支时,合并Release到Master和Develop, 同时在Master分支上打个Tag记住Release版本号,然后可以删除Release分支了。 git-workflow-release-cycle-3release minor版本发布 minor-release major版本发布 major-release hotfix分支 分支名 hotfix/* hotfix分支基于Master分支创建,开发完后需要合并回Master和Develop分支,同时在Master上打一个tag git-workflow-release-cycle-4maintenance hotfix修复分支 hotfix-branch support分支 GitFlow没有真正涵盖support分支,如果需要同时维护多个主要版本,则必不可少。 您也可以使用support分支来支持minor版本。 如果您只是支持major,命名support/<major>.x,minor版本为support/<major>.<minor>.x 多版本hotfix修复 support-hotfix 多版本的release 多版本的git flow的release流程只是把master替换为需要维护的分支。 git flow工具的使用 windows和mac建议使用sourcetree客户端,内置了git flow流程,具体的使用方法,请自行搜索. 当然windows下的git for windows(git bash中)也内置了gitflow-scripts,请参考升级版git-flow-completion完成自动补全功能 如果使用linux,内置了gitflow-scripts(不知道是那个版本),很不负责任的建议使用升级版gitflow-avh. 关于使用git flow工具使用可以参考如何正确使用Git Flow这篇博客文章 上图是sourcetree中git flow的菜单 git flow和standard-version搭配使用 请务必在熟悉git flow的基础上集成使用standard-version,因为两个工具的使用不当,会造成冲突。 git flow流程中standard-version使用 standard-version的作用就是生成changelog更新package.json和package.lock.json中的version字段。所以它在git flow中使用受git flow的流程限制。目前我的经验是只能在git flow的release/*和hotfix/*分支中使用。因为只有这两个分支涉及到发版流程,而changelog只有在发版时刻才生成。 git flow和standard-version冲突的tag功能 git flow和standard-version在使用的过程中,都有自动打tag的功能,但是两者都支持跳过tag阶段,所以这也是这两个工具冲突的地方,建议从两者中选取其一。 强烈推荐 standard-version 自动 tag,不推荐使用git flow的tag功能 使用git flow自动tag必须保证tag格式standard-version能够识别,standard-version使用git-semver-tags解析tag,参考git-semver-tags支持的格式 如果standard-version识别tag出现遗漏,会造成生成的changelog内容重复。standard-version默认的tag前缀是v,可以自定义。 standard-version跳过tag方法 12345678// 方法一: package.json添加配置{ \"standard-version\": { \"skip\": { \"tag\": true } }} 12# 方法二:命令行中添加参数standard-version -r minor --skip.tag git flow跳过tag的方法 12# 跳过tag,并且可以自定义commit message(为了让commit message符合约定提交的格式规范)git flow release finish v0.2.0 -n -m \"chore(release): 0.2.0\" standard-version运行时机 release 分支流程中运行 git flow在release finish阶段是把release/*分支合并到master和develop,所以standard-version就是要在finish结束之前运行生成changelog. 123456789101112131415161718# 1. 模拟一个release流程git flow release start v0.2.0# 2. 做一些修改或者commitgit commit -m 'fix(src): 修复问题'# 3. (可选)如果需要在release/* 分支生成beta测试阶段的changelognpx standard-version -p beta# 4. release finish 之前# 强烈推荐 standard-version 自动tag,不推荐使用git flow的tag功能npx standard-version -r v0.2.0# 5. 正式发布release,自定义commit message为了符合约定式提交的格式git flow release finish v0.2.0 -m \"chore(release): 0.2.0\"# 如果你使用了standard-version的tag功能,git flow应该跳过tag# git flow release finish v0.2.0 -n -m \"chore(release): 0.2.0\" hotfix分支流程中运行 hotfix finish阶段和release非常像是把hotfix/*分支合并到master和develop,但是是否在hotfix分支生成changelog还需要自行决定(有冲突的风险) 1234567891011121314151617# 1. 模拟一个hotfix流程git flow hotfix start v0.2.1# 2. 做一些修改或者commitgit commit -m 'fix(src): 修复问题'# 3. hotfix finish 之前# 强烈推荐 standard-version 自动tag,不推荐使用git flow的tag功能npx standard-version -r v0.2.1 # patch版本号可以使用 patch 替代# npx standard-version -r patch# 正式发布hotfix,自定义commit message为了符合约定式提交的格式git flow hotfix finish v0.2.1 -m \"chore(release): 0.2.1\"# 如果你使用了standard-version的tag功能,git flow应该跳过tag# git flow hotfix finish v0.2.0 -n -m \"chore(release): 0.2.1\" standard-version在git flow不同流程阶段注意的问题 standard-version在release流程中注意事项: release中生成beta版本的changelog 前置条件: release 在hotfix之前创建 hotfix中生成changelog release中生成beta版的changelog release分支的创建时机很重要,git flow流程中release在hotfix之后创建 如果创建release分支之后,出现并修复hotfix并且在hotfix生成changelog,hotfix finish之后release finish就会造成release合并到master和develop时出现冲突. 解决方案: release 分支包含hotfix内容(release 分支在hotfix之后创建,或者hotfix提取成patch,在release分支上apply)(git flow流程中hotfix是包含在next release中的) 如果有更好的方法请告诉我 ~): standard-version在hotfix流程中注意事项: hotfix中生成changelog release分支在hotfix finish 之前建立,会出现在release分支一样的问题 hotfix分支不生成changelog release分支在hotfix finish 之前建立,会造成hotfix修复的的日志无法出现在changelog中 解决方法: release 分支包含hotfix内容(release 分支在hotfix之后创建,或者hotfix提取成patch,在release分支上apply) standard-version不带参数使用 12# 如果直接在分支上,使用standard-version 如果不指定参数直接使用standard-version命令,standard-version会自动分析commit message类型,如果包含patch就累加patch,有feat就自动累加minor,有break change就自动生成 major版本号,风险较大,建议指定参数。 建议 工具和流程模型都是根据使用场景,灵活多变的,所以不要局限于工具和流程模型,实践出适合自己的流程模型才是最重要的。 参考a-successful-git-branching-model 原版git flow scripts弃坑状态不建议使用 原版git-flow-completion 升级版gitflow-avh 升级版git-flow-completion 约定式提交规范 git-flow 备忘清单 git-flow-standard-version git-flow-conventional-commits 如何正确使用Git Flow git rebase保持代码树整洁 集中式工作流 功能分支工作流 Gitflow工作流 Forking工作流 comparing-workflowsgitflow-examples","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"},{"name":"gitflow","slug":"gitflow","permalink":"https://betgar.github.io/tags/gitflow/"},{"name":"standard-version","slug":"standard-version","permalink":"https://betgar.github.io/tags/standard-version/"}]},{"title":"前端小纠结--约定式提交和自动生成changelog","date":"2019-05-20T00:00:00.000Z","path":"2019/05/20/frontend-little-tangle-conventional-commit-and-generate-changelog/","text":"前端小纠结–约定式提交和自动生成changelog约定式提交 约定式提交:每次使用git commit 的时候都需要写commit message,如果message style是按照固定的模版格式书写,对于后期的维护和编写changelog都有巨大的好处。而且现在的很多自动生成changelog的工具,都是建立在约定式提交的基础之上。 约定式提交校验配置 约定式提交不需要任何的配置,只需要严格遵守其规范就可以了。为了保证每次提交的commit message都是遵守conventional commit spec所以添加了校验配置. 约定式提交工具 约定式提交使用交互式提交工具(例如: commitizen),使用工具能够保证约定式提交个格式是满足规范的。 推荐使用vs code 插件commitizen 命令行方式的commitizen配置(vscode commitizen和命令行方式任选其一即可) 1npm install --save-dev commitizen 123456// package.json配置{ scripts: { \"commit\": \"git-cz\" }} 12# 使用npm run commit # 进入交互式, 如果commit message在console中换行,请使用'开始 commitizen的适配器cz-conventional-changelog commitizen只是一个提交的工具,只有添加了约定格式的适配器才能按照固定格式进行交互式提交,否则就和普通的git commit一样了。(还有其他适配器可选,请参考官方文档)不同的适配器,提供的约定标准有差异。 12# 建议安装npx这个神器 npm i npx -gnpx commitizen init cz-conventional-changelog --save-dev --save-exact commitizen工具会在package.json中自动添加配置: 12345678{ \"config\": { \"commitizen\": { // \"path\": \"cz-conventional-changelog\" // 全局安装模式 \"path\": \"./node_modules/cz-conventional-changelog\" } }} 约定式提交格式校验 为了防止出现不满足格式要求的commit message出现,还是需要添加上必要的格式校验. commitlint校验约定式提交格式 commitlint是在commit message之前进行校验格式,格式不对不进行提交。 12# 安装npm install --save-dev @commitlint/config-conventional @commitlint/cli 12# 添加配置文件commitlint.config.js,工程根目录echo \"module.exports = {extends: ['@commitlint/config-conventional']}\" > commitlint.config.js husky配置git hooks husky官网 husky配置 有了git hooks我们可以做很多提交之前的验证。 12# 安装npm i husky --save-dev 123456789// package.json 配置{\"husky\": { \"hooks\": { \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\", // 配合commitlint使用 // \"pre-commit\": \"lint-staged\" // 配置lint-staged } }} lint-staged配置(可选) lint-staged list-staged主要配合linter用来格式化代码(统一的代码风格),这部是可选的。 lint-staged是用来让格式化工具只lint需要提交的文件,其它文件忽略,这样能够提高效率。 1npm i lint-staged --save-dev 123456789// package.json 配置{ \"lint-staged\": { \"src/**/*.{js,ts,css,vue,tsx,jsx}\": [ \"vue-cli-service lint\", // npm run lint \"git add\" ] }} 自动生成changelog 自动生成changelog是建立在约定式提交的基础上。 自动生成changelog有两种方式: conventional-changelog-cli生成changelog standard-version生成changelog standard-version帮你做了自动打tag,自动生成changelog等过程. conventional-changelog-cli配置 在约定式提交的基础上来自动生成changelog,工具类库开发,推荐直接使用standard-version,高度集成了所有东西。 1npm install conventional-changelog-cli --save-dev 12# 基本使用,其它参数参考官方网站conventional-changelog -p angular -i CHANGELOG.md -s -r 0 123456// package.json配置{ scripts: { \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md\", }} 12# 使用npm run changelog standard-version配置12# 安装npm install standard-version --save-dev 1234567891011121314// package.json 配置{ \"scripts\": { \"release\": \"standard-version\" }, // standard-version 好多配置看官方文档(可选) \"standard-version\": { // 可以配置不需要的环节 \"skip\": { // bump, changelog, commit, tag \"tag\": true } }} 123456789101112# 项目根目录下使用,自行安装npx 模块# npm i npx -g npx standard-version --release-as majornpx standard-version --release-as minornpx standard-version --release-as patchnpx standard-version --prerelease alphanpx standard-version --prerelease betanpx standard-version --prerelease rcnpm run release -- -r minornpm run release -- -p beta package.json中的scripts命令组织12345678910111213{ scripts: { \"commit\": \"git-cz\", \"release\": \"standard-version\", \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md\", \"release:major\": \"standard-version -r major -n, \"release:minor\": \"standard-version -r minor -n, \"release:patch\": \"standard-version -r patch -n\", \"prerelease:alpha\": \"standard-version -p alpha -n, \"prerelease:beta\": \"standard-version -p beta -n, \"prerelease:rc\": \"standard-version -p rc -n\" }} 参考约定式提交规范 git commit 、CHANGELOG 和版本发布的标准自动化 Commit message 和 Change log 编写指南","tags":[{"name":"git","slug":"git","permalink":"https://betgar.github.io/tags/git/"},{"name":"conventionalcommit","slug":"conventionalcommit","permalink":"https://betgar.github.io/tags/conventionalcommit/"},{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"shell上显示当前git分支","date":"2019-04-28T16:00:00.000Z","path":"2019/04/29/terminal-prompt-display-what-branch-you-are-currently-on/","text":"shell上显示当前git分支 在当前终端上显示当前所在的分支,能够提供极大的便利。 修改bash的配置 ~/.bash_profile 或者~/.bashrc 原版123456789function git-branch-name { git symbolic-ref HEAD 2>/dev/null | cut -d\"/\" -f 3}function git-branch-prompt { local branch=`git-branch-name` if [ $branch ]; then printf \" [%s]\" $branch; fi}PS1=\"\\u@\\h \\[\\033[0;36m\\]\\W\\[\\033[0m\\]\\[\\033[0;32m\\]\\$(git-branch-prompt)\\[\\033[0m\\] \\$ \" 修改版123456789# terminal-prompt-display-what-branch-you-are-currently-onfunction git-branch-name { git symbolic-ref --short -q HEAD 2>/dev/null}function git-branch-prompt { local branch=`git-branch-name` if [ $branch ]; then printf \" [%s]\" $branch; fi}PS1=\"\\u@\\h \\[\\033[0;36m\\]\\W\\[\\033[0m\\]\\[\\033[0;32m\\]\\$(git-branch-prompt)\\[\\033[0m\\] \\$ \" 把上面的脚步添加到shell配置文件中。 123# 加载一下配置文件,使之生效source ~/.bash_profile # source ~/.bashrc 参考git-setting-up-a-remote-repository-and-doing-an-initial-push 让 Shell 命令提示符显示 Git 分支名称","tags":[{"name":"git","slug":"git","permalink":"https://betgar.github.io/tags/git/"}]},{"title":"axios实现文件下载功能","date":"2019-04-14T10:30:00.000Z","path":"2019/04/14/download-binary-file-use-axios/","text":"axios实现文件下载功能 在开发中遇到了需要实现文件下载的功能,起初以为只用<a>标签就能搞定,<a>标签确实能够搞定常见的场景。但是像导出或者在header里面添加了特殊字段的时候,使用<a>标签就搞不定了,又不想去使用原生XMLHttpRequest,因为又一堆的兼容性需求(技术能力不够ε=ε=ε=┏(゜ロ゜;)┛,有现成的兼容方案为啥要自己造轮子呢,说不定还爆胎>逃666),所以萌生基于Axios封装。 Ajax无法下载文件的原因浏览器的GET(frame、a)和POST(form)请求具有如下特点: response会交由浏览器处理 response内容可以为二进制文件、字符串等 Ajax请求具有如下特点: response会交由Javascript处理 response内容仅可以为字符串 Ajax本身设计的目标就是用来获取文本数据的,而不是用来搞二进制的。 XMLHttpRequest 2.0新增的数据类型Blob 看张老师的文章 理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型 有了Blob类型之后,JavaScript处理二进制进一步增强,可以说以后想怎样就怎样(废话)。 文件下载实现response header前提条件 服务端返回的头部需要设置 Content-Disposition: “attachment; filename=xxxx.docx;” <a>标签的直接下载12345678910111213141516171819202122232425262728293031323334import qs from 'qs';/** * downloadByUrl * @param config - 配置参数 * @param config.url - 地址 * @param config.params - querystring参数. * @param filename 文件名称,包括扩展名部分(不一定生效) * * @description * 原理是使用<a>的href和download属性,所以filename不一定会生效, 浏览器机制问题. * * @see https://zhuanlan.zhihu.com/p/58888918 * @see https://github.com/kennethjiang/js-file-download */export function downloadByUrl(config: { url: string; params: any;}, filename = ''): void { var tempLink = document.createElement('a'); tempLink.style.display = 'none'; tempLink.href = config.url + qs.stringify(config.params, { addQueryPrefix: true }); tempLink.setAttribute('download', filename); if (typeof tempLink.download === 'undefined') { tempLink.setAttribute('target', '_blank'); } document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink);} 主要是使用了js-file-download的代码,进行了简单的封装,而且去除了对Blob的依赖,主要为了兼容低版本的浏览器。同时使用了qs对querystring参数进行了简单的处理。 基于axios的实现123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';import fileDownload from 'js-file-download';import logger from 'js-logger';/** * 提取文件名. * @param response axios的response * @description 从reponse header的content-disposition中提取文件名. */const extractFilenameFromResponseHeader = (response: AxiosResponse): string => { // content-disposition: \"attachment; filename=xxxx.docx;\" const contentDisposition = response.headers['content-disposition']; const patt = new RegExp('filename=([^;]+\\\\.[^\\\\.;]+);*'); const result = patt.exec(contentDisposition) as RegExpExecArray; let filename = ''; if (result) { filename = result.length > 0 ? result[1] : ''; } // 解码之前尝试去除空格和双引号 // content-disposition: \"attachment; filename=\\\"xxxx.docx\\\";\" return decodeURIComponent(filename.trim().replace(new RegExp('\"', 'g'), ''));};const axiosInstance = axios.create({/* 可以传递公共默认的axios配置,但是注意reponse interceptor中默认把reponse.data作为JSON解析的情况*/});// https://www.zhihu.com/question/263323250// https://github.com/axios/axios/issues/815#issuecomment-340972365const downloadByAxios = async function ( config: AxiosRequestConfig, filename = ''): Promise<any | AxiosResponse<any>> { let response = await axiosInstance({ ...config, responseType: 'blob', // 指定类型 }); let resBlob = response.data; // <--- store the blob if it is let respData = null; // 如果确定接口response.data是二进制,所以请求失败时是JSON. // 这里只对response.data做JSON的尝试解析 try { let respText = await new Promise((resolve, reject) => { let reader = new FileReader(); reader.addEventListener('abort', reject); reader.addEventListener('error', reject); reader.addEventListener('loadend', () => { resolve(reader.result as string); }); reader.readAsText(resBlob); }); respData = JSON.parse(respText as string); // <--- try to parse as json evantually } catch (err) { // ignore } // 如果response.data能够确定是二进制,则respData = null说明请求成功 // 否则 respData !== null说明请求失败 if (respData as ResponseData) { logger.error(respData); // 方便调用者有进一步的 then().catch()处理 return Promise.reject({ ...respData, }); } else { // 触发浏览器下载 // 如果没有传递filename尝试从Content-Disposition提取 fileDownload(resBlob, filename || extractFilenameFromResponseHeader( response )); // 方便调用者有进一步的 then().catch()处理 return Promise.resolve({ ...response, }); }}; 代码大部分都是参考这个issue实现的,只有少部分的个人代码。 基于axios实现的功能: 可以使用axios的所有参数,不管请求是GET或者POST 解决了在header中添加额外的参数的需求 可以指定filename,如果服务端没有设置content-disposition的情况 返回Promise方便调用者进一步处理请求 缺点: 只能使用独立的axios实例,不能公用一个axios 本来想把下载功能使用axios interceptor拦截器实现,但是返回的response.data是Blob二进制,但是其它的response interceptor默认前提都是把response.data当作JSON处理,导致全部出现异常,所以把下载功能独立出来,更方便维护。 使用独立的axios实例,所以项目中的axios默认配置需要重新配置一遍 参考链接Content-Disposition axios.js实现下载功能 axios.js #815实现 StreamSaver FileSaver js-file-download理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型","tags":[{"name":"axios","slug":"axios","permalink":"https://betgar.github.io/tags/axios/"}]},{"title":"windows 10使用cygwin安装openssh服务端","date":"2019-04-05T01:00:00.000Z","path":"2019/04/05/install-openssh-with-cygwin-on-windows-10/","text":"windows 10使用cygwin安装openssh服务端 install-openssh-with-cygwin-on-windows-10 本来windows10上已经有内置的openssh,我可能爱折腾不愿意使用内置的,所以使用cygwin重新安装了一个ssh服务. 安装步骤总结 安装cygwin 使用管理员权限运行cygwin安装openssh和openssl ssh-host-config命令开始配置ssh windows10内置的sshd和ssh-host-config默认的服务名冲突解决 一系列ssh-host-config配置,包括daemon名称 ntsec和创建默认的cyg_server来运行sshd服务 ssh-user-config生成用户的密钥(非必须,可以使用密码登陆,用户就是windows的正常用户) 启动服务net start cygsshd 使用putty登陆localhost测试 卸载ssh 重新安装ssh 注意:cygwin使用的是windows的用户和权限,所以默认/etc/下是没用passwd和group等文件的,不要太惊讶。 开始我也没用搞明白,而且ssh-host-config中创建一个cyg_server用户是真实的windows账户,但是需要组策略才能够看到,这个用户用来启动sshd服务的,而且不能用来登陆windows,因为是一个令牌用户,在组策略>计算机配置>windows配置>安全设置>本地策略>用户权限分配> 创建一个令牌对象中看到。 cygwin安装openssh和openssl假设你已经安装了cygwin,使用cygwin安装openssh和openssl详情看参考: how-to-get-ssh-command-line-access-to-windows-7-using-cygwin/ 修复sshd service is already installed. 警告:sshd service is already installed. sshd-service-installed-error 出现sshd service is already installed.的问题是因为windows 10内置了一个openssh,造成了冲突,现在只需要修改cygwin安装的openssh中的默认的sshd服务名称。 修改默认的sshd服务名路径: C:/cygwin64/bin/sshd-host-config (32位的有所区别,找不到用everything搜索一下啊) 修改: service_name=sshd 为 service_name=cygsshd (cygsshd自定义的) 修改完之后重新执行ssh-host-config,上面的问题解决. 下面是运行中的截图. 建议使用默认的cyg_server,没用必要重新自定义,我当时也是好奇,搞得走了很多弯路。 启动服务 也可以重启一下系统,服务会自动启动 123net start cygsshd # cygsshd是自定义的service_name# 或者使用cygwin运行cygrunsrv -S cygsshd ssh-user-config 选择生成用户的RSA口令就行了,RSA比较常用,其它的不是很常用。 卸载ssh服务123456789101112# 删除sshd服务, sshd时默认的服务名,如果你定义了,输入你的服务名cygrunsrv --stop sshdcygrunsrv --remove sshd# Delete any sshd or related users (such as cyg_server) from /etc/passwd# (use your favorite editor)# Delete any sshd or related users (such as cyg_server) from the system# 这里删除的是windows上的用户net user sshd /delete# 如果ssh-host-config配置过程中没用使用cyg_server,请替换为你自定义的名字net user cyg_server /delete 重装ssh重新设置权限 如果设置权限不行,删除sshd.log和empty文件夹. 123456chown <USERNAME> /var/log/sshd.logchown -R <USERNAME> /var/emptychown <USERNAME> /etc/ssh*chmod 755 /var/emptychmod 644 /var/log/sshd.log 参考资料Installing Cygwin and Starting the SSH Daemon how-to-get-ssh-command-line-access-to-windows-7-using-cygwin/ cygwin-sshd-on-windows-domain how-to-regenerate-ssh-client-keys mkpasswd mkgroup how-to-uninstall-reinstall-cygwin-to-use-the-sshd","tags":[{"name":"openssh","slug":"openssh","permalink":"https://betgar.github.io/tags/openssh/"},{"name":"cygwin","slug":"cygwin","permalink":"https://betgar.github.io/tags/cygwin/"}]},{"title":"deepin安装之后分配swap空间","date":"2019-03-12T23:00:00.000Z","path":"2019/03/13/allocation-swap-partition-on-deepin-linux/","text":"deepin安装之后分配swap空间参考这里的文章做个记录 增加swap文件作为空间,五步: 第一步:成为超级用户; 1sudo su 第二步:在根目录生成swap文件,count设定块数,bs设定块大小,生成4G的swap代码如下 1dd if=/dev/zero of=/swapfile count=4096 bs=1024k 第三步:设置交换分区,注意路径和上面的of=/swapfile的一致 1mkswap /swapfile 第四步:挂载交换分区,注意swap文件路径 1swapon /swapfile 第五步:开机自动挂载swap文件: 1、用编辑器打开fstab文件: 1vim /etc/fstab 2、在后面追加以下内容(注意swap文件路径) 12#开机挂载交换分区 /swapfile swap swap defaults 0 0 打开深度系统监视器查看一下是否生效了。","tags":[{"name":"linux","slug":"linux","permalink":"https://betgar.github.io/tags/linux/"},{"name":"deepin","slug":"deepin","permalink":"https://betgar.github.io/tags/deepin/"}]},{"title":"npm私有仓库verdaccio的部署","date":"2019-03-09T03:00:00.000Z","path":"2019/03/09/deploy-private-npm-registry-verdaccio/","text":"verdaccio 因为技术变革太快,注意知识的时效性。 verdaccio是基于node开发的一个库,用来搭建私有的npm仓库。因为npm仓库大多数是公有的免费的,私有仓库或者企业版是收费的,在单位内部或者个人一些私有的模块需要托管的时候就需要自己搭建一个私有的仓库,而verdaccio就是选择之一,最便捷的一种。 安装verdaccio 参照官方文档,而且是中文版哟~~~。 注意: node.js的版本要求 1234567891011# 全局安装npm i -g verdaccio# 启动verdaccio # 使用forever或者pm2能够启动verdaccio到后台# 后台运行forever start `which verdaccio`# 或者pm2 start which verdaccio 配置verdaccio 配置文件路径参考:https://verdaccio.org/docs/zh-CN/cli windows: C:\\Users\\用户名\\.config\\verdaccio\\config.yaml 自定义端口号12listen: 0.0.0.0:10086 添加taobao源12345uplinks: npmjs: url: https://registry.npmjs.org/ taobao: url: https://registry.npm.taobao.org 配置packages 官方参考 配置私有包的规则 建议私有的包都添加统一的前缀或者使用 @npmuser/ 这样的小组范围类型。 1234packages: 'npmuser-*': access: npmuser publish: npmuser 这样配置npmuser-*开头的包,发布的时候全部都发布到verdaccio中。而且之能npmuser这个用户使用。 1234567packages: 'npmuser-*': access: $authenticated publish: $authenticated # verdaccio内置使用htpasswd插件验证,内置的小组名称有: # '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' 这样配置就是npmuser-*包,只能登陆的用户使用。 配置proxy 修改默认的proxy为uplinks中的taobao 1234567891011packages: '@*/*': # scoped packages access: $all publish: $authenticated proxy: taobao # 引用了uplinks中的配置 '**': access: $all publish: $authenticated proxy: taobao # 引用了uplinks中的配置 配置npm的registry 建议使用nrm管理registry 详情参考这篇文章 1234# 添加本地的verdaccionrm add local http://127.0.0.1:4873# 切换到localnrm use local 私有模块发布和使用 发布私有的模块前,要先使用nrm切换到verdaccio发布的registry,并且写先注册一个用户,然后才能发布模块 切换registry 1nrm use local 使用npm注册一个账户 1234npm adduser # 注册用户# 注意这个账户不是npmjs.com的账户,这个账户只是注册到自己发布的verdaccio服务中# verdaccio使用htpasswd来验证用户密码# verdaccio有插件可以使用自己搭建的gitlab用户来验证。 发布模块 1234# 例如: node项目为node-demo# node-demo目录下执行# 发布之前注意修改package.json中的version版本号npm publish 使用私有模块 12# 例如:私有模块名称为:pvt-nodenpm i pvt-node","tags":[{"name":"npm","slug":"npm","permalink":"https://betgar.github.io/tags/npm/"},{"name":"verdaccio","slug":"verdaccio","permalink":"https://betgar.github.io/tags/verdaccio/"}]},{"title":"Linux环境安装node,部署verdaccio私有npm仓库","date":"2019-02-25T16:00:00.000Z","path":"2019/02/26/install-node-on-ubuntu/","text":"ubuntu(16.04)安装node环境 记录一下在Linux下安装Node环境的经过. 安装步骤更改ubuntu的镜像源(非必须) 因为 网络环境的限制 更改镜像源可以加快包的下载 12345678910111213> ## 要注意的是不同的版本对应的仓库是不同的。> > # 备份镜像源> sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak> > # 编辑source.list> sodo vim /etc/apt/sources.list> > # 可以使用网易源,搜狐源,阿里源> # 参考 https://mirrors.ustc.edu.cn/help/ubuntu.html> # http://mirrors.sohu.com/> # http://mirrors.163.com/> 安装nvm1curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash 如果安装之后nvm,再命令行上使用nvm出现未找到命令,只需要重新打开一个终端窗口重试。 安装node 12345> nvm install node # 最新版本> nvm install 6.14.4 # 具体版本> nvm ls-remote # 有哪些版本> nvm use node # 使用具体的哪个版本> 安装nrm1npm install -g nrm --registry=https://registry.npm.taobao.org 安装python 因为node-gyp模块只支持python2.x所以需要安装python,如果linux已经安装,可以使用node-gyp制定配置python地址。 参考再ubuntu上安装python2.7 安装node-gyp1npm install -g node-gyp 123456# 配置python依赖# 如果是直接命令调用node-gyp,就这样配置node-gyp --python /path/to/python2.7 # python的地址# 如果是通过npm工具间接调用node-gyp就这样配置npm config set python /path/to/executable/python2.7 安装verdaccio12345678# 安装verdaccionpm install --global verdaccio# 使用nrm添加名为local的registrynrm add local http://localhost:4873/# 启动verdaccioverdaccio # 这样启动不能关闭命令行,所以我们使用pm2工具 具体的配置看verdaccio配置 安装pm21npm install pm2 -g 安装日志模块(pm2-logrotate)1pm2 install pm2-logrotate 安装监控模块(pm2-server-monit)1pm2 install pm2-server-monit pm2启动verdaccio123456pm2 start which verdaccio # 启动之后可以关闭命令行,进程不会终止。# pm2的其它功能参考文档# pm2 show# pm2 list# pm2 stop# pm2 restart 链接 nvm官网 nrm官网 node-gyp官网 verdaccio官网 pm2官网 pm2-logrotate日志管理 pm2-server-monit监控模块","tags":[{"name":"linux","slug":"linux","permalink":"https://betgar.github.io/tags/linux/"},{"name":"node","slug":"node","permalink":"https://betgar.github.io/tags/node/"},{"name":"ubuntu","slug":"ubuntu","permalink":"https://betgar.github.io/tags/ubuntu/"}]},{"title":"Vue2+TypeScript实践准备","date":"2019-02-12T16:00:00.000Z","path":"2019/02/13/vue2-typescript-action-prepare/","text":"Vue2 + TypeScript实践准备 基于Vue2.5版本,Vue3.0可能有所改变. 知识准备 扎实的 JavaScript / HTML / CSS 基本功 通读Vue官方教程 (guide) 的基础篇 熟悉vue-router 和 vuex 熟悉ES6 modules 熟悉Node.js 基础 了解如何使用 / 配置 Babel 来将 ES2015 编译到 ES5 用于浏览器环境 熟悉 Webpack vue-cli 来搭建Vue项目 Node.jsNode.js是什么? Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式I/O 的模型,使其轻量又高效。 npm模块管理器npm有两层含义。一层含义是Node的开放式模块登记和管理系统,网址为npmjs.org。另一层含义是Node默认的模块管理器,是一个命令行下的软件,用来安装和管理Node模块。 1234567npm initnpm install lodash-esnpm uninstall lodash-esnpm update lodash-esnpm install lodash-es --savenpm install lodash-es --save-dev npm run npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用。 123456789// package.json{ \"scripts\": { \"serve\": \"vue-cli-service serve\", \"build\": \"vue-cli-service build\", \"lint\": \"vue-cli-service lint\", \"test:unit\": \"vue-cli-service test:unit\" }} 1npm run serve package.json文件 每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。 name:描述的是模块的名称 version: 描述模块的版本号(遵循语义化版本) main: 指定入口文件,默认为index.js dependencies:字段指定了项目运行所依赖的模块 devDependencies:指定项目开发所需要的模块。 scripts: npm run 执行的命令就是这里指定的。 CommonJS规范 Node 应用由模块组成,采用 CommonJS 模块规范。 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 123456789// example.jsvar x = 5;var addX = function (value) { return value + x;};// 导出之后才可见module.exports.x = x;module.exports.addX = addX; 12345// 使用example模块var example = require('./example.js');console.log(example.x); // 5console.log(example.addX(1)); // 6 TypeScript TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。 基础类型12345678910111213141516171819202122232425262728// booleanlet isDone: boolean = false;// 数字let decLiteral: number = 6;let hexLiteral: number = 0xf00d;let binaryLiteral: number = 0b1010;let octalLiteral: number = 0o744;// 字符串let name: string = \"bob\";// 数组let list: number[] = [1, 2, 3];let list: Array<number> = [1, 2, 3];// 元组let x: [string, number] = ['hello', 10]; // OKx[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型// 枚举enum Color { Red, Green, Blue}let c: Color = Color.Green; 任意值any 但是 Object 类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法 . 12345678let notSure: any = 4;notSure.ifItExists(); // okay, ifItExists might exist at runtimenotSure.toFixed(); // okay, toFixed exists (but the compiler doe// sn't check)let prettySure: Object = 4;prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist// on type 'Object'. 空值void void 类型像是与 any 类型相反,它表示没有任何类型 123456function warnUser(): void { alert(\"This is my warning message\");}// 声明一个 void 类型的变量没有什么大用,因为你只能为它赋予 undefined 和 nulllet unusable: void = undefined; null和undefined 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。 12let u: undefined = undefined;let n: null = null; 注意:当你指定了 –strictNullChecks 标记, null 和 undefined 只能赋值给 void 和它们各自。 Object object 表示非原始类型,也就是除 number , string , boolean , symbol , null 或 undefined 之外的类型。 强制类型转换1234let someValue: any = \"this is a string\";let strLength: number = (<string>someValue).length;// as 语法let strLength: number = (someValue as string).length; 可选属性 可选属性使用?标识 12345678910111213141516// Vue的ComponentOptions声明的一部分export interface ComponentOptions< V extends Vue, Data=DefaultData<V>, Methods=DefaultMethods<V>, Computed=DefaultComputed, PropsDef=PropsDefinition<DefaultProps>, Props=DefaultProps> { data?: Data; props?: PropsDef; propsData?: object; computed?: Accessors<Computed>; methods?: Methods; watch?: Record<string, WatchOptionsWithHandler<any> | WatchHandler<any> | string>; // ...} 去除null和undefined类型断言使用!标识 有的时候TypeScript无法正确推导出类型时,需要手动去除null和undefined 12345export default class Login extends Vue { @Mutation(UserMutationTypes.SET_USER_NAME) private changeUserName!: (name: string) => void; } 接口 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 123456789interface LabelledValue { label: string;}function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label);}// let myObj = {size: 10, label: \"Size 10 Object\"};let myObj = {size: 10, label: \"Size 10 Object\"};printLabel(myObj); 类1234567891011121314class Animal { move(distanceInMeters: number = 0) { console.log(`Animal moved ${distanceInMeters}m.`); }} class Dog extends Animal { bark() { console.log('Woof! Woof!'); }} const dog = new Dog();dog.bark();dog.move(10);dog.bark(); 函数12345678910111213141516// Named functionfunction add(x, y) { return x + y;}// Anonymous functionlet myAdd = function(x, y) { return x + y; };function tsAdd(x: number, y: number): number { return x + y;} let tsMyAdd = function(x: number, y: number): number { return x +y; }; 声明合并 注意每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。 123456789101112131415161718interface Cloner { clone(animal: Animal): Animal;}interface Cloner { clone(animal: Sheep): Sheep;}interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat;}// 这三个接口合并成一个声明:interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; clone(animal: Sheep): Sheep; clone(animal: Animal): Animal;} 扩充全局或者模块作用域 不论是模块扩充还是全局声明扩充都不能向顶级作用域添加新的项目 - 它们只能为已经存在的声明添加 “补丁”. 123456789101112131415161718// map.tsimport { Observable } from \"./observable\";// 扩充 \"./observable\"declare module \"./observable\" { // 使用接口合并扩充 'Observable' 类的定义 interface Observable<T> { map<U>(proj: (el: T) => U): Observable<U>; }}Observable.prototype.map = /*...*/;// declare global 声明被增强:declare global { interface Array<T> { mapToNumbers(): number[]; }}Array.prototype.mapToNumbers = function () { /* ... */ } Vue对TypeScript的支持 Vue的TypeScript 支持 TypeScript识别Vue组件12345// vue-shim.d.tsdeclare module \"*.vue\" { import Vue from \"vue\"; export default Vue;} TypeScript识别JSX12345678910111213141516171819202122232425262728// 渲染函数 & JSXVue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // 标签名称 this.$slots.default // 子元素数组 ) }, props: { level: { type: Number, required: true } }});// shims-tsx.d.tsdeclare global { namespace JSX { // tslint:disable no-empty-interface interface Element extends VNode {} // tslint:disable no-empty-interface interface ElementClass extends Vue {} interface IntrinsicElements { [elem: string]: any; } }} 扩展Vue类和原型声明书写123456789// 例子:declare module 'vue/types/vue' { interface Vue { $axios: AxiosInstance } interface VueConstructor { $axios: AxiosInstance }} Vue组件声明方式11234567<template> <div> <div class=\"greeting\">Hello {{name}}{{exclamationMarks}}</div> <button @click=\"decrement\">-</button> <button @click=\"increment\">+</button> </div></template> 1234567891011121314151617181920212223242526</script><script lang=\"ts\">import Vue from \"vue\";export default Vue.extend({ props: ['name', 'initialEnthusiasm'], data() { return { enthusiasm: this.initialEnthusiasm, } }, methods: { increment() { this.enthusiasm++; }, decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } }, }, computed: { exclamationMarks(): string { return Array(this.enthusiasm + 1).join('!'); } }});</script> 12345<style>.greeting { font-size: 20px;}</style> Vue组件声明方式21234567891011121314151617import Vue from 'vue'import Component from 'vue-class-component'// @Component 修饰符注明了此类为一个 Vue 组件@Component({ // 所有的组件选项都可以放在这里 template: '<button @click=\"onClick\">Click!</button>'})export default class MyComponent extends Vue { // 初始数据可以直接声明为实例的属性 message: string = 'Hello!' // 组件方法也可以直接声明为实例的方法 onClick (): void { window.alert(this.message) }} Vue组件使用注意事项 Vue组件或者ts文件中导入*.vue文件时,需要完整文件名 1import Toolbar from '@/components/toolbar.vue'; <script lang="ts">标签必须带lang=“ts” <script>导出的对象必须是Vue的实例 1234567@Componentexport default class HelloDecorator extends Vue {}// 或者export default Vue.extend({ // ComponentOptions}) vue-class-component注解的限制 methods直接声明为类方法成员. 计算属性声明为类属性访问器. data声明为类的字段。 data , render和所有的Vue生命周期钩子函数都能可以直接声明为类成员方法。但是你不能在Vue实例上直接调用。当你声明自定义方法时,应该避免声明周期钩子函数保留的名字 其它的options属性,使用传递给@Component装饰器函数。例如:components属性 data属性声明注意事项1234567891011121314151617@Componentexport default class Login extends Vue { public name: string = 'login'; // 直接声明为类的字段 private userName: string = ''; private userPass: string = ''; @Mutation(UserMutationTypes.SET_USER_NAME) private changeUserName!: (name: string) => void; // Vue默认的data函数返回属性 data() { return { labelPosition: 'right', }; }} 如果data属性需要在class的method中修改时,声明为类的字段。 如果data属性只在template中使用,可以使用data函数返回. TypeScript官方声明文件仓库 https://github.com/DefinitelyTyped/DefinitelyTyped npm仓库为: @types/mockjs https://microsoft.github.io/TypeSearch/ 引入第三方库(没有声明文件) 比如说我想引入async-validator,虽然已经在本地安装,但是typescript还是提示找不到模块。原因是typescript是从node_modules/@types目录下去找模块声明,有些库并没有提供typescript的声明文件,所以就需要自己去添加 解决办法:在src/types目前下建一个async-validation.d.ts文件,声明这个模块即可 1234567891011121314151617declare module 'async-validator' { export type AsyncValidator<T = object> = ( rule: RuleConfig, value: any, callback: ValidatorCallback, source: T, options: { messages: object; }, ) => void;}// 看看*.vue模块怎么声明的declare module '*.vue' { import Vue from 'vue'; export default Vue;} 前端涉及的知识前端基础 JavaScript / HTML / CSS ES6 的使用 Vue部分 Vue的基础知识 Vuex和Vue-Router使用 Vue工具链的相关知识 vue-loader vue-cli vetur(vs code 插件) vue.config.js vue-property-decorator vuex-class Vue生态链的组件库(例如: Element UI) Node部分 Node.js模块知识 CommonJs规范(require, module, export) npm基本使用 Node.js的package.json和模块知识 Webpack 了解Webpack配置 Webpack插件 css-loader, style-loader, sass-node… TypeScript TypeScript基本语法 TypeScript类型声明文件*.d.ts写法 tsconfig.json文件配置 了解tslint.json 其它类库 Axios.js库的使用 Eslint 链接npm模块管理器 npm 模块安装机制简介 vue-cli: https://cli.vuejs.org/zh/ vue-router: https://router.vuejs.org/zh/ vuex: https://vuex.vuejs.org/zh/guide/ vue-loader: https://vue-loader.vuejs.org/zh/spec.html vue-class-component https://github.com/vuejs/vue-class-component vue-property-decorator https://github.com/kaorun343/vue-property-decorator vuex-class https://github.com/ktsn/vuex-class/ Axios.js https://github.com/axios/axios Vue的TypeScript支持 https://cn.vuejs.org/v2/guide/typescript.html 语义化版本 https://semver.org/lang/zh-CN/ TypeScript+ Vue起步 https://github.com/Microsoft/TypeScript-Vue-Starter#typescript-vue-starter https://github.com/SimonZhangITer/vue-typescript-dpapp-demo vue + typescript 项目起手式 https://segmentfault.com/a/1190000011744210 https://segmentfault.com/a/1190000011878086 https://github.com/ws456999/vue-typescript-starter https://www.zhihu.com/question/310485097","tags":[{"name":"Vue","slug":"Vue","permalink":"https://betgar.github.io/tags/Vue/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://betgar.github.io/tags/TypeScript/"}]},{"title":"Backbone.marionette新手入门系列","date":"2018-10-24T10:00:00.000Z","path":"2018/10/24/the-beginner-of-backbone-marionette/","text":"Backbone.marionette新手入门系列 已经不用了,所以弃坑。 initialize(options, args...)1234567const MyObject = MnObject.extend({ initialize(options, arg2) { console.log(options.foo, this.getOption('foo'), arg2); }});const myObject = new MyObject({ foo: 'bar' }, 'baz'); // logs \"bar\" \"bar\" \"baz\" extend12// Backbone.extend === _.extendconst view = new MyView(); triggerMethod(methodName, args...)123456789101112const MyObject = MnObject.extend({ initialize(){ this.triggerMethod('foo', 'baz'); }, onFoo(bar){ console.log(bar); }});const myObj = new MyObject(); // console.log \"baz\"myObj.triggerMethod('foo', 'qux'); // console.log \"qux\" bindEvents(entity, events)12345678910111213141516import Radio from 'backbone.radio';import { View } from 'backbone.marionette';const MyView = View.extend({ fooEvents: { 'change:foo': 'doSomething' }, initialize(){ this.fooChannel = Radio.channel('foo'); this.bindEvents(this.fooChannel, this.fooEvents); }, doSomething(){ // the \"change:foo\" event was fired from the radio channel // respond to it appropriately, here. }}); unbindEvents(entity, [hashMap])bindRequests(entity, hashMap) bindRequests:应该叫bindReply更贴切,绑定的是响应处理函数 hashMap => { 'request:name': 'replyHandler' } unbindRequests(channel, [hashMap]) { 'request:name': 'replyHandler' } normalizeMethods(hashMap)123456const hash = { 'action:one': 'handleActionOne', // This will become a reference to `this.handleActionOne` 'action:two': this.handleActionTwo };this.normalizedHash = this.normalizeMethods(hash); getOption(optionName) 获取定义在instance上的option,如果instance没有,则优雅降级从option上查找。 123456789101112131415161718const View = View.extend({ classVal: 'class value', initialize(){ this.instanceVal = 'instance value' }});const view = new View({ optVal: 'option value' });view.getOption('instanceVal'); // instance valueview.getOption('classVal'); // class valueview.getOption('optVal'); // option valueconst view2 = new View({ instanceVal: 'foo', classVal: 'bar', optVal: 'baz' });view.getOption('instanceVal'); // fooview.getOption('classVal'); // barview.getOption('optVal'); // baz Falsey values getOption将返回任何falsey values而不是undefined,如果getOption在options中无法获取属性,将会直接从instance实例中读取。 mergeOptions(options, props) 把options中的props中的属性合并到instances实例上 1234567891011121314151617const MyObject = MnObject.extend({ initialize(options) { // this.mergeOptions(options, ['model', 'something']); // this.model and this.something will now be available }});const myObject = new MyObject({ model: new Backbone.Model(), something: 'test', another: 'value'});console.log(myObject.model);console.log(myObject.something);console.log(myObject.getOption('another')); options initialize中的options和对象instance实例化的options一样,所以在initialize中使用options,有必要使用getOption获取. 12345678910111213141516const MyObject = MnObject.extend({ options: { foo: 'bar', another: 'thing' }, initialize(options) { console.log(options.foo) // undefined console.log(this.getOption('foo')) // 'bar' console.log(this.getOption('another')) // 'value' }});const myObject = new MyObject({ another: 'value'}); Marionette.View 属性: el, id, tagName,attributes,className model, modelEvents, collection, collectionEvents events, triggers template, templateContext childViewEventPrefix,childViewEvents, childViewTriggers behaviors, ui regionClass, regions 注意: 因为所有的events都在el上代理,所以如果events中的selector和childViewEvents中的selector相同时,events中的handle能够同时响应events和childViewEvents中触发的事件,解决这个问题很简单,在childViewEvents的handler中阻止事件冒泡传播 render Marionette.View.render不能被override重写,只能通过before:render events Backbone用来绑定DOM事件 triggers 转换DOM事件=>Marionette Event,就是把DOM Event变为通用的发布订阅事件,这样外部对象就和具体的DOM结构解耦 场景适用:childViewEvents => parentView主要是解决父子view之间的事件传递. 作用:类似Backbone.Model对象,自身定义了很多Entity Event,entity event会在model的属性变化时触发;而triggers中定义的就是DOM事件到Class Event的一个映射关系,所以handler,可以是view内部(和events效果一样),或者外部其它的Marionette对象也可以处理。 123456789101112const MyView = View.extend({ triggers: { 'click a': 'click:link' }, onClickLink(view, event) { console.log('Show the modal'); }});// 也可以手动触发MyView.triggerMethod('click a'); // onClickLink childViewEvents ui 就是query出view context渲染之后的范围内DOM Element(this.$(selector))","tags":[{"name":"backbone","slug":"backbone","permalink":"https://betgar.github.io/tags/backbone/"},{"name":"marionette","slug":"marionette","permalink":"https://betgar.github.io/tags/marionette/"}]},{"title":"jQuery Validation使用总结","date":"2018-10-09T08:00:00.000Z","path":"2018/10/09/how-to-use-jquery-validation/","text":"jQuery Validation使用总结 注意:表单元素必须含有name属性,不然jQuery validation不工作。 options配置 jQuery.validator.defaults 1234567891011121314151617181920212223242526272829{ debug : false, messages: {}, groups: {}, rules: {}, errorClass: \"error\", pendingClass: \"pending\", validClass: \"valid\", errorElement: \"label\", focusCleanup: false, focusInvalid: true, errorContainer: $( [] ), errorLabelContainer: $( [] ), onsubmit: true, // 提交表单时验证 ignore: \":hidden\", // 需要忽略不验证的字段,可以自己写selector ignoreTitle: false, // 不使用表单元素的title当提示信息 onfocusin: function( element ) { }, onfocusout: function( element ) { }, onkeyup: function( element, event ) { }, onclick: function( element ) { }, highlight: function( element, errorClass, validClass ) { }, unhighlight: function( element, errorClass, validClass ) { }} 方法 jQuery.validator有三类方法: $.validator的静态方法 12> $.validator.format('请输入至少{0}个字符');> $('选择器').validate()返回的$.validator实例方法 123> var validator = $('form').validate();> validator.form();> 插件的方法 1234> $().validate();> $().valid();> $().rules();> 自定义的选择器 插件扩展了selector选择器。 :blink :filled :unchecked Validator实例的方法 Validator实例的公共方法。 form() 触发校验. 12345var formValidator = $('form').validate();if (formValidator.form()) { console.log('valid form'); } element(element) 校验单个表单元素 1234var formValidator = $('form').validate();if (formValidator.element('#startDate')){ console.log('valid element'); } resetForm()12var formValidator = $('form').validate();formValidator.resetForm(); showErrors(errors) errors是Object类型, key是表单元素的name属性,value是要显示的消息 1234var formValidator = $('form').validate();formValidator.showErrors({ firstName: '请输入名字'}); numberOfInvalids([errors]) 返回校验不通过的元素个数 12345var validator = $( \"#myform\" ).validate({ invalidHandler: function() { $( \"#summary\" ).text( validator.numberOfInvalids() + \" field(s) are invalid\" ); }}); destory() 销毁Validator实例 12var validator = $( \"#myform\" ).validate();validator.destroy(); Validator的静态方法$.validator.addMethod( name, method [, message ] )123456// value: 元素值// element:元素本身// params:元素规则jQuery.validator.addMethod(\"math\", function(value, element, params) { return this.optional(element) || value == params[0] + params[1];}, jQuery.validator.format(\"Please enter the correct value for {0} + {1}\")); $.validator.format( template, argument, argumentN… )123var template = jQuery.validator.format(\"{0} is not a valid value\");// later, results in 'abc is not a valid value'alert(template(\"abc\")); $.validator.setDefaults(options) options参考validate(options) 重置全局validator配置 123jQuery.validator.setDefaults({ debug: true}); $.validator.addClassRules() 添加校验规则。 1234567891011121314151617jQuery.validator.addClassRules(\"name\", { required: true, minlength: 2});jQuery.validator.addClassRules({ name: { required: true, minlength: 2 }, zip: { required: true, digits: true, minlength: 5, maxlength: 5 }}); Validator内置的方法required 适用: input, select, checkbox, radio 1234// 内部方法签名required();required(selector);required(callback); input 123456<form id=\"myform\"> <label for=\"field\">Required: </label> <input type=\"text\" class=\"left\" id=\"field\" name=\"field\"> <br/> <input type=\"submit\" value=\"Validate!\"></form> 12345678// boolean$( \"#myform\" ).validate({ rules: { field: { required: true } }}); Select 1234567891011<form id=\"myform\"> <label for=\"fruit\">Please select a fruit</label> <select id=\"fruit\" name=\"fruit\" title=\"Please select something!\"> <option value=\"\">Select...</option> <option value=\"1\">Banana</option> <option value=\"2\">Apple</option> <option value=\"3\">Peach</option> </select> <br/> <input type=\"submit\" value=\"Validate!\"></form> 1234567$( \"#myform\" ).validate({ rules: { fruit: { required: true } }}); radio 12345678910111213<form id=\"myform\"> <label for=\"gender_male\"> <input type=\"radio\" id=\"gender_male\" value=\"m\" name=\"gender\" /> Male </label> <label for=\"gender_female\"> <input type=\"radio\" id=\"gender_female\" value=\"f\" name=\"gender\"/> Female </label> <label for=\"gender\" class=\"error\">Please select your gender</label> <br/> <input type=\"submit\" value=\"Validate!\"></form> 1234567$( \"#myform\" ).validate({ rules: { gender: { required: true } }}); checkbox 123456<form id=\"myform\"> <label for=\"agree\">Please agree to our policy</label> <input type=\"checkbox\" id=\"agree\" title=\"Please agree to our policy!\" name=\"agree\" /> <br/> <input type=\"submit\" value=\"Validate!\"></form> 1234567$( \"#myform\" ).validate({ rules: { agree: { required: true } }}); 依赖其它元素(dependence-expression) 12345678<form id=\"myform\"> <label for=\"other\">Check to make next field required</label> <input id=\"other\" type=\"checkbox\"> <br> <input id=\"details\" name=\"details\"> <br> <input type=\"submit\" value=\"Validate!\"></form> 1234567$( \"#myform\" ).validate({ rules: { details: { required: \"#other:checked\" } }}); function 123456789<form id=\"myform\"> <label>Age </label> <input id=\"age\" name=\"age\"> <br> <label>Parent </label> <input id=\"parent\" name=\"parent\"> <br> <input type=\"submit\" value=\"Validate!\"></form> 12345678910111213$( \"#myform\" ).validate({ rules: { age: { required: true, min: 3 }, parent: { required: function(element) { return $(\"#age\").val() < 13; } } }}); minlength(length)12345678$( \"#myform\" ).validate({ rules: { field: { required: true, minlength: 3 } }}); maxlength(length)12345678$( \"#myform\" ).validate({ rules: { field: { required: true, maxlength: 4 } }}); ranglength(Array)12345678$( \"#myform\" ).validate({ rules: { field: { required: true, rangelength: [2, 6] } }}); min(value)12345678$( \"#myform\" ).validate({ rules: { field: { required: true, min: 13 } }}); max(value)12345678$( \"#myform\" ).validate({ rules: { field: { required: true, max: 13 } }}); range( range )12345678$( \"#myform\" ).validate({ rules: { field: { required: true, range: [13, 23] } }}); step( value ) 输入的数字是step的倍数 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, step: 10 } }}); email12345678$( \"#myform\" ).validate({ rules: { field: { required: true, email: true } }}); url12345678$( \"#myform\" ).validate({ rules: { field: { required: true, url: true } }}); number12345678$( \"#myform\" ).validate({ rules: { field: { required: true, number: true } }}); digits12345678$( \"#myform\" ).validate({ rules: { field: { required: true, digits: true } }}); equalTo(Selector)12345678$( \"#myform\" ).validate({ rules: { password: \"required\", password_again: { equalTo: \"#password\" } }}); dateISO 字段是合法时间,非格式。 适用:input 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, dateISO: true } }}); additional-methods 包含在additional-methods.js accept(mimetype) 校验文件后缀 适用:<input type="file"> 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, accept: \"audio/*\" } }}); creditcard() 校验信用卡号 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, creditcard: true } }}); phoneUS 可以参考实现电话校验 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, phoneUS: true } }}); require_from_group 一组至少填写几个。 123456789101112<form id=\"myform\"> <label for=\"mobile_phone\">Mobile phone: </label> <input class=\"left phone-group\" id=\"mobile_phone\" name=\"mobile_phone\"> <br/> <label for=\"home_phone\">Home phone: </label> <input class=\"left phone-group\" id=\"home_phone\" name=\"home_phone\"> <br/> <label for=\"work_phone\">Work phone: </label> <input class=\"left phone-group\" id=\"work_phone\" name=\"work_phone\"> <br/> <input type=\"submit\" value=\"Validate!\"></form> 12345678910111213$( \"#myform\" ).validate({ rules: { mobile_phone: { require_from_group: [1, \".phone-group\"] }, home_phone: { require_from_group: [1, \".phone-group\"] }, work_phone: { require_from_group: [1, \".phone-group\"] } }}); extension( [extension ] ) 参数:string 默认值: png|jpe?g|gif 适用:<input> 12345678$( \"#myform\" ).validate({ rules: { field: { required: true, extension: \"xls|csv\" } }}); 重构规则(组合规则) 重构规则,可以把多个规则组合起来,一起使用。 123456789// alias required to cRequired with new message$.validator.addMethod(\"cRequired\", $.validator.methods.required, \"Customer name required\");// alias minlength, too$.validator.addMethod(\"cMinlength\", $.validator.methods.minlength, // leverage parameter replacement for minlength, {0} gets replaced with 2 $.validator.format(\"Customer name must have at least {0} characters\"));// combine them both, including the parameter for minlength$.validator.addClassRules(\"customer\", { cRequired: true, cMinlength: 2 }); 错误消息 错误消息有四种提供方式: options, data-*, element title, default messages 错误消息的优先级:options > element title属性 > default messages 可以使用统一消息,也可以指定具体的规则消息 12<input required data-msg=\"Please fill this field\"><input data-rule-minlength=\"2\" data-rule-maxlength=\"4\" data-msg-minlength=\"At least two chars\" data-msg-maxlength=\"At most fours chars\"> 跳过验证提交 添加formnovalidate属性。 1<input type=\"submit\" formnovalidate name=\"cancel\" value=\"Cancel\"> 验证事件 默认情况下,form在用户点击submit按钮,或者input获得焦点时,按下enter键。 另外,如果一个field已经高亮并且invalid,当用户输入任何东西,它都会被验证(使用onkeyup). 当用户在一个已经验证为valid的field中输入任何东西,在field失去焦点时被验证(onfoucsout)。 normalizer normalizer: 是非常有用的一个方法,因为form中的组件,不可能全是原生的组件,所以提供这个方法,用来返回组件的值。 注意: normalizer:不能改变组件的值,只能把改变后的值,给validator进行校验。 参数value,是validator内部获取element的值,在使用第三方组件时,很可能不正确 this时当前被校验的DOMelement 返回值必须时一个string类型 12345678910$( \"#myform\" ).validate( { rules: { field: { required: true, normalizer: function( value ) { return dayjs($(this).datepicker('getDate')).format('YYYY-MM-DD'); } } }} ); 1234567891011121314151617181920// 全局的normalizer和local的normalizer同时使用$( \"#myform\" ).validate( { normalizer: function( value ) { return $.trim( value ); }, rules: { username: { required: true }, url_input: { required: true, url: true, normalizer: function( value ) { var url = value; // 可以修改 return 'https://' + url; } } }} );","tags":[{"name":"Validation","slug":"Validation","permalink":"https://betgar.github.io/tags/Validation/"}]},{"title":"requirejs的快速入门","date":"2018-09-11T16:00:00.000Z","path":"2018/09/12/the-quick-start-of-requirejs/","text":"requirejs的快速入门 阮一峰老师的入门系列。 RequireJS和AMD规范 Javascript模块化编程(一):模块的写法 Javascript模块化编程(二):AMD规范 Javascript模块化编程(三):require.js的用法 AMD规范 AMD规范对loader,loader plugins, require有详细的规定。详情见 AMD 规范wiki,这里还有中文版。 define函数define(id?, dependencies?, factory) require, exports, module 三个模块是顶级模块。 例子: 创建一个名为”alpha”的模块,使用了require,exports,和名为”beta”的模块: 1234567define(\"alpha\", [\"require\", \"exports\", \"beta\"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //Or: return require(\"beta\").verb(); }}); 一个返回对象的匿名模块: 1234567define([\"alpha\"], function (alpha) { return { verb: function(){ return alpha.verb() + 2; } };}); 一个没有依赖性的模块可以直接定义对象: 12345define({ add: function(x, y){ return x + y; }}); 一个使用了简单CommonJS转换的模块定义: 123456define(function (require, exports, module) { var a = require('a'), b = require('b'); exports.action = function () {};}); require函数1234567891011define(['require'], function (require) { //the require in here is a local require.});define(function (require, exports, module) { //the require in here is a local require.});define(function () { var $ = require('jquery'); // 全局require}); 全局require函数作用于全局,和define()类似。 全局require和局部require有着相同的行为,包含以下特征: 模块ID应该认为是一个绝对的模块名称,而不是相对另一个模块的ID。 只有在异步的时候,才可以使用require(id, callback?)的回调形式。因为异步加载模块的方式是先发出一个异步请求,然后等主线程代码段执行完毕才能进行异步回调来处理加载好的模块。 require(String) 如果模块没有加载或者执行完成,就会抛出错误。特别需要指出的是,在同步加载的回调中,如果模块没有加载完成,禁止动态的获取模块,否则,就会抛出异常。 使用define()定义模块时,依赖项中可以找到一个AMD模块: 123define(function (require) { var a = require('a'); }); require(Array, Function) 根据参数,同步地返回模块ID所代表的模块。 参数Array是一个由模块ID组成的数组。当模块ID所以代表的模块加载完成且可用时,回调函数Function才开始执行,并且只被执行一次。各个模块按照依赖数组中的位置顺序以参数的形式传入到Function里。 12345define(function (require) { require(['a', 'b'], function (a, b) { //modules a and b are now available for use. }); }); require.toUrl(String) 将形如[module ID] + ‘.extension’这种字符形式转化成URL路径。 require.toUrl()方法采用通用的模块ID路径转化规则,将模块ID字符解析成URL路径.但它不支持以”.js”这种扩展形式。所以,我们必须将’.extension’添加到了解析路径里。 1234567//cart.js contents: define (function(require) { // 模块ID名 './templates/a' // 扩展名 '.html' // 模板路径大致以这样的形式结尾 'modules/cart/templates/a.html' var templatePath = require.toUrl('./templates/a.html'); }); config配置公共项: 模块id前缀: 1some/very/long/name 例如:以下都是模块id前缀: some some/very some/very/long some/very/long/name 模块id前缀的第一个段: 模块id,从第一个 /作为分割,之前的字符串部分全是。 baseUrl 字符串类型。给id到path转换指定一个根目录 。 123{ baseUrl: './js'} paths 对象类型。给模块id前缀(module ID prefix)显式指定一个路径(path)。 paths对象的属性是一个绝对的模块ID前缀(module ID prefix),属性值可以是: 字符串:字符串值是一个相对于baseUrl的路径。也可以是一个绝对路径,只要以 /或者//或者http开头。 数组:可选。提供一个可选的容错功能。如果数组第一个路径加载失败,则可以尝试从下一个路径加载,以此类推。 12345678910// 路径: js/lib/jquery.js{ paths: { jquery: './lib/jquery' bootstrap: [ 'https://cdn/bootstrap', './lib/bootstrap' ] }} packagespackage 配置对象的值是一个数组。因为CommonJS规范和AMD规范的loader查找路径方式的差异,所以package配置对象是为了使用实现了CommonJS规范的包。 默认的查找规则:baseUrl + 'module/id' + .js,其中paths配置可以用来映射module/id到另一个路径。 包的查找规则:使用了一个packageName。所以一个包的主模块就是baseUrl + 'packageName' + .js main.js默认作为一个package的入口. 1baseUrl + (packageConfig.location || 'packageName') + '/' + (packageConfig.main || 'main') + '.js' 对于packageName/otherId,规则和paths相似,但是packageConfig是这样实现的: 1baseUrl + (packageConfig.location || 'packageName') + '/' + 'otherId' + '.js' package 配置对象可以是: 字符串:例如:bootstrap/modal:其中bootstrap是packageName名称,boostrap/modal也是一个packageName。依赖bootstrap/modal时,查找规则如下: 1baseUrl + `bootstrap/modal` + main.js` 对象: a configuration object that can have the following properties: name: 字符串. 解析规则同上述字符串解析规则一样. location: 字符串,可选.相对于baseUrl的路径。 main: 字符串. 可选.指定包入口。 默认为main 12345678910{ packages: [ 'main', { name: 'dojo', location: 'dojo/1.7.1', main:'main' } ]} config 用来模块间传递一些信息使用的。 1234567{ config: { 'some/module/id': { limit: 40 } }} 123define(['some/module/id'], function (someModule) { someModule.config().limit === 40 // true}); shim 解决那些非AMD模块之间的依赖。 123456789101112131415161718192021{ shim: { //This version just specifies the //dependency for the jQuery plugin. //Some implementations may be able to do //better error correction in older IE if //you specify the exports string value, //which in this case would be //exports: 'jQuery.fn.rotator' 'jquery.rotator': ['jquery'], underscore: { exports: '_' }, backbone: { deps: ['jquery', 'underscore'], exports: 'Backbone' } }} map 解决模块间同时依赖不同版本的问题。 限制: 这个功能只能用于那些,真正使用define()定义的AMD 模块并且是匿名模块. 12345678910{ map: { '*': { 'foo': 'foo1.2' }, 'some/oldmodule': { 'foo': 'foo1.0' } }} 12345678910{ map: { 'some/newmodule': { 'foo': 'foo1.2' }, 'some/oldmodule': { 'foo': 'foo1.0' } }} requirejs 路径解析:baseUrl+paths; / , http:或者https:,.js结尾的除外。 requirejs加载脚本非依赖顺序加载,执行时是顺序执行。 data-main入口define函数定义一个对象123define({ name: 'test'}); 定义一个函数1234567define(function () { // 初始化工作 return { name: 'test' };}); requirejs.undef 使用的场景比较少,主要在模块加载失败时使用较多 1234567891011121314151617181920212223242526272829303132333435363738// 官方的demorequirejs.config({ enforceDefine: true, paths: { jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min' }});//Laterrequire(['jquery'], function ($) { //Do something with $ here}, function (err) { //The errback, error callback //The error has a list of modules that failed var failedId = err.requireModules && err.requireModules[0]; if (failedId === 'jquery') { //undef is function only on the global requirejs object. //Use it to clear internal knowledge of jQuery. Any modules //that were dependent on jQuery and in the middle of loading //will not be loaded yet, they will wait until a valid jQuery //does load. requirejs.undef(failedId); //Set the path to jQuery to local path requirejs.config({ paths: { jquery: 'local/jquery' } }); //Try again. Note that the above require callback //with the \"Do something with $ here\" comment will //be called if this new attempt to load jQuery succeeds. require(['jquery'], function () {}); } else { //Some other error. Maybe show message to the user. }}); requirejs.config requirejs的配置项,内容较多,参考reqquirejs.org require.toUrl 获取模块的路径。 1234// 官方demodefine([\"require\"], function(require) { var cssUrl = require.toUrl(\"./style.css\");});","tags":[{"name":"AMD","slug":"AMD","permalink":"https://betgar.github.io/tags/AMD/"},{"name":"requirejs","slug":"requirejs","permalink":"https://betgar.github.io/tags/requirejs/"}]},{"title":"adminlte-theme-zh-cn-docs","date":"2018-09-09T08:52:00.000Z","path":"2018/09/09/adminlte-theme-zh-cn-docs/","text":"adminlte主题中文文档 文件结构 12345678910111213141516File Hierarchy of the Source Code Package AdminLTE/ ├── dist/ │ ├── CSS/ │ ├── JS │ ├── img ├── build/ │ ├── less/ │ │ ├── AdminLTE's Less files │ └── Bootstrap-less/ (Only for reference. No modifications have been made) │ ├── mixins/ │ ├── variables.less │ ├── mixins.less └── plugins/ ├── All the customized plugins CSS and JS files 布局 .wrapper.一个div包裹着整个网站。 .main-header. 包含导航和logo。 .sidebar-wrapper. 包含用户面板和侧边栏。 .content-wrapper. 包含页面的头部和内容。 布局options AdminLTE 2.0提供了一组应用于主布局的选项。可以将这些类中的每一个添加到body标记中以获得所需的目标。 修复:使用该类.fixed获取固定标题和侧边栏。 折叠边栏:使用该类.sidebar-collapse在加载时折叠侧边栏。 盒装布局:使用该类.layout-boxed获得仅延伸至1250像素的盒装布局。 顶部导航使用该类.layout-top-nav删除侧边栏并在顶部导航栏中显示您的链接。 皮肤皮肤可以在dist / css / skins文件夹中找到。选择所需的外观文件,然后将相应的类添加到body标签以更改模板的外观。以下是可用皮肤的列表: 皮肤 skin-blue skin-blue-light skin-yellow skin-yellow-light skin-green skin-green-light skin-purple skin-purple-light skin-red skin-red-light skin-black skin-black-light adminlte JavaScirpt选项可以使用以下方法之一修改AdminLTE app.js的选项。 编辑app.js在主Javascript文件中,修改$.AdminLTE.options对象以适合您的用例。 定义AdminlteOptions在初始化app.js之前初始化,例如 12345678910111213<script> var AdminLTEOptions = { //Enable sidebar expand on hover effect for sidebar mini //This option is forced to true if both the fixed layout and sidebar mini //are used together sidebarExpandOnHover: true, //BoxRefresh Plugin enableBoxRefresh: true, //Bootstrap.js tooltip enableBSToppltip: true };</script><script src=\"dist/js/app.js\" type=\"text/javascript\"></script> 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899{ // 添加slimscroll.js到导航菜单,在每个页面的app.js加载之前 navbarMenuSlimscroll: true, // scroll bar的width navbarMenuSlimscrollWidth: \"3px\", // inner menu的height navbarMenuHeight: \"200px\", // 通用的动画速度,例如box的collapse/expend和sidebar treeview的slid up/down // 这个选项接受毫秒整数或者 fast, normal, slow animationSpeed: 500, // sidebar中的push menu 触发器的选择器 sidebarToggleSelector: \"[data-toggle='offcanvas']\", // 激活状态的sidebar的push menu sidebarPushMenu: true, // 如果设置fixed layout,激活sidebar的slimscroll sidebarSlimScroll: true, // 开启 sidebar mini的expand悬浮特效 // 这个选项在fixed layout和sidebar mini同时使用的时候强制设置为true sidebarExpandOnHover: false, // 开启 BoxRefresh 插件 enableBoxRefresh: true, // 开启bootstrap.js的tooltip插件 enableBSToppltip: true, // bootstrap tooltip的选择器 BSTooltipSelector: \"[data-toggle='tooltip']\", // 开启fast click, fastclick.js 在触控设备上创建了一个更原生的触控体验 // 如果你开启了,确保在app.js加载之前加载fastclick.js enableFastclick: true, //Control Sidebar Tree Views // 开启控制 sidebar tree views enableControlTreeView: true, // 开启 控制sidebar enableControlSidebar: true, // control sidebar options controlSidebarOptions: { // 触发sidebar open/close事件的button选择器 toggleBtnSelector: \"[data-toggle='control-sidebar']\", // sidebar 选择器 selector: \".control-sidebar\", // 开启slide over content slide: true }, // 开启这个选项允许box的collapsed 和 removed效果 enableBoxWidget: true, //Box Widget plugin options boxWidgetOptions: { boxWidgetIcons: { // 折叠的图标 collapse: 'fa-minus', // 展开图标 open: 'fa-plus', // 移除图标 remove: 'fa-times' }, boxWidgetSelectors: { // 移除按钮的选择器 remove: '[data-widget=\"remove\"]', // 折叠按钮的选择器 collapse: '[data-widget=\"collapse\"]' } }, // Direct Chat plugin options directChat: { // 开启direct chat插件 enable: true, // 开启或关闭聊天联系人窗口的button选择器 contactToggleSelector: '[data-widget=\"chat-pane-toggle\"]' }, //Define the set of colors to use globally around the website // 定一个站点的全局一个颜色集合 colors: { lightBlue: \"#3c8dbc\", red: \"#f56954\", green: \"#00a65a\", aqua: \"#00c0ef\", yellow: \"#f39c12\", blue: \"#0073b7\", navy: \"#001F3F\", teal: \"#39CCCC\", olive: \"#3D9970\", lime: \"#01FF70\", orange: \"#FF851B\", fuchsia: \"#F012BE\", purple: \"#8E24AA\", maroon: \"#D81B60\", black: \"#222222\", gray: \"#d2d6de\" }, //The standard screen sizes that bootstrap uses. //If you change these in the variables.less file, change //them here too. screenSizes: { xs: 480, sm: 768, md: 992, lg: 1200 } } 主标题主标题包含徽标和导航栏。导航栏的构造与Bootstrap略有不同,因为它具有Bootstrap不提供的组件。导航栏可以通过两种方式构建。这是普通导航栏的示例,接下来我们将提供顶部导航布局的示例。 侧边航栏123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140<header class=\"main-header\"> <a href=\"../../index2.html\" class=\"logo\"> <!-- LOGO --> AdminLTE </a> <!-- Header Navbar: style can be found in header.less --> <nav class=\"navbar navbar-static-top\" role=\"navigation\"> <!-- Navbar Right Menu --> <div class=\"navbar-custom-menu\"> <ul class=\"nav navbar-nav\"> <!-- Messages: style can be found in dropdown.less--> <li class=\"dropdown messages-menu\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\"> <i class=\"fa fa-envelope-o\"></i> <span class=\"label label-success\">4</span> </a> <ul class=\"dropdown-menu\"> <li class=\"header\">You have 4 messages</li> <li> <!-- inner menu: contains the actual data --> <ul class=\"menu\"> <li><!-- start message --> <a href=\"#\"> <div class=\"pull-left\"> <img src=\"dist/img/user2-160x160.jpg\" class=\"img-circle\" alt=\"User Image\"> </div> <h4> Sender Name <small><i class=\"fa fa-clock-o\"></i> 5 mins</small> </h4> <p>Message Excerpt</p> </a> </li><!-- end message --> ... </ul> </li> <li class=\"footer\"><a href=\"#\">See All Messages</a></li> </ul> </li> <!-- Notifications: style can be found in dropdown.less --> <li class=\"dropdown notifications-menu\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\"> <i class=\"fa fa-bell-o\"></i> <span class=\"label label-warning\">10</span> </a> <ul class=\"dropdown-menu\"> <li class=\"header\">You have 10 notifications</li> <li> <!-- inner menu: contains the actual data --> <ul class=\"menu\"> <li> <a href=\"#\"> <i class=\"ion ion-ios-people info\"></i> Notification title </a> </li> ... </ul> </li> <li class=\"footer\"><a href=\"#\">View all</a></li> </ul> </li> <!-- Tasks: style can be found in dropdown.less --> <li class=\"dropdown tasks-menu\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\"> <i class=\"fa fa-flag-o\"></i> <span class=\"label label-danger\">9</span> </a> <ul class=\"dropdown-menu\"> <li class=\"header\">You have 9 tasks</li> <li> <!-- inner menu: contains the actual data --> <ul class=\"menu\"> <li><!-- Task item --> <a href=\"#\"> <h3> Design some buttons <small class=\"pull-right\">20%</small> </h3> <div class=\"progress xs\"> <div class=\"progress-bar progress-bar-aqua\" style=\"width: 20%\" role=\"progressbar\" aria-valuenow=\"20\" aria-valuemin=\"0\" aria- valuemax=\"100\"> <span class=\"sr-only\">20% Complete</span> </div> </div> </a> </li><!-- end task item --> ... </ul> </li> <li class=\"footer\"> <a href=\"#\">View all tasks</a> </li> </ul> </li> <!-- User Account: style can be found in dropdown.less --> <li class=\"dropdown user user-menu\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\"> <img src=\"dist/img/user2-160x160.jpg\" class=\"user-image\" alt=\"User Image\"> <span class=\"hidden-xs\">Alexander Pierce</span> </a> <ul class=\"dropdown-menu\"> <!-- User image --> <li class=\"user-header\"> <img src=\"dist/img/user2-160x160.jpg\" class=\"img-circle\" alt=\"User Image\"> <p> Alexander Pierce - Web Developer <small>Member since Nov. 2012</small> </p> </li> <!-- Menu Body --> <li class=\"user-body\"> <div class=\"col-xs-4 text-center\"> <a href=\"#\">Followers</a> </div> <div class=\"col-xs-4 text-center\"> <a href=\"#\">Sales</a> </div> <div class=\"col-xs-4 text-center\"> <a href=\"#\">Friends</a> </div> </li> <!-- Menu Footer--> <li class=\"user-footer\"> <div class=\"pull-left\"> <a href=\"#\" class=\"btn btn-default btn-flat\">Profile</a> </div> <div class=\"pull-right\"> <a href=\"#\" class=\"btn btn-default btn-flat\">Sign out</a> </div> </li> </ul> </li> </ul> </div> </nav></header> 顶部导航布局 要使用此主标题而不是常规标题,必须将该layout-top-nav类添加到body标记中。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<header class=\"main-header\"> <nav class=\"navbar navbar-static-top\"> <div class=\"container-fluid\"> <div class=\"navbar-header\"> <a href=\"../../index2.html\" class=\"navbar-brand\"><b>Admin</b>LTE</a> <button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#navbar-collapse\"> <i class=\"fa fa-bars\"></i> </button> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class=\"collapse navbar-collapse\" id=\"navbar-collapse\"> <ul class=\"nav navbar-nav\"> <li class=\"active\"> <a href=\"#\">Link <span class=\"sr-only\">(current)</span></a></li> <li><a href=\"#\">Link</a></li> <li class=\"dropdown\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\">Dropdown <span class=\"caret\"></span></a> <ul class=\"dropdown-menu\" role=\"menu\"> <li><a href=\"#\">Action</a></li> <li><a href=\"#\">Another action</a></li> <li><a href=\"#\">Something else here</a></li> <li class=\"divider\"></li> <li><a href=\"#\">Separated link</a></li> <li class=\"divider\"></li> <li><a href=\"#\">One more separated link</a></li> </ul> </li> </ul> <form class=\"navbar-form navbar-left\" role=\"search\"> <div class=\"form-group\"> <input type=\"text\" class=\"form-control\" id=\"navbar-search-input\" placeholder=\"Search\"> </div> </form> <ul class=\"nav navbar-nav navbar-right\"> <li><a href=\"#\">Link</a></li> <li class=\"dropdown\"> <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\">Dropdown <span class=\"caret\"></span></a> <ul class=\"dropdown-menu\" role=\"menu\"> <li><a href=\"#\">Action</a></li> <li><a href=\"#\">Another action</a></li> <li><a href=\"#\">Something else here</a></li> <li class=\"divider\"></li> <li><a href=\"#\">Separated link</a></li> </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> </header> sidebar12345678910111213141516171819202122232425262728293031323334353637383940414243444546<div class=\"main-sidebar\"> <!-- Inner sidebar --> <div class=\"sidebar\"> <!-- user panel (Optional) --> <div class=\"user-panel\"> <div class=\"pull-left image\"> <img src=\"dist/img/user2-160x160.jpg\" class=\"img-circle\" alt=\"User Image\"> </div> <div class=\"pull-left info\"> <p>User Name</p> <a href=\"#\"><i class=\"fa fa-circle text-success\"></i> Online</a> </div> </div><!-- /.user-panel --> <!-- Search Form (Optional) --> <form action=\"#\" method=\"get\" class=\"sidebar-form\"> <div class=\"input-group\"> <input type=\"text\" name=\"q\" class=\"form-control\" placeholder=\"Search...\"> <span class=\"input-group-btn\"> <button type=\"submit\" name=\"search\" id=\"search-btn\" class=\"btn btn-flat\"> <i class=\"fa fa-search\"></i> </button> </span> </div> </form><!-- /.sidebar-form --> <!-- Sidebar Menu --> <ul class=\"sidebar-menu\"> <li class=\"header\">HEADER</li> <!-- Optionally, you can add icons to the links --> <li class=\"active\"><a href=\"#\"><span>Link</span></a><</li> <li><a href=\"#\"><span>Another Link</span></a></li> <li class=\"treeview\"> <a href=\"#\"><span>Multilevel</span> <i class=\"fa fa-angle-left pull-right\"></i></a> <ul class=\"treeview-menu\"> <li><a href=\"#\">Link in level 2</a></li> <li><a href=\"#\">Link in level 2</a></li> </ul> </li> </ul><!-- /.sidebar-menu --> </div><!-- /.sidebar --></div><!-- /.main-sidebar --> 控制边栏控制侧栏是右侧栏。它可以用于多种用途,并且非常容易创建。侧边栏有两种不同的显示/隐藏样式。第一个允许侧边栏在内容上滑动。第二个推送内容为侧边栏腾出空间。可以通过Javascript选项设置这些方法中的任何一个。 以下代码应放在.wrapperdiv中。我更喜欢把它放在页脚之后。 1234567<!-- The Right Sidebar --><aside class=\"control-sidebar control-sidebar-dark\"><!-- Content of the sidebar goes here --></aside><!-- The sidebar's background --><!-- This div must placed right after the sidebar for it to work--><div class=\"control-sidebar-bg\"></div> 1234567<!-- The Right Sidebar --><aside class=\"control-sidebar control-sidebar-light\"><!-- Content of the sidebar goes here --></aside><!-- The sidebar's background --><!-- This div must placed right after the sidebar for it to work--><div class=\"control-sidebar-bg\"></div> 创建侧边栏后,您需要一个切换按钮来打开/关闭它。通过将属性添加data-toggle="control-sidebar"到任何按钮,它将自动充当切换按钮。 切换按钮示例 1<button class=\"btn btn-default\" data-toggle=\"control-sidebar\">Toggle Right Sidebar</button> 信息框信息框用于显示统计代码段。有两种类型的信息框。 第一类信息框 infobox 标记 12345678<div class=\"info-box\"> <!-- Apply any bg-* class to to the icon to color it --> <span class=\"info-box-icon bg-red\"><i class=\"fa fa-star-o\"></i></span> <div class=\"info-box-content\"> <span class=\"info-box-text\">Likes</span> <span class=\"info-box-number\">93,139</span> </div><!-- /.info-box-content --> </div><!-- /.info-box --> 第二类信息框 123456789101112131415<!-- Apply any bg-* class to to the info-box to color it --> <div class=\"info-box bg-red\"> <span class=\"info-box-icon\"><i class=\"fa fa-comments-o\"></i></span> <div class=\"info-box-content\"> <span class=\"info-box-text\">Likes</span> <span class=\"info-box-number\">41,410</span> <!-- The progress section is optional --> <div class=\"progress\"> <div class=\"progress-bar\" style=\"width: 70%\"></div> </div> <span class=\"progress-description\"> 70% Increase in 30 Days </span> </div><!-- /.info-box-content --> </div><!-- /.info-box --> 您需要更改为在这些样式之间切换的唯一方法是更改bg- 类的位置。对于第一个样式,将任何bg- 类应用于图标本身。对于其他样式,将bg- *类应用于info-box div。 框框组件是整个模板中使用最广泛的组件。您可以将它用于从显示图表到文本块的任何内容。它有许多不同的风格,我们将在下面探讨。 默认Box标记12345678910111213141516<div class =“box”> <div class =“box-header with-border”> <h3 class =“box-title”>默认框示例</ h3> <div class =“box-tools pull-right”> <! - 按钮,标签和许多其他东西都可以放在这里! - > <! - 这是一个标签,例如 - > <span class =“label label-primary”>标签</ span> </ div> <! - /.box-tools - > </ div> <! - /.box-header - > <div class =“box-body”> 盒子的主体 </ div> <! - /.box-body - > <div class =“box-footer”> 盒子的页脚 </ div> <! - box-footer - > </ div> <! - / .box - > 123456<div class =“box box-default”> ... </ div> <div class =“box box-primary”> ... </ div> <div class =“box box-info”> ... </ div> <div class =“box box-warning”> ... </ div> <div class =“box box-success”> ... </ div> <div class =“box box-danger”> ... </ div> 实心盒子Solid Boxes是展示盒子的另一种方式。只需将box-solid类添加到box组件即可创建它们。您也可以将固定框与上下文类一起使用。 123456<div class =“box box-solid box-default”> ... </ div> <div class =“box box-solid box-primary”> ... </ div> <div class =“box box-solid box-info”> ... </ div> <div class =“box box-solid box-warning”> ... </ div> <div class =“box box-solid box-success”> ... </ div> <div class =“box box-solid box-danger”> ... </ div> 盒子工具框可以包含用于部署特定事件或提供简单信息的工具。以下示例在框的标题中使用了多个AdminLTE组件。 AdminLTE data-widget属性提供了可以折叠或删除的框。按钮放置在盒子工具中,盒子工具放置在盒子标题中。 1234<! - 这将导致单击时删除框 - ><button class =“btn btn-box-tool”data-widget =“remove”data-toggle =“tooltip”title =“Remove”> <i class =“fa fa-times”> </ i> </ button ><! - 这将导致单击框时折叠 - ><button class =“btn btn-box-tool”data-widget =“collapse”data-toggle =“tooltip”title =“Collapse”> <i class =“fa fa-minus”> </ i> </ button > 如果在app.js加载后在文档中插入了一个框,则必须通过调用.activateBox()以下方法显式激活折叠/删除按钮: 123<script> $(\"#box-widget\").activateBox();</script> Loading States要模拟加载状态,只需将此代码放在.box结束标记之前。 123<div class=\"overlay\"><i class=\"fa fa-refresh fa-spin\"></i></div>","tags":[{"name":"adminlte","slug":"adminlte","permalink":"https://betgar.github.io/tags/adminlte/"}]},{"title":"AMD最佳实践(requirejs)","date":"2018-08-03T09:00:00.000Z","path":"2018/08/03/AMD-best-practice/","text":"AMD最佳实践(requirejs) 一下内容全部来自网络。 AMD部分 阅读这部分文章的前提是你已经使用过AMD(requirejs)等异步加载库。 玩转AMD系列 写在前面 设计思路 应用实践 AMD - Loader 模块规划 模块化目录规范 包结构规范 模块和加载器规范 项目目录结构规范","tags":[{"name":"AMD","slug":"AMD","permalink":"https://betgar.github.io/tags/AMD/"},{"name":"requirejs","slug":"requirejs","permalink":"https://betgar.github.io/tags/requirejs/"}]},{"title":"egg.js和nestjs使用场景对比","date":"2018-08-02T10:00:00.000Z","path":"2018/08/02/eggjs-nestjs-usage-scenario-compare/","text":"egg.js和nestjs使用场景对比提醒大家:注意文章的时效性,毕竟不同的事件节点,框架成熟度不同。分享这篇文章主要是希望能够让大家认清框架的定位和应对的业务场景,框架没有好坏之分,只有是否适合自己。 原标题:关于nodejs-web框架的调研 作者:xingyuzhe 日期: Feb 25, 2018 原文: https://github.com/xingyuzhe/blog/issues/1 以下内容其实是补漏, 算是对1月份一点工作的总结。 加入了新团队, 初次接触, 粗略的查看了一些项目(nodejs server端), 发现存在几个问题: 对于新成立的团队,存在以上问题可以理解, 本次是讨论和解决第一个问题。 对于集团范围内而言,同种技术存在多种不同规范和框架很正常, 但是对于一个几十人的团队而言, 资源有限,还是集中力量统一一下效率高些, 有鉴于此, 觉得做一个种子项目比较合适: 一个企业定制框架, 共同维护, 同步更新, 信息共享和沉淀最佳实践。 跟领导聊了一下, 总结了一下当前状态团队对于这块内容的一些主观诉求: 框架实现够简单, 团队能全面掌控, 低风险。 给新手从基础开始学习的机会, 就是希望除了满足业务需求之外, 团队也能得到成长。 一个企业基础框架应满足的基本要求: 约束规范、扩展机制、安全、高效业务开发 最终还是希望能沉淀出一套自己的轮子 根据上面的要求, 筛选出了eggjs和nestjs2个web框架, 其中nestjs是团队一部分同学比较喜欢和尝试使用了的。 首先从外层信息做些调查。 我通读了eggjs和nestjs的文档,查阅了一些资料, 简单对比如下: – eggjs nestjs github stars 7014 4291 github forks 720 250 gitHub dependents 1591/305 0/0 npm search results 565 53+ github contributors 101 31 github core contributors 4(10+) 1(2+) github releases 49 19 github issues 86/1416 20/347 基础框架依赖 koa2 express 文档 业界良心 一般 核心原理 载入-挂载 模块容器-依赖注入(通过装饰器和元数据实现) 核心理念 微内核-插件机制 组件树-装饰器-流程控制 eggjs的特点和解决的主要问题: 通过载入代码-自动挂载代码到对象的方式解决了到处写import/require的问题, 不再需要手动维护模块之间的依赖关系, 核心就是一个loader, 虽然官方没有提及, 但是本人开下脑洞猜测, 这个模块的实现灵感有可能来自于坟头草已经一人高了的seajs, -_-, 这种挂载模式实现起来简单粗暴直接高效, 但是一个小问题就是在使用TS编写代码的时候, 会影响书写体验的流畅度, 毕竟不很OOP。 微核心 + 插件机制(这个理念其实一直以来都是代码组织的核心手段, 本人在以前开发播放器和IM客户端这块时感同身受), 大部分功能通过插件实现, 从而剥离非核心生命周期功能代码, 达到解耦/降低思维负担的目的; 另外一点就是跨模块API调用比较自由没有限制, 这个地方带来便捷的同时也有可能需要一定约束。 统一约束和规范, 对开发人员强约束, 保证不会出现千人千面的代码风格和设计。 种子项目 + 渐进式开发, 可以沉淀出自己的插件和业务框架、最佳实践 针对业务中遇到的常见问题基本都给出了解决方案, 插件丰富, 配套齐全 内部实现了进程间管理和通信功能 文档可是说是比较细致了, 基本把web这块涉及到的点都涵盖了, 仅仅通读文档对新手而言都会有不少收获 核心开发者较多, 属于团队项目, 整体看来比较严谨 沉淀时间较长, 正式发版2年, 实际发展了4年 明确定义了agent和app模式, 也实现了schedule功能 nestjs的特点和解决的主要问题: 通过模块容器-依赖注入维护组件树的模式解决了到处写import/require的问题, 不再需要手动维护模块之间的依赖关系, 另外编码方式与java十分相似。 组件树容器模式也达到了解耦效果, 实现方式上很大程度上受angular-module影响, 但是需要指定节点component的依赖关系, 跨模块调用严格依赖于声明. 其实这块功能github有独立的项目专做这个东西, 例如: InversifyJS | bottlejs, nestjs 自己实现了这个模块。 统一约束和规范, 对开发人员强约束 大量使用装饰器, 这个未支持的特性需要编译, 对代码阅读上可能会提高一些难度 流程控制上比较细致, 提出了filter、pipe、guard、interceptor这些明确的概念, 虽然这些工作在正常开发时也会做, 但是明确提出来并制定规范还是很有必要的 明确了Exception Layer的概念 默认使用/推荐TS开发 自带微服务实现 自带swagger接口文档生成功能 核心开发基本就作者一人, 属于个人项目, 提交记录比较难看, 前期的提交非常随便, 到后期才慢慢改善 2017年1月立项, 2017年5月发布第一个正式版本, 尚需时日印证 从上述情况来看, 2个框架都在123这几项做了工作, 完成了一个框架所应具备的基本诉求; 总体上看eggjs更加成熟和全面, 配套/生态相对完善的多, 低风险; nestjs则比较青涩和单一, 但是在组件树、流程控制、错误层级处理上有自己的特色, 理念上更加OOP,学习并吸收这些理念是很可取的, 但暂时不建议在核心产品线上投入使用。 eggjs详细阅读了核心相关代码, 粗略阅读了cluster等一些模块和插件的代码, 代码读起来总体比较流畅, 发现框架的核心实现非常简单明了: 框架基础依赖一个非常独立的loader模块, 功能就是加载代码文件和挂载函数到指定对象。 框架本身核心类只有6个: koa本身的application, context, response, request, egg自身新增的controller, service 这2个类, 后续所有的挂载动作都在这几个类上面进行 框架明确了app, framework, plugin3个概念, 依赖方式大概是这样: 框架启动的核心流程主要是这样: 由于eggjs自己实现了cluster, 自带进程管理和进程间通信功能, 所以egg自身部署时并不需要pm2这个工具, 官方文档上也对此做了解释。不过由于这块内容的引入(cluster还涉及到schedule、socket等功能),给框架本身带来了大量额外的代码和逻辑, 总体提升了框架的复杂度。 但是有些时候由于某些原因并不希望直接使用框架提供的cluster解决方案, 另外我查看了下pm2的API, 也是有进程间通信的API的, 当然用起来可能没有自定义实现时那样自由, 也尚未听说该API有被广泛使用过, 不过, cluster相关的内容能否作为一个扩展包, 而不是强耦合进框架核心流程中? 这样框架本身更加简单纯洁, 或者更容易被接受一些? 为此尝试抽离了eggjs的核心代码, 目标是仅保留最最核心的代码(移除cluster等周边代码), 具备egg的扩展机制和能正常直接使用eggjs的周边插件, 结果最后只需要几百行代码, 很少几个文件即可完成。后续在此基础上整理了一个上层框架, 预想作为业务项目的基础框架使用,主要涉及以下一些方面: 抽离eggjs的核心代码, 仅保留其插件机制和对应的约束规范 集成常用扩展函数、中间件、插件 集成多节点/进程下消息推送解决方案示例 集成schedule定时任务模块 之后花了点时间用这个上层框架开发了一个抽奖小项目, 开发体验还算流畅, 虽说是草量级项目, 不过也是五脏俱全, 作为example还挺适合。 nestjs 粗略查看了下nestjs的源码, nestjs的核心其实就是一个IoC模块管理容器的实现, 这块内容的逻辑实现作者处理的还是相对复杂的多, 这里吐槽下作者的代码组织方式和略显随意的注释大量的接口引用和糟糕的历史提交记录…,真是额外提高了阅读的难度. TypeScript的加持和作者本人的光环也不能阻挡这一点。言归正传, 这里说下它的核心原理和流程。 要想理解nestjs的源码先要理解和掌握以下知识: ES6的proxy,reflect TypeScript的decorator inversion of control (IoC)的基本概念 一定TypeScript基础. 如何通过decorator和元数据实现依赖注入 container类: 用于存储所有模块 scanner类: 递归提取出所有模块并存储到容器中; 提取出模块间的关联关系和模块自身的各种类以及内部联系,并存储到模块中; module类: 存储自身的关联模块、组件、可注入类、控制器类 injector类: 依赖注入的核心环节, 在所有模块的内容和关系都被扫描出后, 来创建实例, instanceLoader类: 使用Injector来加载模块的各种实例, 这个过程很复杂, 伴随递归和各种判断 这个IoC容器实现的核心流程是这样:scanner扫描所有module并提取关系存入module->module存入 container-> injector创建实例(依赖注入)-> instanceLoader加载实例 我们再拿nestjs实现上面eggjs版本lottery的例子, 大概是这样: 结合源码和实际开发体验来说: eggjs框架本身的模块管理(扩展机制)非常简单, 复杂度主要在于cluster这块内容和为此配套的周边设施(命令行工具、调试工具),但是这个复杂度是脱离于核心之外的东西。 nestjs的复杂度主要在于IoC模块管理器这块的实现上, 实际上这个东西理论上可以独立出来, 以此降低框架本身逻辑的复杂度。 从总体上讲, eggjs相对成熟, 更贴合实际开发需求。 nestjs的优势就是在一些细节上的约束和控制以及理念上的新颖(仅相对node-web框架而言), 但是这些并不是核心诉求。 另外eggjs团队做的工作内容相对于nestjs而言相当的多, 这些与人力、时间资源的投入是分不开的。 最后, 对于想要的新框架的处理结论已经有了: 1 社区模式(节省资源) 直接在eggjs基础上做一个上层框架, 吸收nestjs的一些优点, 作为插件/扩展内容去完善框架本身. 2 造轮子模式 eggjs插件机制 + nestjs流程控制 + component模式(可选) => 新轮子 3 所应具备的特性 约束规范 沉淀/扩展模式 模块依赖管理 集成typescript开发环境 集成API文档输出方案 环境配置 多节点/cluster解决方案 socket.io schedule 集成常用插件功能 logger cookie session security i18n redis sequelize multipart oauth onerror passport view role crypto 定义控制流/数据流约束规范 schema pipe interceptor guard migration 微服务 错误分级 监控 测试用例/代码覆盖率 调试 压力测试 补充:随着各种工具的发展, 加上eggjs使用也有一段时日,最初一些模糊的预感变得清晰:1 egg内置cluster模式带来的额外麻烦太多, 现在各种配套工具逐渐完善, 这个功能并没有什么用 2 egg模块隔离度不够但矛盾的是有时候又有限制, 简单的说就是代码组织模式上作为框架不够好, 需要自行定制上层规范; egg的是plugin模式, 相对于nest的component模式还是不够用; 3 egg的ts开发的流畅度差那么一点 4 实际的项目, 需要各种配套设施、工具、系统的协作联合,框架只是一个组成部分, 就应该只做它应该做的事就可以了 各有优劣, 诉求不一样, 选择就不一样:1 eggjs: 短平快稳,上手极快, 文档完善,配套齐全, 能极大缩短工期。2 nestjs: 有点追求, 复杂度高的协作项目, 核心诉求是代码组织模式的 之前egg很好的满足了我们的诉求, 但是下一个项目, 会考虑使用nestjs或者自行开发框架(足够闲的话)。 egg官方维护者atian25的评论:点击详见 补充下几点: Cluster 里面并没有 schedule Socket 那个是为了实现高级的 IPC Cluster 还是很建议使用的,如果有什么特殊的定制需求,何不如提下 RFC/ISSUE 大家讨论下 egg-core + egg-cluster 才成为 egg,你要是想定制,可以类似 egg-core + my-cluster(虽然不建议) 框架复杂度其实反而是降低了,因为 PM2 的代码比 egg-cluster 复杂多了 Egg 的定位是框架的框架,跟 thinkjs/sails/nest 这些是没法直接对比的,因为不是一个层面的概念。就像你只能对比 Express 和 Koa,而不能对比 nest 和 Koa,因为 nest 是在 Express 之上的封装。 基于 Egg 封装的针对某个领域的上层框架,才能对比。譬如完全可以用官方的 TS 方案,再封装集成几个装饰器,成为一个上层框架。就可以拿来对比了。 相关资料Egg & Node.js 从小工坊走向企业级开发 选择JavaScript开源库时,你需要考虑这些问题 12个因素","tags":[{"name":"fe","slug":"fe","permalink":"https://betgar.github.io/tags/fe/"}]},{"title":"自适应全背景图片布局","date":"2018-07-26T13:30:00.000Z","path":"2018/07/26/perfect-full-page-background-image/","text":"自适应全背景图片布局 原文在这里 实例1234567891011html { height: auto; min-height: 100%; margin: 0; padding: 0; background: url(http://desk.fd.zol-img.com.cn/t_s1366x768c5/g5/M00/01/03/ChMkJ1or0MGIGULYABCo5rcHl4AAAi_rgHdSLYAEKj-970.jpg?downfile=1532612079420.jpg) no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover;} 123","tags":[{"name":"layout","slug":"layout","permalink":"https://betgar.github.io/tags/layout/"}]},{"title":"Bootstrap-validator的使用","date":"2018-07-25T12:00:00.000Z","path":"2018/07/25/the-docs-of-bootstrap-validator/","text":"Bootstrap-validator的使用 翻译官方的文档。 这个插件遵循Bootstrap核心jQuery插件提出的约定,所以一定要检查这些插件,以便更好地理解这个插件的目标和设计。 例子 使用这个简单的插件为表单添加验证。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859<form data-toggle=\"validator\" role=\"form\"> <div class=\"form-group\"> <label for=\"inputName\" class=\"control-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"inputName\" placeholder=\"Cina Saffary\" required> </div> <div class=\"form-group has-feedback\"> <label for=\"inputTwitter\" class=\"control-label\">Twitter</label> <div class=\"input-group\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" pattern=\"^[_A-z0-9]{1,}$\" maxlength=\"15\" class=\"form-control\" id=\"inputTwitter\" placeholder=\"1000hz\" required> </div> <span class=\"glyphicon form-control-feedback\" aria-hidden=\"true\"></span> <div class=\"help-block with-errors\">Hey look, this one has feedback icons!</div> </div> <div class=\"form-group\"> <label for=\"inputEmail\" class=\"control-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"inputEmail\" placeholder=\"Email\" data-error=\"Bruh, that email address is invalid\" required> <div class=\"help-block with-errors\"></div> </div> <div class=\"form-group\"> <label for=\"inputPassword\" class=\"control-label\">Password</label> <div class=\"form-inline row\"> <div class=\"form-group col-sm-6\"> <input type=\"password\" data-minlength=\"6\" class=\"form-control\" id=\"inputPassword\" placeholder=\"Password\" required> <div class=\"help-block\">Minimum of 6 characters</div> </div> <div class=\"form-group col-sm-6\"> <input type=\"password\" class=\"form-control\" id=\"inputPasswordConfirm\" data-match=\"#inputPassword\" data-match-error=\"Whoops, these don't match\" placeholder=\"Confirm\" required> <div class=\"help-block with-errors\"></div> </div> </div> </div> <div class=\"form-group\"> <div class=\"radio\"> <label> <input type=\"radio\" name=\"underwear\" required> Boxers </label> </div> <div class=\"radio\"> <label> <input type=\"radio\" name=\"underwear\" required> Briefs </label> </div> </div> <div class=\"form-group\"> <div class=\"checkbox\"> <label> <input type=\"checkbox\" id=\"terms\" data-error=\"Before you wreck yourself\" required> Check yourself </label> <div class=\"help-block with-errors\"></div> </div> </div> <div class=\"form-group\"> <button type=\"submit\" class=\"btn btn-primary\">Submit</button> </div></form> 用法 可以通过data-api或JavaScript在标记中启用表单验证。通过添加data-toggle="validator"到表单元素自动启用表单验证。 123<form role=\"form\" data-toggle=\"validator\"> ...</form> 或者通过JavaScript激活验证: 1$('#myForm').validator() 标记按照Bootstrap的示例进行适当的表单标记。重要的是每个输入字段都在其自己的单独.form-group容器中,以便正确显示错误消息。 验证规则通过以下标准HTML5属性在表单输入上指定: type="email" type="url" type="number",通过附加约束max,min和step属性 pattern="Reg(ular )?Exp(ression)?"(用于输入类型的text,search,tel,url或email) required 以及以下非标准属性: data-match="#inputToMatch" 确保两个字段匹配,例如密码确认 data-minlength="5" 强制执行最少量的字符 data-remote="/path/to/remote/validator"发出AJAX请求以确定该字段是否有效。请务必为输入提供name属性,因为请求将被发送到/path/to/remote/validator?<name>=<value>。200 OK如果字段有效,则远程端点应返回a ,4xx否则返回。这是使用Express 的参考服务器实现。 标准属性验证器标准HTML5属性的验证规则由浏览器使用HTML5约束验证API完全处理。因此,此插件无法控制有资格作为有效电子邮件地址或URL的内容。如果您发现需要对这些字段进行更严格的验证,则可以使用该pattern属性进一步限制可接受的范围。 但请注意,您不要过于严格,这可能会导致漏报和糟糕的用户体验。例如,根据标准,您会对哪种电子邮件地址被视为有效感到惊讶。 跨浏览器兼容性由于此插件依赖于HTML5约束验证API,因此不支持Internet Explorer 9及更早版本。如果您需要支持这些浏览器,则必须添加像Ryan Seddon的H5F这样的polyfill。 要显示错误消息,请在输入字段后面包含容器help-block和with-errors类。 1234567<form role=\"form\" data-toggle=\"validator\"> <div class=\"form-group\"> <label for=\"inputEmail\">Email</label> <input type=\"email\" id=\"inputEmail\"> <div class=\"help-block with-errors\"></div> </div></form> 验证字段默认情况下,验证程序仅验证插件初始化时存在的字段。如果表单具有动态字段集,则需要调用$(...).validator('update')以通知插件要验证的字段集已更改。 用于确定验证哪些字段的默认选择器是: 1$.fn.validator.Constructor.INPUT_SELECTOR = ':input:not([type=\"hidden\"], [type=\"submit\"], [type=\"reset\"], button)' 如果需要更改此默认行为,可以在代码中覆盖此值。或者,您可以添加data-validate="true"/ data-validate="false"到特定输入以强制将其包含/排除在已验证字段集中。 选项选项可以通过数据属性或JavaScript传递。对于数据属性,请将选项名称附加到data-,如data-delay=""。 名称 类型 默认 描述 delay number 500 在表单字段上显示错误之前要等待的毫秒数。 html boolean false 将HTML插入错误消息中。如果为false,则使用jQuery的文本方法将内容插入DOM。如果您担心XSS攻击,请使用文本。 disable boolean true 禁用提交按钮,直到表单有效并且所有必填字段都已完成。 focus boolean true 在验证表单时,滚动并聚焦第一个字段并显示错误。例如,如果页面顶部有一个固定的导航栏,并且需要调整窗口顶部和焦点字段之间的填充量,则可以覆盖以下变量:$.fn.validator.Constructor.FOCUS_OFFSET默认为20(px)。 feedback object glyphicon classes 覆盖用于表单反馈图标的类。默认为Bootstrap的glyphicons:feedback: { success: 'glyphicon-ok', error: 'glyphicon-remove' } custom object {} 添加要运行的自定义验证程序。验证器应该是接收jQuery元素作为参数的函数,如果字段无效则返回错误消息。这是一个自定义验证器的示例,它检查输入是否等于某个指定值:custom: { equals: function($el) { var matchValue = $el.data("equals") // foo if ($el.val() !== matchValue) { return "Hey, that's not valid! It's gotta be " + matchValue } } }通过将其名称作为数据属性引用,将验证器添加到输入中就像完成其他操作一样:<input data-equals="foo">。在这种情况下,如果用户输入除以外的任何内容,该字段将显示错误foo。 1234567891011121314151617181920// feedback{ feedback: { success: 'glyphicon-ok', error: 'glyphicon-remove' }}// custom{ custom: { equals: function($el) { var matchValue = $el.data(\"equals\") // foo if ($el.val() !== matchValue) { return \"Hey, that's not valid! It's gotta be \" + matchValue } } }} 各个表单字段的错误消息可以通过使用数据属性来指定单个表单字段的错误消息。您可以在字段中的每个类型的验证,即指定一个错误信息data-pattern-error="",data-required-error="",data-match-error="",等…或者使用data-error=""用于衬垫的错误消息,以显示该字段的任何错误。 方法.validator(options)将验证器附加到表单集合。 .validator('update')更新将验证的输入集合。如果需要验证的字段集已更改,请调用此方法。 .validator('validate')立即验证整个表格。 .validator('destroy')销毁形式验证器并清理留下的数据。 事件所有事件都在表单元素上触发,并提供对事件所属的表单字段的引用event.relatedTarget。 事件类型 描述 validate.bs.validator 验证表单字段时,此事件立即触发。 invalid.bs.validator 当表单字段变为无效时会触发此事件。通过提供字段错误event.detail。 valid.bs.validator 当表单字段变为有效时,将触发此事件。之前的字段错误通过提供event.detail。 validated.bs.validator 在验证表单字段后触发此事件。 有条件地处理提交事件当表单无效时,.preventDefault()将在提交事件上调用。因此,如果您想要挂钩提交事件并根据表单是否有效而有条件地执行某些操作,则可以检查事件是否有效.isDefaultPrevented()。在表单上初始化插件后,请确保您的提交处理程序已绑定。 1234567$('#form').validator().on('submit', function (e) { if (e.isDefaultPrevented()) { // handle the invalid form... } else { // everything looks good! }})","tags":[{"name":"Bootstrap","slug":"Bootstrap","permalink":"https://betgar.github.io/tags/Bootstrap/"},{"name":"Validation","slug":"Validation","permalink":"https://betgar.github.io/tags/Validation/"}]},{"title":"Bootstrap 表单布局","date":"2018-07-23T11:00:00.000Z","path":"2018/07/23/bootstrap-form-layout/","text":"Bootstrap 表单布局 bootstrap已经使用很久了,但是对于表单布局,尤其是HTML结构嵌套层次还有很多疑问。 所以又翻了一遍官网和资料,总结一下。 本文部分内容翻译自这里. 基本原则Bootstrap表单控件自动获得一些全局样式,如所有textual <input>,<textarea>以及<select>带类的元素.form-control的宽度为100%。表单布局的基本规则如下; 始终使用<form role="form">,有助于改善使用屏幕阅读器的人的可访问性 用于最佳间距包装标签和表格控件 <div class="form-group"> 将类.form-control添加到所有文本<input>,<textarea>和<select>元素 表单垂直布局(vertical form) 垂直布局也是Bootstrap的默认布局,因为我们的浏览器默认的普通流就是从左到右(LTR),从上到下。 12345678910111213141516<!DOCTYPE html> <html lang=\"en\"> <head> <title>My first Bootstrap page </title> <meta name=\"viewport\" content=\"width=device-width, initialscale=1\"> <link href=\"CSS/bootstrap.min.css\" rel=\"stylesheet\"> <link href=\"CSS/bootstrap-theme.min.css\" rel=\"stylesheet\"> <script type=\"text/javascript\" src=\"JS/bootstrap.min.js\"></script> </head> <body><div class=\"container\" style=\"max-width:600px;margin:60px auto;\"> <form role=\"form\"> <div class=\"form-group\"> <label for=\"name\">First name</label> <input type=\"name\" class=\"form-control\" id=\"fname\" placeholder=\"Enter name\"> <div class=\"form-group\"> 表单水平布局(horizontal form) 水平形式与垂直形式布局的不同之处在于标记量和表单的表示形式。在此表单中,布局标签浮动到输入字段的左侧。标签和输入字段都出现在同一行上。除默认规则外,水平表单的规则, 所有你需要做的就是添加.form-horizontal类的<form> 元素,.control-label类的所有<label>元素。使用Bootstrap的预定义网格类来对齐标签和表单控件。 12345678910111213141516171819202122232425262728293031<div class=\"container\" style=\"max-width:600px;padding:40px 20px;background:#ebeff2\"> <h3>Signup</h3> <form class=\"form-horizontal\" role=\"form\"> <div class=\"form-group\"> <label for=\"name\" class =\"control-label col-sm-3\">First name</label> <div class=\"col-sm-8\"> <input type=\"name\" class=\"form-control\" id=\"name\" placeholder=\"Enter name\"> </div> </div> <div class=\"form-group\"> <label for=\"address\" class =\"control-label col-sm-3\">Second name</label> <div class=\"col-sm-8\"> <input type=\"address\" class=\"form-control\" id=\"address\" placeholder=\"Enter address\"> </div> <div class=\"form-group\"> <label for=\"email\" class =\"control-label col-sm-3\">Email</label> <div class=\"col-sm-8\"> <input type=\"email\" class=\"form-control\" id=\"email\" placeholder=\"Enter email\"> </div> </div> <div class=\"form-group\"> <label for=\"pwd\" class =\"control-label col-sm-3\">Password</label> <div class=\"col-sm-8\"> <input type=\"password\" class=\"form-control\" id=\"pwd\" placeholder=\"Enter password\"> </div> </div> <div class=\"col-sm-offset-2 col-sm-8\"> <button type=\"submit\" class=\"btn btn-default\">Register</button> </div> </form></div> 注意:label元素在水平布局中必须添加.control-label样式。 内联表单(inline form ) Bootstrap的内联表单布局可用于将表单控件并排放置在紧凑的布局中。在内联形式中,所有元素都是直线,左对齐,标签位于旁边。为此,您需要将.form-inline类添加到<form>元素。 123456789101112131415<form class=\"form-inline\" role=\"form\"> <div class=\"form-group\"> <label for=\"name\">Name</label> <input type=\"name\" class=\"form-control\" id=\"name\" placeholder=\"Enter name\"> </div> <div class=\"form-group\"> <label for=\"email\">Email</label> <input type=\"email\" class=\"form-control\" id=\"email\" placeholder=\"Enter email\"> </div> <div class=\"form-group\"> <label for=\"pwd\">Password</label> <input type=\"password\" class=\"form-control\" id=\"pwd\" placeholder=\"Enter password\"> </div> <button type=\"submit\" class=\"btn btn-default\">Register</button></form> 注意事项 .form-group类用于封装组中的多个控件 - 就像我们对标签和相应的文本框所做的那样。 .control-label和.form-control类分别用于设置标签和表单元素的样式。 使用.input-group类可以相邻地关联多个控件。 禁用或只读状态可用于控件 - Bootstrap将自动关联必要的样式,但是,所有控件都必须添加.form-control类。 为了控制大小,可以使用.input-lg,.input-sm类。要控制组的大小,可以使用.form-group-lg或.form-group-sm类。 要显示任何帮助文本,可以在单独的标签中使用.help-block类。这将以新的一行显示文本。 静态表单 经常需要静态展示表单信息,所以顺便总结一下静态表单的展示。 1234567891011121314<form class=\"form-horizontal\"> <div class=\"form-group\"> <label class=\"col-sm-2 control-label\">Email</label> <div class=\"col-sm-10\"> <p class=\"form-control-static\">[email protected]</p> </div> </div> <div class=\"form-group\"> <label for=\"inputPassword\" class=\"col-sm-2 control-label\">Password</label> <div class=\"col-sm-10\"> <input type=\"password\" class=\"form-control\" id=\"inputPassword\" placeholder=\"Password\"> </div> </div></form> 参考这里 表单元素校验状态 涉及.input-group-addon .has-feedback .form-control-feedback .has-success .help-block等。 这里有一个bootstrap-validation校验库就是使用这些class样式实现的. 123456789101112131415161718192021<form class=\"form-horizontal\"> <div class=\"form-group has-success has-feedback\"> <label class=\"control-label col-sm-3\" for=\"inputSuccess3\">Input with success</label> <div class=\"col-sm-9\"> <input type=\"text\" class=\"form-control\" id=\"inputSuccess3\" aria-describedby=\"inputSuccess3Status\"> <span class=\"glyphicon glyphicon-ok form-control-feedback\" aria-hidden=\"true\"></span> <span id=\"inputSuccess3Status\" class=\"sr-only\">(success)</span> </div> </div> <div class=\"form-group has-success has-feedback\"> <label class=\"control-label col-sm-3\" for=\"inputGroupSuccess2\">Input group with success</label> <div class=\"col-sm-9\"> <div class=\"input-group\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" class=\"form-control\" id=\"inputGroupSuccess2\" aria-describedby=\"inputGroupSuccess2Status\"> </div> <span class=\"glyphicon glyphicon-ok form-control-feedback\" aria-hidden=\"true\"></span> <span id=\"inputGroupSuccess2Status\" class=\"sr-only\">(success)</span> </div> </div></form> 例子参考这里 bootstrap表格样式class 总结一下bootstrap表格样式class的id,以下内容来自bootstrap的forms.less文件。 表单元素标签样式 Bootstrap内部对如下的标签进行过样式的修饰。 fieldset legend label input[type=”search”] input[type=”radio”] input[type=”checkbox”] input[type=”file”] input[type=”range”] select[multiple] select[size] output select textarea input[type=”text”] input[type=”password”] input[type=”datetime”] input[type=”datetime-local”] input[type=”date”] input[type=”month”] input[type=”time”] input[type=”week”] input[type=”number”] input[type=”email”] input[type=”url”] input[type=”search”] input[type=”tel”] input[type=”color”] 表单样式class.form-inline.form-horizontal.control-label.form-control focus disabled readonly .form-control-static 静态表单展示使用 .form-group .form-group-sm .form-group-lg .input-group (input-groups.less) .input-group-addon .input-group-btn .input-sm .input-lg .input-group-lg .input-group-sm 参考这里 .radio和.checkbox .radio .radio-inline .checkbox .checkbox-inline 参考这里 form-control的add-on系列 .has-feedback .form-control-feedback 123456789<div class=\"form-group has-success has-feedback\"> <label class=\"control-label\" for=\"inputGroupSuccess1\">Input group with success</label> <div class=\"input-group\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" class=\"form-control\" id=\"inputGroupSuccess1\" aria-describedby=\"inputGroupSuccess1Status\"> </div> <span class=\"glyphicon glyphicon-ok form-control-feedback\" aria-hidden=\"true\"></span> <span id=\"inputGroupSuccess1Status\" class=\"sr-only\">(success)</span></div> 参考这里 .help-block .help-block主要给表单做一些说明性的文字 <legend>和<fieldset>的使用<legend>的使用 <legend>:主要用来添加表单标题说明的. <fieldset>使用 <fieldset>标签平时使用比较少,主要用在,当表单元素比较多,不同的表单元素需要进行分组,这时比较方便,而且对于分组表单元素进行统一的禁用时。 注意:浏览器兼容性问题 12> <fieldset disabled=\"disabled\">内部的表单元素都被禁用</fieldset>> 例子参考这里: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950<!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\"><title>Example of Bootstrap 3 Disabled Fieldsets</title><link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\"><script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js\"></script><script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script> <style type=\"text/css\"> .bs-example{ margin: 20px; }</style></head><body><div class=\"bs-example\"> <form class=\"form-horizontal\"> <legend>登录信息</legend> <fieldset disabled=\"disabled\"> <div class=\"form-group\"> <label for=\"inputEmail\" class=\"control-label col-xs-2\">Email</label> <div class=\"col-xs-10\"> <input type=\"email\" class=\"form-control\" id=\"inputEmail\" placeholder=\"Email\"> </div> </div> <div class=\"form-group\"> <label for=\"inputPassword\" class=\"control-label col-xs-2\">Password</label> <div class=\"col-xs-10\"> <input type=\"password\" class=\"form-control\" id=\"inputPassword\" placeholder=\"Password\"> </div> </div> <div class=\"form-group\"> <div class=\"col-xs-offset-2 col-xs-10\"> <div class=\"checkbox\"> <label><input type=\"checkbox\"> Remember me</label> </div> </div> </div> <div class=\"form-group\"> <div class=\"col-xs-offset-2 col-xs-10\"> <button type=\"submit\" class=\"btn btn-primary\">Login</button> </div> </div> </fieldset> </form></div></body></html> 参考资料http://stacktips.com/tutorials/bootstrap/vertical-horizontal-and-inline-form-example-in-bootstrap https://v3.bootcss.com/css/#forms https://www.tutorialrepublic.com/twitter-bootstrap-tutorial/ https://www.tutorialrepublic.com/twitter-bootstrap-tutorial/bootstrap-forms.php","tags":[{"name":"Bootstrap","slug":"Bootstrap","permalink":"https://betgar.github.io/tags/Bootstrap/"}]},{"title":"创建基于ES6的项目","date":"2018-07-17T13:00:00.000Z","path":"2018/07/17/create-project-based-on-es6-with-babel/","text":"创建基于ES6的项目(babel) 首先感谢业界的很多大神,创造了很多的模版项目,成功的把我用成了傻逼。今天有个小的功能,使用CRC进行checksum校验,选择使用node-crc包,本来想写个小demo测试一下,结果发现crc全部使用es6语法进行构建,长期使用别人模版的我瞬间傻逼了。所以才有了这篇文章。 babel handbook 中文版 初始化项目12cd crc-demo # crc-demo自己的项目目录npm init 安装依赖 babel和目标运行环境依赖(es5) 目标环境根据自己实际的环境定义,具体参考preset-env 12# rimraf 用来删除文件的npm install babel-cli babel-preset-es2015 rimraf --save-dev 创建 .babelrc 配置文件 在crc-demo下添加.babelrc文件,参考这里 12345{ \"presets\": [ \"es2015\" // 环境和安装依赖的环境相关(babel-preset-es2015) ]} package.json的scripts中添加babel src目录的js文件会编译到dist中,每次编译之前都会先删除dist文件。 123456{ \"scripts\": { \"build\": \"rimraf dist/ && babel src --out-dir dist/ --copy-files\", \"start\": \"npm run build && node dist/index.js\" }} 执行index.js内容1npm run start 参考https://www.cnblogs.com/weschen/p/7154284.html https://www.babeljs.cn/docs/plugins/preset-env/ https://www.babeljs.cn/docs/usage/cli/","tags":[{"name":"babel","slug":"babel","permalink":"https://betgar.github.io/tags/babel/"}]},{"title":"Bootstrap响应式的HTML标签嵌套规则","date":"2018-07-12T13:00:00.000Z","path":"2018/07/12/the-bootstrap-responsive-nested-rule-of-html/","text":"Bootstrap响应式的HTML嵌套规则 使用Bootstrap已经很长时间了,总结一些HTML嵌套规则和Bootstrap的class使用。本来要自己总结的,重新看Bootstrap官网时发现了bootlint,所以直接把内容搬了过来,进行了分类汇总。 Bootlint使用 Bootlint是Bootstrap官方推出的一个工具,用来检测HTML结构是否符合Bootstrap响应式的规则。因为不正确的HTML嵌套,Bootstrap响应式效果不会生效。 bootlint:问题代码参考地址https://github.com/twbs/bootlint/wiki 手动引入bootlint.js 在</body>之前引入bootlint.js 使用bookmarklet动态引入 添加一个书签,url配置为: 12> javascript:(function(){var s=document.createElement(\"script\");s.onload=function(){bootlint.showLintReportForCurrentDocument([]);};s.src=\"https://maxcdn.bootstrapcdn.com/bootlint/latest/bootlint.min.js\";document.body.appendChild(s)})();> 当你点击书签时,自动在当前页面引入。 Bootstrap的HTML嵌套规则 为什么要了解Bootstrap框架规定的HTML嵌套规则呢?因为只有了解了嵌套规则才能把响应式布局用好。 .container.container和.container-fluid不能嵌套12345678910111213141516171819202122232425262728<!-- wrong --><div class=\"container\"> <div class=\"container\"> ... </div></div><div class=\"container-fluid\"> <div class=\"container-fluid\"> ... </div></div><div class=\"container\"> <div class=\"foobar\"> ... <div class=\"container\"> ... </div> ... </div></div><div class=\"container-fluid\"> <div class=\"container\"> ... </div></div> .row.row的子节点应该只有.col-*-*Bootlint found non-grid-column children of grid rows. Bootstrap’s grid system requires that all children of grid rows must be grid columns. Wrong: 12345<div class=\"row\"> <div class=\"my-awesome-thing\">...</div> <div class=\"other-thing\">...</div> <div class=\"row\">...</div></div> Right: 1234567<div class=\"row\"> <div class=\"col-sm-6\">...</div> <div class=\"col-lg-12\">...</div> <div class=\"col-xs-12\"> <div class=\"row\">...</div> </div></div> .row必须是.container或者.container-fluid的后代节点12345<div class=\"container\"> <div class=\"row\"> </div></div> .row and .col-*-* 不能同时用在同一个元素上Wrong: 1<div class=\"row col-xs-6\">...</div> Right (Note that the two options have different meanings): 123456789<div class=\"row\"> <div class=\"col-xs-6\">...</div></div><!-- Or, depending on the semantics you need: --><div class=\"col-xs-6\"> <div class=\"row\">...</div></div> column(.col-*-*).col-*-*不能应用float属性wrong 123<div class=\"col-sm-7 pull-right\"><div class=\"col-sm-7\" style=\"float: left;\"> 多余的col-*-*1<div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"></div> col-xs-6适用于等于和大于它的尺寸,所以其它col-sm-6 col-md-6 col-lg-6都是多余的。 但是不同的尺寸下,宽度不一样时,不能这样使用。 1<div class=\"col-xs-6\">这样就行了</div> .col-*-*只应该是.row和.form-groups的子节点Wrong: 1234567891011<div class=\"my-random-thing\"> <div class=\"col-sm-6\">...</div> <div class=\"col-sm-6\">...</div></div><div class=\"row\"> <div class=\"col-sm-12\"> <div class=\"col-sm-12\">...</div> <!-- You can't nest columns directly like this. This is missing a level of .row --> </div></div> Right: 12345678910111213141516171819<div class=\"row\"> <div class=\"col-sm-6\">...</div> <div class=\"col-sm-6\">...</div></div><div class=\"form-horizontal\"> <div class=\"form-group\"> <div class=\"col-sm-6\">...</div> <div class=\"col-sm-6\">...</div> </div></div><div class=\"row\"> <div class=\"col-sm-12\"> <div class=\"row\"> <div class=\"col-sm-12\">...</div> </div> </div></div> .input-group.input-group 不能包含 <select>Wrong:123456789<!-- select 不能自适应宽度 在webkit浏览器中 --><div class=\"input-group\"> <span class=\"input-group-addon\">$</span> <select class=\"form-control\"> <option value=\"5.00\"><option> <option value=\"50.00\"><option> <option value=\"500.00\"><option> </select></div> .input-group 不能包含 <textarea>Wrong:12345<!-- input-group只能包含那些基于input的元素 --><div class=\"input-group\"> <span class=\"input-group-addon\">✉</span> <textarea class=\"form-control\" placeholder=\"To Whom It May Concern:\"></textarea></div> .input-group不能包含多个.form-control元素Wrong (unsupported): 12345<div class=\"input-group\"> <span class=\"input-group-addon\">$</span> <input type=\"text\" class=\"form-control\" placeholder=\"Dollars\"> <input type=\"text\" class=\"form-control\" placeholder=\"Cents\"></div> Right (supported): 1234<div class=\"input-group\"> <span class=\"input-group-addon\">$</span> <input type=\"text\" class=\"form-control\" placeholder=\"Amount\"></div> Wrong (unsupported): 12345<div class=\"input-group\"> <input type=\"text\" class=\"form-control\" placeholder=\"Username\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" class=\"form-control\" placeholder=\"Domain\"></div> Right (supported): 1234<div class=\"input-group\"> <input type=\"text\" class=\"form-control\" placeholder=\"Username\"> <span class=\"input-group-addon\">@example.com</span></div> .input-group和.form-group不能同时应用同一元素 应该在.from-group中嵌套.input-group Wrong: 1234<div class=\"form-group input-group\"> <div class=\"input-group-addon\">@</div> <input class=\"form-control\" type=\"email\" placeholder=\"Enter nickname\"></div> Right: 123456<div class=\"form-group\"> <div class=\"input-group\"> <div class=\"input-group-addon\">@</div> <input class=\"form-control\" type=\"email\" placeholder=\"Enter nickname\"> </div></div> .input-group and .col-*-* 不能同时直接应用在同一元素上 .col-*-*不能用在 .input-group元素上. 应该在 .col-*-*中嵌套.input-group Wrong: 1234<div class=\"input-group col-sm-5\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" class=\"form-control\" placeholder=\"Username\"></div> Right: 123456<div class=\"col-sm-5\"> <div class=\"input-group\"> <span class=\"input-group-addon\">@</span> <input type=\"text\" class=\"form-control\" placeholder=\"Username\"> </div></div> .input-group不支持在input单边(左边或者右边)包含多个.input-group-addonRight (two add-ons are fine so long as they’re on different sides): 12345<div class=\"input-group\"> <span class=\"input-group-addon\">$</span> <input type=\"text\" class=\"form-control\"> <span class=\"input-group-addon\">.00</span></div> Wrong: 12345<div class=\"input-group\"> <span class=\"input-group-addon\">USD</span> <span class=\"input-group-addon\">$</span> <input type=\"text\" class=\"form-control\"></div> Wrong: 12345<div class=\"input-group\"> <input type=\"text\" class=\"form-control\"> <span class=\"input-group-addon\">.00</span> <span class=\"input-group-addon\">USD</span></div> button.btn 只能用在<a> button <label> <input>元素上1234<a class=\"btn btn-primary\">Button</a><button type=\"button\" class=\"btn btn-primary\">Button</button><input type=\"button\" class=\"btn btn-primary\" value=\"Button\"><label class=\"btn btn-primary\">Button</label> button和input大小(在input-group中) 应该使用input-group-lg代替btn-lg Wrong: 123456<div class=\"input-group\"> <span class=\"input-group-btn\"> <button class=\"btn btn-default btn-lg\" type=\"button\">Go!</button> </span> <input type=\"text\" class=\"form-control input-lg\" /></div> Right: 123456<div class=\"input-group input-group-lg\"> <span class=\"input-group-btn\"> <button class=\"btn btn-default\" type=\"button\">Go!</button> </span> <input type=\"text\" class=\"form-control\" /></div> .btn.dropdown-toggle 只能是button group最后一个元素.Due to current limitations, for proper rendering, the dropdown toggle button in a split button dropdown must be the last button in the .btn-group. Wrong:1234567891011121314<div class=\"btn-group\"> <button type=\"button\" class=\"btn btn-danger dropdown-toggle\" data-toggle=\"dropdown\"> <span class=\"caret\"></span> <span class=\"sr-only\">Toggle Dropdown</span> </button> <ul class=\"dropdown-menu\" role=\"menu\"> <li><a href=\"#\">Action</a></li> <li><a href=\"#\">Another action</a></li> <li><a href=\"#\">Something else here</a></li> <li class=\"divider\"></li> <li><a href=\"#\">Separated link</a></li> </ul> <button type=\"button\" class=\"btn btn-danger\">Action</button></div> Right:1234567891011121314<div class=\"btn-group\"> <button type=\"button\" class=\"btn btn-danger\">Action</button> <button type=\"button\" class=\"btn btn-danger dropdown-toggle\" data-toggle=\"dropdown\"> <span class=\"caret\"></span> <span class=\"sr-only\">Toggle Dropdown</span> </button> <ul class=\"dropdown-menu\" role=\"menu\"> <li><a href=\"#\">Action</a></li> <li><a href=\"#\">Another action</a></li> <li><a href=\"#\">Something else here</a></li> <li class=\"divider\"></li> <li><a href=\"#\">Separated link</a></li> </ul></div> checkbox.checkboxclass要求结构固定 .checkbox>label>input[type="checkbox"] 12345<div class=\"checkbox\"> <label> <input type=\"checkbox\" /> Label text goes here </label></div> .checkbox-inline 只能用在 <label>元素Wrong: 123<span class=\"checkbox-inline\"> <input type=\"checkbox\" id=\"inlineCheckbox1\" value=\"option1\" /> Option number one</span> Right: 123<label class=\"checkbox-inline\"> <input type=\"checkbox\" id=\"inlineCheckbox1\" value=\"option1\" /> Option number one</label> .checkbox-inline class正确的标签结构 正确的结构是label.checkbox-inline>input[type="checkbox"] .checkbox-inline requires a specific DOM structure. There cannot be any intervening layers of elements. Wrong: 12345<label class=\"checkbox-inline\"> <span class=\"my-awesome-wrapper\"> <input type=\"checkbox\" id=\"inlineCheckbox1\" value=\"option1\" /> Option number one </span></label> Right: 123<label class=\"checkbox-inline\"> <input type=\"checkbox\" id=\"inlineCheckbox1\" value=\"option1\" /> Option number one</label> .radioclass要求固定结构 .radio>label>input[type="radio"] 123456<div class=\"radio\"> <label> <input type=\"radio\" name=\"someChoiceGroup\" value=\"option1\" /> This is option number one </label></div> .radio-inline 只应该用在 <label>元素Wrong: 123456<span class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio1\" value=\"option1\" /> Option one</span><span class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio2\" value=\"option2\" /> Option two</span> Right: 123456<label class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio1\" value=\"option1\" /> Option one</label><label class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio2\" value=\"option2\" /> Option two</label> .radio-inline class正确使用方式 正确标签结构:label.radio-inline>input[type="radio"] .radio-inline requires a specific DOM structure. There cannot be any intervening layers of elements. Wrong: 12345678910<label class=\"radio-inline\"> <span class=\"my-awesome-wrapper\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio1\" value=\"option1\" /> Option one </span></label><label class=\"radio-inline\"> <span class=\"my-awesome-wrapper\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio2\" value=\"option2\" /> Option two </span></label> Right: 123456<label class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio1\" value=\"option1\" /> Option one</label><label class=\"radio-inline\"> <input type=\"radio\" name=\"someInlineRadioOptions\" id=\"inlineRadio2\" value=\"option2\" /> Option two</label> .active & checked.checkbox和.radio的.active状态 .bt-group中使用.checkbox和.radio,当input[type="checkbox"]和input[type="radio"]元素是checked状态,它们的<label>标签必须添加.active样式。 Wrong: 12345678<div class=\"btn-group\" data-toggle=\"buttons\"> <label class=\"btn btn-primary\"> <input type=\"checkbox\" checked /> Option 1 (pre-checked) </label> <label class=\"btn btn-primary active\"> <input type=\"checkbox\" /> Option 2 (not pre-checked) </label></div> Right: 12345678<div class=\"btn-group\" data-toggle=\"buttons\"> <label class=\"btn btn-primary active\"> <input type=\"checkbox\" checked /> Option 1 (pre-checked) </label> <label class=\"btn btn-primary\"> <input type=\"checkbox\" /> Option 2 (not pre-checked) </label></div> Wrong: 12345678<div class=\"btn-group\" data-toggle=\"buttons\"> <label class=\"btn btn-primary\"> <input type=\"radio\" name=\"options\" id=\"option1\" checked /> Option 1 (preselected) </label> <label class=\"btn btn-primary active\"> <input type=\"radio\" name=\"options\" id=\"option2\" /> Option 2 (not preselected) </label></div> Right: 12345678<div class=\"btn-group\" data-toggle=\"buttons\"> <label class=\"btn btn-primary active\"> <input type=\"radio\" name=\"options\" id=\"option1\" checked /> Option 1 (preselected) </label> <label class=\"btn btn-primary\"> <input type=\"radio\" name=\"options\" id=\"option2\" /> Option 2 (not preselected) </label></div> panel.panel-footer和.panel-heading 父元素必须是 .panel.panel-footer、 .panel-heading必须是.panel的直接子节点。.panel-footer、.panel-heading 和 .panel之间不能有任何的其它节点干扰。 Wrong: 1234567891011<div class=\"panel panel-default\"> <div class=\"my-super-special-wrapper\"> <div class=\"panel-heading\">Panel heading without title</div> </div> <div class=\"panel-body\"> Panel content </div> <div class=\"my-super-special-wrapper\"> <div class=\"panel-footer\">Panel footer</div> </div></div> Right: 1234567<div class=\"panel panel-default\"> <div class=\"panel-heading\">Panel heading without title</div> <div class=\"panel-body\"> Panel content </div> <div class=\"panel-footer\">Panel footer</div></div> .panel-title父元素必须是.panel-headingtable.table-responsice应用在table的父元素上,而不是table元素Wrong: 123<table class=\"table table-responsive\"> ...</table> Right: 12345<div class=\"table-responsive\"> <table class=\"table\"> ... </table></div> form.form-group元素不能嵌套123456<div class=\"form-group\"> ...</div><div class=\"form-group\"> ...</div> .form-inline 或者.form-horizontal直接使用在 .form-group. 元素上 或者 .form-group 嵌套在 .form-inline or .form-horizontal内部 1234567891011<div class=\"form-inline\"> <div class=\"form-group\"> ... </div></div><div class=\"form-horizontal\"> <div class=\"form-group\"> ... </div></div> .form-control-feedback .form-control-feedback元素的祖先元素必须有 .form-group.has-feedback 样式 Wrong: 12345<div class=\"form-group has-error\"> <label class=\"control-label\" for=\"inputError2\">Input with error</label> <input type=\"text\" class=\"form-control\" id=\"inputError2\"> <span class=\"glyphicon glyphicon-remove form-control-feedback\"></span></div> Right: 12345<div class=\"form-group has-feedback has-error\"> <label class=\"control-label\" for=\"inputError2\">Input with error</label> <input type=\"text\" class=\"form-control\" id=\"inputError2\"> <span class=\"glyphicon glyphicon-remove form-control-feedback\"></span></div> .form-control不能用在没有交互输入的文本属性的input上Right 1234567<input type=\"text\" class=\"form-control\" value=\"Text input\" /><textarea class=\"form-control\">Text area</textarea><select class=\"form-control\"> <option>Option 1</option> <option>Option 2</option> ...</select> glyphicon.glyphicon-*必须和.glyphicon结合使用1<span class=\"glyphicon glyphicon-heart\"></span> .glyphicon-*的元素,不能有子节点和文本节点Wrong: 1<span class=\"glyphicon glyphicon-heart\"><a href=\"#love\">I love this</a></span> Right: 1<span class=\"glyphicon glyphicon-heart\"></span> <a href=\"#love\">I love this</a> modalmodal正确的嵌套结构 .modal .modal-dialog .modal-content .modal-header .modal-title .modal-body .modal-footer嵌套结构不能改变。 .modal-header .modal-footer是可选的。 1234567891011121314151617<div class=\"modal fade\"> <div class=\"modal-dialog\"> <div class=\"modal-content\"> <div class=\"modal-header\"> <button type=\"button\" class=\"close\" data-dismiss=\"modal\"><span aria-hidden=\"true\">&times;</span><span class=\"sr-only\">Close</span></button> <h4 class=\"modal-title\">Modal title</h4> </div> <div class=\"modal-body\"> <p>One fine body</p> </div> <div class=\"modal-footer\"> <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Close</button> <button type=\"button\" class=\"btn btn-primary\">Save changes</button> </div> </div> </div></div> .hide should not be used on .modal in Bootstrap v3. .hide在Bootstrap3中不能应用在.modal身上。 .modal必须含有tabindex和role属性123<div class=\"modal\" tabindex=\"-1\" role=\"dialog\"> ...</div> .modal-dialog元素必须含有role="document"123<div class=\"modal-dialog\" role=\"document\"> ...</div> .alert.alert 包含dismiss button时 必须包含 .alert-dismissible样式1234<div class=\"alert alert-warning alert-dismissible\" role=\"alert\"> <button type=\"button\" data-dismiss=\"alert\">Dismiss</button> Better check yourself, you're not looking too good.</div> .alert的.closebutton必须是第一个子节点1234<div class=\"alert alert-warning alert-dismissible\" role=\"alert\"> <button type=\"button\" class=\"close\" data-dismiss=\"alert\"><span aria-hidden=\"true\">&times;</span><span class=\"sr-only\">Close</span></button> Better check yourself, you're not looking too good.</div> media.media-left and .media-right 在.media 内部使用.12345678<div class=\"media\"> <div class=\"media-left\"> ... </div> <div class=\"media-body\"> ... </div></div> nav.navbar-left and .navbar-right should not be used outside of navbars.12345678910111213141516<nav class=\"navbar navbar-default\"> <div class=\"container-fluid\"> <div class=\"navbar-header\"> <button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#bs-example-navbar-42\"> <span class=\"sr-only\">Toggle navigation</span> <span class=\"icon-bar\"></span> <span class=\"icon-bar\"></span> <span class=\"icon-bar\"></span> </button> <a class=\"navbar-brand\" href=\"#\">Brand</a> </div> <div class=\"collapse navbar-collapse\" id=\"bs-example-navbar-42\"> <p class=\"navbar-text navbar-right\">Signed in as Mark Otto</p> </div> </div></nav> .carousel.carousel-inner 必须有一个活动状态的子节点 .item.active12345<div class=\"carousel-inner\" role=\"listbox\"> <div class=\"item active\">...</div> <div class=\"item\">...</div> ...</div> .carousel 必须有且只有一个 .carousel-inner 子节点12345678910111213141516<div id=\"carousel-example-generic\" class=\"carousel slide\"> <ol class=\"carousel-indicators\">...</ol> <div class=\"carousel-inner\" role=\"listbox\"> ... </div> <a class=\"left carousel-control\" href=\"#carousel-example-generic\" role=\"button\" data-slide=\"prev\"> <span class=\"glyphicon glyphicon-chevron-left\" aria-hidden=\"true\"></span> <span class=\"sr-only\">Previous</span> </a> <a class=\"right carousel-control\" href=\"#carousel-example-generic\" role=\"button\" data-slide=\"next\"> <span class=\"glyphicon glyphicon-chevron-right\" aria-hidden=\"true\"></span> <span class=\"sr-only\">Next</span> </a></div> 等等规则,详情见bootlint wiki 参考https://www.tutorialrepublic.com/twitter-bootstrap-tutorial/ https://github.com/twbs/bootlint/ https://github.com/twbs/bootlint/blob/master/dist/browser/bootlint.js","tags":[{"name":"Bootstrap","slug":"Bootstrap","permalink":"https://betgar.github.io/tags/Bootstrap/"}]},{"title":"iCheck使用总结","date":"2018-07-11T12:00:00.000Z","path":"2018/07/11/use-the-icheck/","text":"iCheck使用 注意:使用iCheck时需要自己引用相关的皮肤,官方提供了几套皮肤,每一套还有不同的颜色,使用之前一定要引入相关css,不然会出现checkbox无法显示的窘境。 初始化iCheck 具体的请参考官方文档。 引入样式CSS和JavaScript 123<script src=\"your-path/jquery.js\"></script><link href=\"your-path/minimal/red.css\" rel=\"stylesheet\"><script src=\"your-path/icheck.js\"></script> 添加HTML 1234<input type=\"checkbox\"><input type=\"checkbox\" checked><input type=\"radio\" name=\"iCheck\"><input type=\"radio\" name=\"iCheck\" checked> 初始化iCheck 123456789101112131415161718192021222324<!-- 例子1: 指定皮肤样式 --><script> $(document).ready(function(){ $('input').iCheck({ checkboxClass: 'icheckbox_minimal', // 指定的皮肤样式 radioClass: 'iradio_minimal', increaseArea: '20%' // optional }); });</script><!-- 例子2: 指定皮肤样式和颜色 --><script> $(document).ready(function(){ $('input').iCheck({ checkboxClass: 'icheckbox_minimal-red', radioClass: 'iradio_minimal-red', increaseArea: '20%' // optional }); });</script><!-- 例子3: html上指定皮肤 --><input type=\"checkbox\" checked class=\"minimal\"> 全选功能实现 实现一个全选功能。 1234567891011121314151617181920<div class=\"row\"> <div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"> <label> <input type=\"checkbox\" class=\"js-all\">checkbox2 </label> </div></div> <div class=\"row\" data-role=\"checkbox\"> <div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"> <label> <input type=\"checkbox\">checkbox1 </label> </div> <div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"> <label> <input type=\"checkbox\">checkbox2 </label> </div></div> 1234567891011121314151617181920212223$(function () { var checkAll = $('input.js-all'); var checkboxes = $('input[type=\"checkbox\"]', '[data-role=\"checkbox\"]'); $('input').iCheck(); // 全部选中或者不选. checkAll.on('ifChecked ifUnchecked', function(event) { if (event.type == 'ifChecked') { checkboxes.iCheck('check'); } else { checkboxes.iCheck('uncheck'); } }); // 改变全选checkbox状态. checkboxes.on('ifChanged', function(event){ if(checkboxes.filter(':checked').length == checkboxes.length) { checkAll.prop('checked', true); } else { checkAll.prop('checked', false); } checkAll.iCheck('update'); });}); 动态添加checkbox事件代理 动态添加checkbox事件不生效问题,应该使用事件代理。 12345678910111213<div class=\"row\" data-role=\"checkbox\"> <div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"> <label> <input type=\"checkbox\">checkbox1 </label> </div> <div class=\"col-xs-6 col-sm-6 col-md-6 col-lg-6\"> <label> <input type=\"checkbox\">checkbox2 </label> </div> <!-- 这里动态添加checkbox 时, js需要修改为事件代理模式 --></div> 1234567891011121314// 如果所有<div class=\"row\" data-role=\"checkbox\">// 内所有的checkbox都选中,或者不全选需要动态的改变“全选”checkbox状态// 因为有动态加入的checkbox,所以这里需要修改成代理事件var $checkboxContainer = $('.row[data-role=\"checkbox\"]');$checkboxContainer.on('ifChanged', function(event){ // checkboxes需要动态获取 var checkboxes = $('input[type=\"checkbox\"]', $checkboxContainer); if(checkboxes.filter(':checked').length == checkboxes.length) { checkAll.prop('checked', true); } else { checkAll.prop('checked', false); } checkAll.iCheck('update');}); 获取checked状态的checkbox iCheckbox是没有提供获取checked状态的方法,所以你要使用jQuery提供的prop方法。 12345678var checkboxes = $('input[type=\"checkbox\"]', $checkboxContainer);// 获取选中的checkboxvar allChecked = checkboxes.filter(':checked');// 获取checked状态$.each(checkboxes, function (checkbox, i) { console.log($(this).prop('checked')); // true | false}); 参考http://icheck.fronteed.com/ http://github.com/fronteed/iCheck/ https://stackoverflow.com/questions/17820080/function-select-all-and-icheck https://github.com/fronteed/iCheck/issues/44 https://www.cnblogs.com/xcsn/p/6307610.html","tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://betgar.github.io/tags/jQuery/"},{"name":"iCheck","slug":"iCheck","permalink":"https://betgar.github.io/tags/iCheck/"}]},{"title":"个人装机软件","date":"2018-07-03T14:46:12.000Z","path":"2018/07/03/software-for-daily/","text":"个人装机软件 总结一下个人windows装机软件,比较适合coder. 压缩软件 7-zip bindizip 下载软件 迅雷极速版 you-get proxyee-down 安全软件 火绒 辅助软件 ccleaner xmeters rainmeter(很漂亮也很复杂) everything freecommander 文档工具 ms office typora(markdown) notepad++ SumatraPDF office tool plus 浏览器 firefox(自动同步书签和多个终端的tabs) chrome 邮件 foxmail 通讯软件 QQ(TIM没广告) weixin 代理软件 shadowsocks 输入法 QQ拼音 搜狗输入法 cmd git for windows cmder cygwin ftp filezilla ssh putty xmanager 编辑器&IDE webStorm vscode eclipse spring tool suite plsql developer notepad++ 资料 可以看看amazing系列。 https://legacy.gitbook.com/@amazing-apps https://amazing-apps.gitbooks.io/windows-apps-that-amaze-us/zh-CN/","tags":[{"name":"software","slug":"software","permalink":"https://betgar.github.io/tags/software/"}]},{"title":"test-hexo-synax-again","date":"2018-06-26T15:30:00.000Z","path":"2018/06/26/test-hexo-synax-again/","text":"hexo语法测试 预定义参数测试","tags":[{"name":"hexo","slug":"hexo","permalink":"https://betgar.github.io/tags/hexo/"}]},{"title":"Android 开发环境部署","date":"2018-06-25T02:00:00.000Z","path":"2018/06/25/android-dev-env/","text":"Android 开发环境部署 android studio 3.0 android SDK Java环境配置 Java SDK安装:JDK8.X Java环境变量配置 我的电脑配置环境变量: JAVA_HOME D:\\Android\\jdk1.6.0_03 CLASSPATH %JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar; 3-PATH %JAVA_HOME%\\bin; Android Studio安装 android studio 解压就可以用. Android SDK manager 更新SDK 第一次启动android studio,有让你选择的android SDK的路径,然后勾选一些选项然后进行更新。 也可以跳过,然后启动android studio之后,进入configure搜索sdk进行更新(目前好像不存在科学上网的问题了)。 Android studio 常用插件 Butterknife Zelezny Parcelable Code Generator Prettify ADB Idea GsonFormat Android Drawable Importer Android Code Generator Android Material Design Icon Generator 参考:https://www.cnblogs.com/zhayh/p/6555850.html ADB环境变量 ADB环境变量配置,方便调试使用adb命令。 环境变量里面配置: ANDROID_HOME=你的anroid sdk路径 path=%ANDROID_HOME%\\platform-tools;%ANDROID_HOME%\\tools; 参考: https://www.cnblogs.com/yxysuanfa/p/7251253.html ADB usb driver安装 推荐使用adbinstaller http://adbdriver.com/","tags":[{"name":"android","slug":"android","permalink":"https://betgar.github.io/tags/android/"}]},{"title":"apt-cyg安装","date":"2018-06-04T00:29:00.000Z","path":"2018/06/04/apt-cyg/","text":"apt-cyg 因为使用在windows下使用cygwin,为了方便安装其它包,所以需要安装apt-cyg包管理器。 使用setup-x86_64.exe安装(apt-cyg) setup-x86_64.exe已经是cygwin的包管理器,所以其它包都可以通过它来安装。 别名是为了方便使用,设置只在当前的shell生效 alias cyg-get=”/cygdrive/d/path/to/cygwin/setup-x86_64.exe -q -P” 建议使用ftp://sourceware.org/pub/cygwin镜像比较快 http://mirrors.163.com/cygwin/cyg-get wget 安装wget 使用apt-cyg需要先安装wget 下载apt-cyg 下载apt-cyg保存到$root\\cygwin64\\bin 地址:https://raw.githubusercontent.com/transcode-open/apt-cyg/master/apt-cyg 设置镜像源1234# 添加权限chmod +x /bin/apt-cyg# 设置官方镜像apt-cyg mirror ftp://sourceware.org/pub/cygwin 安装chere的package chere可以把cygwin添加到资源管理器的右键菜单(windows explorer context menu) 123456# 安装chereapy-cyg install chere# 注册到context menuchere -i -t mintty -s bash# windows context menu 有Bash Prompt here apt-cyg源码 如果apt-cyg安装不成功,直接copy下文源码保存apt-cyg文件,放到cygwin的安装目录的bin下,添加一下执行权限(如果需要)。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673#!/bin/bash# apt-cyg: install tool for Cygwin similar to debian apt-get## The MIT License (MIT)## Copyright (c) 2013 Trans-code Design## Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the \"Software\"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:## The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.## THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.if [ ${BASH_VERSINFO}${BASH_VERSINFO[1]} -lt 42 ]then echo 'Bash version 4.2+ required' exitfiusage=\"\\NAME apt-cyg - package manager utilitySYNOPSIS apt-cyg [operation] [options] [targets]DESCRIPTION apt-cyg is a package management utility that tracks installed packages on a Cygwin system. Invoking apt-cyg involves specifying an operation with any potential options and targets to operate on. A target is usually a package name, file name, URL, or a search string. Targets can be provided as command line arguments.OPERATIONS install Install package(s). remove Remove package(s) from the system. update Download a fresh copy of the master package list (setup.ini) from the server defined in setup.rc. download Retrieve package(s) from the server, but do not install/upgrade anything. show Display information on given package(s). depends Produce a dependency tree for a package. rdepends Produce a tree of packages that depend on the named package. list Search each locally-installed package for names that match regexp. If no package names are provided in the command line, all installed packages will be queried. listall This will search each package in the master package list (setup.ini) for names that match regexp. category Display all packages that are members of a named category. listfiles List all files owned by a given package. Multiple packages can be specified on the command line. search Search for downloaded packages that own the specified file(s). The path can be relative or absolute, and one or more files can be specified. searchall Search cygwin.com to retrieve file information about packages. The provided target is considered to be a filename and searchall will return the package(s) which contain this file. mirror Set the mirror; a full URL to a location where the database, packages, and signatures for this repository can be found. If no URL is provided, display current mirror. cache Set the package cache directory. If a file is not found in cache directory, it will be downloaded. Unix and Windows forms are accepted, as well as absolute or regular paths. If no directory is provided, display current cache.OPTIONS --nodeps Specify this option to skip all dependency checks. --version Display version and exit.\"version=\"\\apt-cyg version 1The MIT License (MIT)Copyright (c) 2005-9 Stephen Jungels\"function wget { if command wget -h &>/dev/null then command wget \"$@\" else warn wget is not installed, using lynx as fallback set \"${*: -1}\" lynx -source \"$1\" > \"${1##*/}\" fi}function find-workspace { # default working directory and mirror # work wherever setup worked last, if possible cache=$(awk ' BEGIN { RS = \"\\n\\\\<\" FS = \"\\n\\t\" } $1 == \"last-cache\" { print $2 } ' /etc/setup/setup.rc) mirror=$(awk ' /last-mirror/ { getline print $1 } ' /etc/setup/setup.rc) mirrordir=$(sed ' s / %2f g s : %3a g ' <<< \"$mirror\") mkdir -p \"$cache/$mirrordir/$arch\" cd \"$cache/$mirrordir/$arch\" if [ -e setup.ini ] then return 0 else get-setup return 1 fi}function get-setup { touch setup.ini mv setup.ini setup.ini-save wget -N $mirror/$arch/setup.bz2 if [ -e setup.bz2 ] then bunzip2 setup.bz2 mv setup setup.ini echo Updated setup.ini else echo Error updating setup.ini, reverting mv setup.ini-save setup.ini fi}function check-packages { if [[ $pks ]] then return 0 else echo No packages found. return 1 fi}function warn { printf '\\e[1;31m%s\\e[m\\n' \"$*\" >&2}function apt-update { if find-workspace then get-setup fi}function apt-category { check-packages find-workspace for pkg in \"${pks[@]}\" do awk ' $1 == \"@\" { pck = $2 } $1 == \"category:\" && $0 ~ query { print pck } ' query=\"$pks\" setup.ini done}function apt-list { local sbq for pkg in \"${pks[@]}\" do let sbq++ && echo awk 'NR>1 && $1~pkg && $0=$1' pkg=\"$pkg\" /etc/setup/installed.db done let sbq && return awk 'NR>1 && $0=$1' /etc/setup/installed.db}function apt-listall { check-packages find-workspace local sbq for pkg in \"${pks[@]}\" do let sbq++ && echo awk '$1~pkg && $0=$1' RS='\\n\\n@ ' FS='\\n' pkg=\"$pkg\" setup.ini done}function apt-listfiles { check-packages find-workspace local pkg sbq for pkg in \"${pks[@]}\" do (( sbq++ )) && echo if [ ! -e /etc/setup/\"$pkg\".lst.gz ] then download \"$pkg\" fi gzip -cd /etc/setup/\"$pkg\".lst.gz done}function apt-show { find-workspace check-packages for pkg in \"${pks[@]}\" do (( notfirst++ )) && echo awk ' $1 == query { print fd++ } END { if (! fd) print \"Unable to locate package \" query } ' RS='\\n\\n@ ' FS='\\n' query=\"$pkg\" setup.ini done}function apt-depends { find-workspace check-packages for pkg in \"${pks[@]}\" do awk ' @include \"join\" $1 == \"@\" { apg = $2 } $1 == \"requires:\" { for (z=2; z<=NF; z++) reqs[apg][z-1] = $z } END { prpg(ENVIRON[\"pkg\"]) } function smartmatch(small, large, values) { for (each in large) values[large[each]] return small in values } function prpg(fpg) { if (smartmatch(fpg, spath)) return spath[length(spath)+1] = fpg print join(spath, 1, length(spath), \" > \") if (isarray(reqs[fpg])) for (each in reqs[fpg]) prpg(reqs[fpg][each]) delete spath[length(spath)] } ' setup.ini done}function apt-rdepends { find-workspace for pkg in \"${pks[@]}\" do awk ' @include \"join\" $1 == \"@\" { apg = $2 } $1 == \"requires:\" { for (z=2; z<=NF; z++) reqs[$z][length(reqs[$z])+1] = apg } END { prpg(ENVIRON[\"pkg\"]) } function smartmatch(small, large, values) { for (each in large) values[large[each]] return small in values } function prpg(fpg) { if (smartmatch(fpg, spath)) return spath[length(spath)+1] = fpg print join(spath, 1, length(spath), \" < \") if (isarray(reqs[fpg])) for (each in reqs[fpg]) prpg(reqs[fpg][each]) delete spath[length(spath)] } ' setup.ini done}function apt-download { check-packages find-workspace local pkg sbq for pkg in \"${pks[@]}\" do (( sbq++ )) && echo download \"$pkg\" done}function download { local pkg digest digactual pkg=$1 # look for package and save desc file awk '$1 == pc' RS='\\n\\n@ ' FS='\\n' pc=$pkg setup.ini > desc if [ ! -s desc ] then echo Unable to locate package $pkg exit 1 fi # download and unpack the bz2 or xz file # pick the latest version, which comes first set -- $(awk '$1 == \"install:\"' desc) if (( ! $# )) then echo 'Could not find \"install\" in package description: obsolete package?' exit 1 fi dn=$(dirname $2) bn=$(basename $2) # check the md5 digest=$4 case ${#digest} in 32) hash=md5sum ;; 128) hash=sha512sum ;; esac mkdir -p \"$cache/$mirrordir/$dn\" cd \"$cache/$mirrordir/$dn\" if ! test -e $bn || ! $hash -c <<< \"$digest $bn\" then wget -O $bn $mirror/$dn/$bn $hash -c <<< \"$digest $bn\" || exit fi tar tf $bn | gzip > /etc/setup/\"$pkg\".lst.gz cd ~- mv desc \"$cache/$mirrordir/$dn\" echo $dn $bn > /tmp/dwn}function apt-search { check-packages echo Searching downloaded packages... for pkg in \"${pks[@]}\" do key=$(type -P \"$pkg\" | sed s./..) [[ $key ]] || key=$pkg for manifest in /etc/setup/*.lst.gz do if gzip -cd $manifest | grep -q \"$key\" then package=$(sed ' s,/etc/setup/,, s,.lst.gz,, ' <<< $manifest) echo $package fi done done}function apt-searchall { cd /tmp for pkg in \"${pks[@]}\" do printf -v qs 'text=1&arch=%s&grep=%s' $arch \"$pkg\" wget -O matches cygwin.com/cgi-bin2/package-grep.cgi?\"$qs\" awk ' NR == 1 {next} mc[$1]++ {next} /-debuginfo-/ {next} /^cygwin32-/ {next} {print $1} ' FS=-[[:digit:]] matches done}function apt-install { check-packages find-workspace local pkg dn bn requires wr package sbq script for pkg in \"${pks[@]}\" do if grep -q \"^$pkg \" /etc/setup/installed.db then echo Package $pkg is already installed, skipping continue fi (( sbq++ )) && echo echo Installing $pkg download $pkg read dn bn </tmp/dwn echo Unpacking... cd \"$cache/$mirrordir/$dn\" tar -x -C / -f $bn # update the package database awk ' ins != 1 && pkg < $1 { print pkg, bz, 0 ins = 1 } 1 END { if (ins != 1) print pkg, bz, 0 } ' pkg=\"$pkg\" bz=$bn /etc/setup/installed.db > /tmp/awk.$$ mv /etc/setup/installed.db /etc/setup/installed.db-save mv /tmp/awk.$$ /etc/setup/installed.db [ -v nodeps ] && continue # recursively install required packages requires=$(awk '$1==\"requires\", $0=$2' FS=': ' desc) cd ~- wr=0 if [[ $requires ]] then echo Package $pkg requires the following packages, installing: echo $requires for package in $requires do if grep -q \"^$package \" /etc/setup/installed.db then echo Package $package is already installed, skipping continue fi apt-cyg install --noscripts $package || (( wr++ )) done fi if (( wr )) then echo some required packages did not install, continuing fi # run all postinstall scripts [ -v noscripts ] && continue find /etc/postinstall -name '*.sh' | while read script do echo Running $script $script mv $script $script.done done echo Package $pkg installed done}function apt-remove { check-packages cd /etc cygcheck awk bash bunzip2 grep gzip mv sed tar xz > setup/essential.lst for pkg in \"${pks[@]}\" do if ! grep -q \"^$pkg \" setup/installed.db then echo Package $pkg is not installed, skipping continue fi if [ ! -e setup/\"$pkg\".lst.gz ] then warn Package manifest missing, cannot remove $pkg. Exiting exit 1 fi gzip -dk setup/\"$pkg\".lst.gz awk ' NR == FNR { if ($NF) ess[$NF] next } $NF in ess { exit 1 } ' FS='[/\\\\\\\\]' setup/{essential,$pkg}.lst esn=$? if [ $esn = 0 ] then echo Removing $pkg if [ -e preremove/\"$pkg\".sh ] then preremove/\"$pkg\".sh rm preremove/\"$pkg\".sh fi mapfile dt < setup/\"$pkg\".lst for each in ${dt[*]} do [ -f /$each ] && rm /$each done for each in ${dt[*]} do [ -d /$each ] && rmdir --i /$each done rm -f setup/\"$pkg\".lst.gz postinstall/\"$pkg\".sh.done awk -i inplace '$1 != ENVIRON[\"pkg\"]' setup/installed.db echo Package $pkg removed fi rm setup/\"$pkg\".lst if [ $esn = 1 ] then warn apt-cyg cannot remove package $pkg, exiting exit 1 fi done}function apt-mirror { if [ \"$pks\" ] then awk -i inplace ' 1 /last-mirror/ { getline print \"\\t\" pks } ' pks=\"$pks\" /etc/setup/setup.rc echo Mirror set to \"$pks\". else awk ' /last-mirror/ { getline print $1 } ' /etc/setup/setup.rc fi}function apt-cache { if [ \"$pks\" ] then vas=$(cygpath -aw \"$pks\") awk -i inplace ' 1 /last-cache/ { getline print \"\\t\" vas } ' vas=\"${vas//\\\\/\\\\\\\\}\" /etc/setup/setup.rc echo Cache set to \"$vas\". else awk ' /last-cache/ { getline print $1 } ' /etc/setup/setup.rc fi}if [ -p /dev/stdin ]then mapfile -t pksfi# process optionsuntil [ $# = 0 ]do case \"$1\" in --nodeps) nodeps=1 shift ;; --noscripts) noscripts=1 shift ;; --version) printf \"$version\" exit ;; update) command=$1 shift ;; list | cache | remove | depends | listall | download | listfiles |\\ show | mirror | search | install | category | rdepends | searchall ) if [[ $command ]] then pks+=(\"$1\") else command=$1 fi shift ;; *) pks+=(\"$1\") shift ;; esacdoneset -aif type -t apt-$command | grep -q functionthen readonly arch=${HOSTTYPE/i6/x} apt-$commandelse printf \"$usage\"fi","tags":[{"name":"cygwin","slug":"cygwin","permalink":"https://betgar.github.io/tags/cygwin/"}]},{"title":"npm常用命令","date":"2018-06-04T00:00:00.000Z","path":"2018/06/04/npm-common-commands/","text":"npm常用命令 总结一下npm常用的命令。 123456789101112131415npm -vnpm install -g npm@latestnpm install -g npm@nextnpm install -g npm@versionnpm updatenpm outdatednpm uninstall lodash# 如需从 package.json 文件中删除依赖,需要在命令后添加参数 --savenpm uninstall --save lodash # package.json 文件中依赖devDependency,就是--save-dev 参数保存的# 必须通过 --save-dev 参数可以将其卸载。npm uninstall --save-dev lodash npm生命周期的命令 install: 安装依赖。 npm run install的默认值是node-gyp rebuild,前提是项目根目录下有binding.gyp文件。 build:构建项目。 npm 脚本有pre和post两个钩子。举例来说,build脚本命令的钩子就是prebuild和postbuild 。 12345678// package.json{ \"scripts\": { \"prebuild\": \"echo I run before the build script\", \"build\": \"cross-env NODE_ENV=production webpack\", \"postbuild\": \"echo I run after the build script\" }} rebuild:重新构建。 test:测试。 start:开发阶段启动服务。 上面代码中,npm run start的默认值是node server.js,前提是项目根目录下有server.js这个脚本。 restart:重新启动。 npm restart 是一个复合命令,实际上会执行三个脚本命令:stop、restart、start。具体的执行顺序如下。 prerestart prestop stop poststop restart prestart start poststart postrestart stop:停止服务。 publish:对外发布package。 republish npm default hook npm命令的默认钩子命令。 prepublish,postpublish preinstall,postinstall preuninstall,postuninstall preversion,postversion pretest,posttest prestop,poststop prestart,poststart prerestart,postrestart 注意: prepublish这个钩子不仅会在npm publish命令之前运行,还会在npm install(不带任何参数)命令之前运行。这种行为很容易让用户感到困惑,所以 npm 4 引入了一个新的钩子prepare,行为等同于prepublish,而从 npm 5 开始,prepublish将只在npm publish命令之前运行。 npm shortcut commands四个常用的 npm 脚本有简写形式。 npm start是npm run start npm stop是npm run stop的简写 npm test是npm run test的简写 npm restart是npm run stop && npm run restart && npm run start的简写 npm run是npm run-script的简写。 npm config 好多内容以后在补充。 npm scripts变量 node环境中可以引用package.json和bash等变量。 变量前缀:npm_package_npm 脚本有一个非常强大的功能,就是可以使用 npm 的内部变量。 首先,通过npm_package_前缀,npm 脚本可以拿到package.json里面的字段。比如,下面是一个package.json。 12345678> {> "name": "foo", > "version": "1.2.5",> "scripts": {> "view": "node view.js"> }> }> 那么,变量npm_package_name返回foo,变量npm_package_version返回1.2.5。 1234> // view.js> console.log(process.env.npm_package_name); // foo> console.log(process.env.npm_package_version); // 1.2.5> 上面代码中,我们通过环境变量process.env对象,拿到package.json的字段值。 变量前缀:$npm_package (bash变量)如果是 Bash 脚本,可以用$npm_package_name和$npm_package_version取到这两个值。 变量:npm_config_npm 脚本还可以通过npm_config_前缀,拿到 npm 的配置变量,即npm config get xxx命令返回的值。比如,当前模块的发行标签,可以通过npm_config_tag取到。 1234// config{ \"view\": \"echo $npm_config_tag\"} 环境变量覆盖package.json的config对象package.json里面的config对象,可以被环境变量覆盖。 12345{ "name" : "foo", "config" : { "port" : "8080" }, "scripts" : { "start" : "node server.js" }} 上面代码中,npm_package_config_port变量返回的是8080。这个值可以用下面的方法覆盖。 1$ npm config set foo:port 80 package.json 详情见:http://javascript.ruanyifeng.com/nodejs/packagejson.html package.lock.json npm5以后,每次npm install会自动创建package.lock.json,但是有一些潜规则。 如果package.json和package.lock.json一致,则使用package.lock.json中的配置。 如果package.json和package.lock.json不一致,则使用package.json中的配置。","tags":[{"name":"npm","slug":"npm","permalink":"https://betgar.github.io/tags/npm/"}]},{"title":"Node环境配置","date":"2018-05-31T16:00:00.000Z","path":"2018/06/01/node-env-config/","text":"Node环境配置 Node环境及配置参考 nvm for windows nvm是一个Node的版本管理模块,使用它可以随时切换Node环境。 注意: 安装 node 前保证自己电脑中的node,npm已卸载干净 nvm安装 nvm-windows 查看当前版本 1nvm --version 查看当前已安装 node 的版本 1nvm ls 安装某版本 node, 根据 LTS 查看此处安装 8.11.1 1nvm install 8.11.1 使用某版本 node 1nvm use 8.11.1 检查本地是否存在 node 1node -v nrm管理下载源 nrm 是为了解决切换 npm 源的问题,因为如果直接通过 npm 手动设置 registry 非常麻烦,并且不容易记忆,因此,才有了 nrm 的出现。npm的官方源对广大中国开发者来说较卡,因此推荐 cnpm 或者 taobao。 nrm 是为了解决切换npm 源的问题,因为如果直接通过 npm 手动设置 registry 非常麻烦,并且不容易记忆,因此,才有了nrm的出现。 npm的官方源对广大中国开发者来说较卡,因此推荐 cnpm 或者 taobao。 nrm的安装有了npm后,一切都变得非常简单,全局安装 nrm, 使用taobao源下载 1npm install -g nrm 查询当前有哪些可用源 1nrm ls 切换淘宝源 1nrm use taobao 测试源延迟毫秒数 (也与当前网络环境有关) 1nrm test python 安装 node-gyp模块需要安装python(python (v2.7 recommended, v3.x.x is not supported) python依赖.net framework 4.5 解决方式1: 12# run as Administratornpm install --global --production windows-build-tools 解决方式2:自己到微软的官网下载最新的.net framework安装。 node-gyp配置python 注意: node-gyp官网 因为python有v2和v3版本,并且两个版本不兼容,如果电脑上有应用软件使用python的版本不同, 最好不要把python路径配置到系统path中。 12# 如果你使用node-gyp单独编译一些模块,则这样配置(多数情况下是通过npm调用的)node-gyp --python /path/to/python2.7 npm配置python 如果使用npm 调用的node-gyp,则使用npm配置python就可以 1npm config set python /path/to/executable/python2.7 verdaccio安装 如果搭建npm私服可以参考。verdaccio官网 verdaccio是用来搭建npm私服,已经安装过的模块会缓存起来,以后安装依赖是会优先查看缓存。 安装1npm i -g verdaccio 启动1verdaccio 配置 配置主要修改使用taobao源和端口修改 12# 配置文件地址:我的window 10是.config非官网说的.local# C:\\Users\\你的用户名称\\.config\\verdaccio 修改默认端口(如果有需要) 添加uplinks 修改使用的proxy 12345678910111213141516171819202122232425262728293031323334# 修改默认的端口listen: 0.0.0.0:10086# a list of other known repositories we can talk touplinks: npmjs: url: https://registry.npmjs.org/ # 添加taobao taobao: url: https://registry.npm.taobao.orgpackages: '@*/*': # scoped packages access: $all publish: $authenticated # 使用taobao proxy: taobao '**': # allow all users (including non-authenticated users) to read and # publish all packages # # you can specify usernames/groupnames (depending on your auth plugin) # and three keywords: \"$all\", \"$anonymous\", \"$authenticated\" access: $all # allow all known users to publish packages # (anyone can register by default, remember?) publish: $authenticated # if package is not available locally, proxy requests to 'npmjs' registry proxy: taobao # 使用taobao chrome driver安装 使用淘宝源 1npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver vue项目使用sass 如果使用vue-cli创建项目,并且使用.vue单文件,使用scss来写CSS需要安装两个依赖。 vue-cli默认没有安装node-sass和sass-loader。 安装依赖12npm install node-sass --save-devnpm install sass-loader --save-dev 配置webpack12345// webpack.base.config.js 中添加 { test: /\\.scss$/, loaders: [\"style\", \"css\", \"sass\"] } 参考资料Node.js简介以及环境配置","tags":[{"name":"node","slug":"node","permalink":"https://betgar.github.io/tags/node/"}]},{"title":"jQuery最佳实践","date":"2018-04-19T12:00:00.000Z","path":"2018/04/19/jquery-best-practice/","text":"jQuery最佳实践 本文介绍一些使用jQuery的通用标准和最佳实践,这些标准不涵盖Javascript的标准和最佳实践,英文原文地址是 http://lab.abhinayrathore.com/jquery-standards/。 中文地址: http://keenwon.com/955.html 加载jQuery1、尽量使用CDN加载: 12<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js\"></script><script>window.jQuery || document.write('<script src=\"js/jquery-1.11.0.min.js\" type=\"text/javascript\"><\\/script>')</script> 2、就像上面的代码一样,在CDN加载失败时,要回源到本地服务器的同版本jQuery。 3、使用依赖协议的URL(不加http:和https:,直接使用//),参看上面的例子。 4、尽可能在页面底部加载jQuery和javascript。 5、如何选择jQuery版本? 如果要兼容IE6/7/8,不要使用2.x版本的 对于新的应用,如果不考虑一些插件的兼容性问题,尽可能的使用最新版 从CDN加载jQuery的时候,使用完整的版本号(例如,使用1.11.0,不用1.11和1) 不要加载多个版本的jQuery 6、如果你使用了Prototype, MooTools, Zepto等,尝试使用jQuery代替$。可以使用$.noConflict()让出$的控制权。 jQuery变量1、所有被缓存的jQuery对象,变量名以$开头。 2、总是缓存jQuery选择器返回的对象,方便复用: 12var $myDiv = $(\"#myDiv\");$myDiv.click(function(){...}); 3、使用驼峰命名。 选择器1、尽可能的使用ID选择器。ID选择器使用document.getElementById(),所以更快。 2、当使用类选择器的时候,不要加DOM元素类型: 12var $products = $(\"div.products\"); // 慢var $products = $(\".products\"); // 快 3、使用find查找ID->Child的嵌套查询,.find()方法更快,因为第一次选择没有使用jQuery的选择器引擎: 12345// 差, 全部使用jQuery的选择器引擎var $productIds = $(\"#products div.id\"); // 好, #products使用document.getElementById(), 只有div.id需要使用jQuery的选择器引擎var $productIds = $(\"#products\").find(\"div.id\"); 4、越靠右侧越具体: 12345// 差$(\"div.data .gonzalez\"); // 最佳$(\".data td.gonzalez\"); 5、选择器不要太“具体”: 1234$(\".data table.attendees td.gonzalez\"); // 更好,尽可能省去中间部分$(\".data td.gonzalez\"); 6、给选择器指定上下文(限定范围): 12345// 慢,因为它在整个DOM结构中查找$('.class'); // 快,因为它只查找 class-container 下的.$('.class', '#class-container'); 7、避免使用“*”: 12$('div.container > *'); // 差$('div.container').children(); // 好 8、避免隐式使用“*” 12$('div.someclass :radio'); // 差,还是用了“*”$('div.someclass input:radio'); // 好 9、不要使用多个ID选择器,或者嵌套使用ID选择器,单独的ID选择器使用document.getElementById(),速度很快: 1234$('#outer #inner'); // 差$('div#inner'); // 差$('.outer-container #inner'); // 差$('#inner'); // 好, 调用 document.getElementById() DOM操作1、先把元素分离出DOM结构,再进行复杂的操作,之后再附加回DOM中。不明白原因的话,可以了解下.detach()这个方法 123var $myList = $(\"#list-container > ul\").detach();// 对 $myList 执行大量复杂操作$myList.appendTo(\"#list-container\"); 2、使用字符串连接或者array.join(),少用.append(),点击这里查看性能比较。 1234567891011121314151617181920// 慢var $myList = $(\"#list\");for(var i = 0; i < 10000; i++){ $myList.append(\"<li>\"+i+\"</li>\");} // 快var $myList = $(\"#list\");var list = \"\";for(var i = 0; i < 10000; i++){ list += \"<li>\"+i+\"</li>\";}$myList.html(list); // 更快var array = []; for(var i = 0; i < 10000; i++){ array[i] = \"<li>\"+i+\"</li>\"; }$myList.html(array.join('')); 3、不要对不存在的元素做操作: 12345678// 差: 在发现该ID不存在之前,已经执行了3个函数$(\"#nosuchthing\").slideUp(); // 好var $mySelection = $(\"#nosuchthing\");if ($mySelection.length) { $mySelection.slideUp();} 事件1、一个页面只使用一个Document ready,便于调试和跟踪。 2、不要使用匿名函数绑定事件,匿名函数不利于调试,复用,测试,debug: 123$(\"#myLink\").on(\"click\", function(){...}); // 差 // 好function myLinkClickHandler(){...}$(\"#myLink\").on(\"click\", myLinkClickHandler); 3、Document ready 不要用匿名函数,原因同上: 1234567$(function(){ ... }); // 差: 你没办法写单元测试和复用 // 好$(initPage); // or $(document).ready(initPage);function initPage(){ //...} 4、Document ready 的处理函数应该写在外部文件,内联的javascript可以在一些初始化设置之后调用 ready 函数,例如: 12345<script src=\"my-document-ready.js\"></script><script> // 做一些初始化设置. $(document).ready(initPage); // or $(initPage); initPage函数在外部文件中</script> 5、不要把事件直接写在HTML元素上,这样不方便调试: 1<a id=\"myLink\" href=\"#\" onclick=\"myEventHandler();\">my link</a> <!-- 差--> 1$(\"#myLink\").on(\"click\", myEventHandler); // 好 6、尽可能的给事件加上命名空间。这样方便解除绑定,而不影响该DOM元素的其他事件: 123$(\"#myLink\").on(\"click.mySpecialClick\", myEventHandler); // 好// 之后,很容易的解除绑定$(\"#myLink\").unbind(\"click.mySpecialClick\"); AJAX1、使用.ajax(),避免.getJson()或者.get(),它们在内部也是调用.ajax()的。 2、不要在https的站点上请求http,使用依赖协议的URL 3、不要把数据加在url上,使用data属性。 1234567891011// 可读性差$.ajax({ url: \"something.php?param1=test1&param2=test2\", ....}); // 可读性好$.ajax({ url: \"something.php\", data: { param1: test1, param2: test2 }}); 4、指定dataType属性,更容易知道数据类型。 5、对于ajax加载的内容,使用委托绑定事件,这样可以在元素不存在的时候执行绑定。(之后ajax加载进DOM结构中) 1$(\"#parent-container\").on(\"click\", \"a\", delegatedClickHandlerForAjax); 6、使用Promise 123456$.ajax({ ... }).then(successHandler, failureHandler); // 或者var jqxhr = $.ajax({ ... });jqxhr.done(successHandler);jqxhr.fail(failureHandler); 7、抽象出ajax模板,方便复用: 1234567891011121314var jqxhr = $.ajax({ url: url, type: \"GET\", // default is GET but you can use other verbs based on your needs. cache: true, // default is true, but false for dataType 'script' and 'jsonp', so set it on need basis. data: {}, // add your request parameters in the data object. dataType: \"json\", // specify the dataType for future reference jsonp: \"callback\", // only specify this to match the name of callback parameter your API is expecting for JSONP requests. statusCode: { // if you want to handle specific error codes, use the status code mapping settings. 404: handler404, 500: handler500 }});jqxhr.done(successHandler);jqxhr.fail(failureHandler); 动画效果1、Adopt a restrained and consistent approach to implementing animation functionality(大意:采取受限制的和统一的方法来实现动画功能。) 2、满足用户体验即可,不要做过多的动画: 尽量使用简单的show/hide,slideUp/slideDown来切换元素 尽量使用jQuery预定义的时间间隔(durations),“slow”,“fast”或者400(中等) 插件1、选择兼容性好,文档,测试齐全何社区支持好的插件。 2、检查插件在不同版本jQuery下的兼容性。 3、任何参加的可复用的组件,都应该以插件的形式实现。 链式调用1、使用链式调用代替变量缓存和多次调用选择器: 1$(\"#myDiv\").addClass(\"error\").show(); 2、当链式调用超过3级的时候,适当的换行增加可读性: 12345$(\"#myLink\") .addClass(\"bold\") .on(\"click\", myClickHandler) .on(\"mouseover\", myMouseOverHandler) .show(); 3、链太长的时候,缓存中间对象是可以接受的。 其他1、参数尽量使用对象形式: 1234567$myLink.attr(\"href\", \"#\").attr(\"title\", \"my link\").attr(\"rel\", \"external\"); // 差, 调用三次attr()// 好, 调用一次$myLink.attr({ href: \"#\", title: \"my link\", rel: \"external\"}); 2、尽量不要把css混在js中: 1.error { color: red; font-weight: bold; } /* 好*/ 12$(\"#mydiv\").css({'color':red, 'font-weight':'bold'}); // 差$(\"#mydiv\").addClass(\"error\"); // 好 3、不要使用过时的方法,注意新版更新时弃用的方法,不要使用它们。 4、必要的时候使用原生js,这里看查看性能对比 http://jsperf.com/document-getelementbyid-vs-jquery/3 12$(\"#myId\"); // 还是有一点点点点慢...document.getElementById(\"myId\");","tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://betgar.github.io/tags/jQuery/"}]},{"title":"qem-manual(使用手册)","date":"2018-04-17T12:00:00.000Z","path":"2018/04/17/qem-manual/","text":"qem-manual(使用手册) q$error_manager使用手册 (DBMS_UTILITY.format_error_backtrace()也挺好用) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455SELECT * FROM q$log;SELECT * FROM q$error;SELECT * FROM q$error_context;SELECT * FROM q$error_instance;DELETE FROM q$error_instance;DELETE FROM q$error;---------------------------- q$error_manager install-- 执行qem$install.sql--------------------------- q$error_manager.pks-- q$error_manager.pkb-- 清空q$logCALL q$error_manager.clear_trace();-- 查询traceCALL q$error_manager.show_trace(from_id_in => NULL, context_like_in => 'km_sum_with_obj');-- A查询CALL q$error_manager.show_trace(from_id_in => NULL, context_like_in => 'km_sum_with_obj', text_like_in => 'XTCACMERGEDATA');-- 等价A查询SELECT * FROM q$log l WHERE l.context LIKE '%km_sum_with_obj' AND l.text LIKE '%XTCACMERGEDATA%' ORDER BY l.ID;execsql;-------------- 跟踪代码/*IF NOT q$error_manager.trace_enabled(context_in => 'km_sum_with_obj') THEN q$error_manager.trace_on(context_like_in => 'km_sum_with_obj');END IF;*/----------- 记录/*IF q$error_manager.trace_enabled('km_sum_with_obj') THEN q$error_manager.trace(context_in => 'km_sum_with_obj',text_in => V_SQLSTRING);END IF;*/------------- 记录错误q$error_manager.error_trace_on();q$error_manager.register_error(error_code_in => SQLCODE, error_instance_id_out => error_instance_id, text_in => dbms_utility.format_error_backtrace); q$error_manager.error_trace_off(); Usageregister_error qem_demo_register_error.sql 123456789101112131415161718BEGIN DELETE FROM q$error_instance; FOR rec IN (SELECT * FROM employees) LOOP DBMS_OUTPUT.put_line (rec.last_name); q$error_manager.register_error (error_name_in => 'UNANTICIPATED-ERROR' , text_in => 'My message here: ' || rec.last_name , name1_in => 'FIRSTNAME' , value1_in => rec.first_name ); q$error_manager.mark_q$error_handled; END LOOP; COMMIT;END; trace logging with multiple contexts qem_demo_mult_contexts.sql 123456789101112131415161718192021222324252627282930313233343536/*Test the trace logging with multiple contexts*/CREATE OR REPLACE PROCEDURE dept_sal (department_id_in IN PLS_INTEGER)IS l_max_salary NUMBER;BEGIN IF q$error_manager.trace_enabled('context1') THEN q$error_manager.trace ('context1', department_id_in); END IF; l_max_salary := CASE WHEN department_id_in > 100 THEN 10000 ELSE 20000 END; IF q$error_manager.trace_enabled('context2') THEN q$error_manager.trace ('context2', l_max_salary); END IF;END dept_sal;/BEGIN q$error_manager.trace_on ('context2,context1'); q$error_manager.toscreen;-- or use q$error_manager.totable so the output is writed to the q$log table dept_sal (50); dept_sal (200); IF q$error_manager.trace_enabled('context3') THEN q$error_manager.trace ('context3', 'All done!'); END IF;END;/ discard_error_state qem_demo_discard_error_state.sql 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354/* Raise and propagate an Oracle error, thentrap that error and extract information about the error.*/CREATE OR REPLACE PROCEDURE dept_sal (department_id_in IN PLS_INTEGER)IS l_max_salary NUMBER;BEGIN l_max_salary := CASE WHEN department_id_in > 100 THEN 10000 ELSE 20000 END; BEGIN IF l_max_salary = 10000 THEN DBMS_OUTPUT.put_line ('Raising DUP_VAL_ON_INDEX'); RAISE DUP_VAL_ON_INDEX; ELSE DBMS_OUTPUT.put_line ('Raising NO_DATA_FOUND'); RAISE NO_DATA_FOUND; END IF; EXCEPTION WHEN OTHERS THEN /* Trap the error, add some context, pass it along. */ q$error_manager.raise_unanticipated (name1_in => 'DEPARTMENT ID' , value1_in => department_id_in , name2_in => 'MAX SALARY' , value2_in => l_max_salary , name3_in => 'TABLE_NAME' , value3_in => 'DEPARTMENTS' , name4_in => 'OWNER' , value4_in => USER ); END;EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);END dept_sal;/BEGIN DBMS_OUTPUT.put_line ('Keep Error State:'); q$error_manager.keep_error_state; dept_sal (50); dept_sal (200); DBMS_OUTPUT.put_line ('Discard Error State:'); q$error_manager.discard_error_state; dept_sal (50); dept_sal (200);END;/ q$error_manager安装 qem$install.sql文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322/*| This program is a part of the Quest Error Manager for Oracle.| This product is freeware and is not supported by Quest.| | www.quest.com| | Copyright, Quest Software, Inc, 2007| All rights reserved?2008 Quest Software, Inc.ALL RIGHTS RESERVED.Redistribution and use of the Quest Error Manager for Oracle software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain (i) the following copyright notice: \"?008 Quest Software, Inc. All rights reserved,\" (ii) this list of conditions, and (iii) the disclaimer below. 2. Redistributions in binary form must reproduce (i) the following copyright notice: \"?008 Quest Software, Inc. All rights reserved,\" (ii) this list of conditions, and (iii) the disclaimer below, in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of the Quest Error Manager for Oracle software must display the following acknowledgement: This product includes software developed by Quest Software, Inc. and its contributors.4. Neither the name of Quest Software, Inc. nor the name its affiliates, subsidiaries or contributors may be used to endorse or promote products derived from the Quest Error Manager for Oracle software without specific prior written permission from Quest Software, Inc. Disclaimer:THIS SOFTWARE IS PROVIDED BY QUEST SOFTWARE, INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL QUEST SOFTWARE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE QUEST ERROR MANAGER SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/SPOOL qem$install.logCREATE SEQUENCE q$error_seq/CREATE SEQUENCE q$log_seq/CREATE SEQUENCE q$error_context_seq/CREATE SEQUENCE q$error_instance_seq/CREATE TABLE q$error_context( id INTEGER , error_instance_id INTEGER , name VARCHAR2 (500) NOT NULL , VALUE VARCHAR2 (4000) , created_on TIMESTAMP , created_by VARCHAR2 (100) , changed_on TIMESTAMP , changed_by VARCHAR2 (100))//* 1.3.3 If QEM is already installed, use the alter statements to change the datatypes */ALTER TABLE q$error_context MODIFY (created_on TIMESTAMP, changed_on TIMESTAMP)/COMMENT ON TABLE q$error_context IS'Actual value for the named context for a given error instance.'/CREATE TABLE q$error( id INTEGER , error_category_name VARCHAR2 (500) , code INTEGER , name VARCHAR2 (500) , description VARCHAR2 (4000) , substitute_string VARCHAR2 (4000) , recommendation VARCHAR2 (4000) , created_on TIMESTAMP , created_by VARCHAR2 (100) , changed_on TIMESTAMP , changed_by VARCHAR2 (100))//* 1.3.3 If QEM is already installed, use the alter statements to change the datatypes */ALTER TABLE q$error MODIFY (created_on TIMESTAMP, changed_on TIMESTAMP)/COMMENT ON TABLE q$error IS'The set of pre-defined errors copied from sa_error when app was deployed'/CREATE TABLE q$error_instance( id INTEGER , error_id INTEGER , error_stack VARCHAR2 (4000) , call_stack VARCHAR2 (4000) , MESSAGE VARCHAR2 (4000) , system_error_code INTEGER , system_error_message VARCHAR2 (4000) , environment_info VARCHAR2 (4000) , created_on TIMESTAMP , created_by VARCHAR2 (100) , changed_on TIMESTAMP , changed_by VARCHAR2 (100))//* 1.3.3 If QEM is already installed, use the alter statements to change the datatypes */ALTER TABLE q$error_instance MODIFY (created_on TIMESTAMP, changed_on TIMESTAMP)/COMMENT ON TABLE q$error_instance IS'A particular instance of an error that occurs in the application'/CREATE TABLE q$log( id INTEGER , context VARCHAR2 (500) , text VARCHAR2 (4000) , call_stack VARCHAR2 (4000) , created_on TIMESTAMP , created_by VARCHAR2 (100) , changed_on TIMESTAMP , changed_by VARCHAR2 (100))//* 1.3.3 If QEM is already installed, use the alter statements to change the datatypes */ALTER TABLE q$log MODIFY (created_on TIMESTAMP, changed_on TIMESTAMP)/CREATE UNIQUE INDEX q$error_context_un ON q$error_context (error_instance_id, name)/CREATE UNIQUE INDEX q$error_context_pk ON q$error_context (id)/CREATE UNIQUE INDEX q$error_pk ON q$error (id)/CREATE UNIQUE INDEX q$error_code_pk ON q$error (code)/CREATE UNIQUE INDEX q$error_instance_pk ON q$error_instance (id)/CREATE UNIQUE INDEX q$log_pk ON q$log (id)/ALTER TABLE q$error_context ADD ( CONSTRAINT q$error_context_pk PRIMARY KEY (id))/ALTER TABLE q$error ADD ( CONSTRAINT q$error_pk PRIMARY KEY (id))/ALTER TABLE q$error_instance ADD ( CONSTRAINT q$error_instance_pk PRIMARY KEY (id))/ALTER TABLE q$log ADD ( CONSTRAINT q$log_pk PRIMARY KEY (id))/ALTER TABLE q$error_context ADD ( CONSTRAINT q$error_context_errinst_fk FOREIGN KEY (error_instance_id) REFERENCES q$error_instance (id) ON DELETE CASCADE)/ALTER TABLE q$error_instance ADD ( CONSTRAINT q$error_instance_err_fk FOREIGN KEY (error_id) REFERENCES q$error (id) ON DELETE CASCADE)//* 1.2.17 Provided by Filipe de Silva; add env info. */CREATE OR REPLACE TRIGGER q$error_instance_ir BEFORE INSERT ON q$error_instance REFERENCING OLD AS old NEW AS new FOR EACH ROWBEGIN :new.created_on := SYSDATE; :new.created_by := SYS_CONTEXT ('USERENV', 'SESSION_USER'); :new.environment_info := 'instance: ' || SYS_CONTEXT ('USERENV', 'INSTANCE') || '/' || SYS_CONTEXT ('USERENV', 'INSTANCE_NAME') || CHR (10) || 'db_name: ' || SYS_CONTEXT ('USERENV', 'DB_NAME') || CHR (10) || 'db_domain: ' || SYS_CONTEXT ('USERENV', 'DB_DOMAIN') || CHR (10) || 'host: ' || SYS_CONTEXT ('USERENV', 'SERVER_HOST') || CHR (10) || 'service_name: ' || SYS_CONTEXT ('USERENV', 'SERVICE_NAME') || CHR (10) || '--' || CHR (10) || 'session_user: ' || SYS_CONTEXT ('USERENV', 'SESSION_USER') || CHR (10) || 'session_id: ' || SYS_CONTEXT ('USERENV', 'SESSIONID') || CHR (10) || '--' || CHR (10) || 'host: ' || SYS_CONTEXT ('USERENV', 'HOST') || CHR (10) || 'ip_address: ' || SYS_CONTEXT ('USERENV', 'IP_ADDRESS') || CHR (10) || 'os_user: ' || SYS_CONTEXT ('USERENV', 'OS_USER') || CHR (10) || '--' || CHR (10) || 'module: ' || SYS_CONTEXT ('USERENV', 'MODULE') || CHR (10) || 'action: ' || SYS_CONTEXT ('USERENV', 'ACTION') || CHR (10) || 'client_identifier: ' || SYS_CONTEXT ('USERENV', 'CLIENT_IDENTIFIER') || CHR (10) || 'client_info: ' || SYS_CONTEXT ('USERENV', 'CLIENT_INFO') || CHR (10) || '--' || CHR (10) || 'bg_job_id: ' || SYS_CONTEXT ('USERENV', 'BG_JOB_ID') || CHR (10) || 'fg_job_id: ' || SYS_CONTEXT ('USERENV', 'FG_JOB_ID') || CHR (10); /* 1.3.7 Provided by Filipe Silva */ <<debug_web_variables>> BEGIN FOR i IN 1 .. OWA.num_cgi_vars LOOP :new.environment_info := :new.environment_info || OWA.cgi_var_name (i) || ': ' || OWA.cgi_var_val (i) || CHR (10); END LOOP; EXCEPTION WHEN VALUE_ERROR THEN NULL; END debug_web_variables; END;//* 1.3.7 Trigger needed to allow DBA partition of table. Provided by Filipe Silva. */CREATE OR REPLACE TRIGGER \"Q$ERROR_CONTEXT_IR\" BEFORE INSERT ON \"Q$ERROR_CONTEXT\" FOR EACH ROW BEGIN :new.created_on := SYSTIMESTAMP; :new.created_by := SYS_CONTEXT ('USERENV', 'SESSION_USER');END;//* 1.3.7 Populate the changed* columns */CREATE OR REPLACE TRIGGER \"Q$ERROR_CONTEXT_UR\" BEFORE UPDATE ON \"Q$ERROR_CONTEXT\" FOR EACH ROW BEGIN :new.changed_on := SYSTIMESTAMP; :new.changed_by := SYS_CONTEXT ('USERENV', 'SESSION_USER');END;/CREATE OR REPLACE TRIGGER \"Q$ERROR_INSTANCE_UR\" BEFORE UPDATE ON \"Q$ERROR_INSTANCE\" FOR EACH ROW BEGIN :new.changed_on := SYSTIMESTAMP; :new.changed_by := SYS_CONTEXT ('USERENV', 'SESSION_USER');END;/@@q$error_manager.pks@@q$error_manager.pkbSPOOL OFF q$error_manager包声明 q$error_manager.pks 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850create or replace PACKAGE q$error_manager/*| This program is a part of the Quest Error Manager for Oracle.| This product is freeware and is not supported by Quest.|| www.quest.com|?2009 Quest Software, Inc.ALL RIGHTS RESERVED.Redistribution and use of the Quest Error Manager for Oracle software in source and binary forms,with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain (i) the following copyright notice: \"?008 Quest Software, Inc.All rights reserved,\" (ii) this list of conditions, and (iii) the disclaimer below.2. Redistributions in binary form must reproduce (i) the following copyright notice:\"?008 Quest Software, Inc. All rights reserved,\" (ii) this list of conditions, and (iii)the disclaimer below, in the documentation and/or other materials provided with the distribution.3. All advertising materials mentioning features or use of the Quest Error Managerfor Oracle software must display the following acknowledgement:This product includes software developed by Quest Software, Inc. and its contributors.4. Neither the name of Quest Software, Inc. nor the name its affiliates, subsidiariesor contributors may be used to endorse or promote products derived from the Quest ErrorManager for Oracle software without specific prior written permission from Quest Software, Inc.Disclaimer:THIS SOFTWARE IS PROVIDED BY QUEST SOFTWARE, INC. AND ITS CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL QUEST SOFTWARE OR ITSCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTEGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THEQUEST ERROR MANAGER SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/IS c_version CONSTANT PLS_INTEGER := 1; c_release CONSTANT PLS_INTEGER := 4; c_subrelease CONSTANT PLS_INTEGER := 1; /* Modification History Date Who Vrsn What ----------- ----- ------ ------------------------------------------------------- Oct 15 2010 FS 1.4.1 * removed declaration of unreference variables * labeled end tags * discard_error_state: clear state already stored Jul 27 2010 FS 1.4.0 * add error_category_name field to error_info_rt * minor refactoring * add more last_* functions and last_error_info function to return the record * add option to disregard the remaining_context in the message Jul 23 2010 FS 1.3.8 * make register_error truncate text_in to 4000 to make the insert possible (thanks to Bill Pribyl for reporting) * add truncante to 4000 to DBMS_UTILITY.format* output when storing to table Mar 22 2010 FS 1.3.7 * add trigger to populate environmental info to q$error_context * q$error_instance_ir trigger: add web environment variables log (if available) * corrected commentaries in example scripts and cleaned unused variables * add missing example file from the last versions: QEM in APEX Mar 04 2010 SF 1.3.6 * Change all argument names in raise/register/add from err_ to error_ for consistency. Synch up with QCGU scripts. Feb 19 2010 SF 1.3.5 * Add last* functions so that you can easily get values for last-raised error and among other things test more easily (request from Aci Polajnar). Dec 01 2009 SF 1.3.4 * Add clean_up_errors to help users remove error information. * Add grab_settings to register_error (error_name_in, text_in) overload. It was missing. * Remove redundant overloading of register_error (Why did Oracle let me COMPILE it the way it was, I wonder...) Oct 05 2009 SF 1.3.3 Change audit dates to TIMESTAMP from DATE Sep 12 2009 SF 1.3.2 If error stack is empty assign call stack to it. Add \"simple assert\" routines that simply call RAE. Add PROCEDURE clean_up_errors to remove rows from q$error_instance and q$error_context. Idea from Dennis Anderson. Aug 18 2009 SF 1.3.1 Add fields to the error_info_rt and overloadings to also return that information through parameters. Also make sure that environment info is passed back through get_error_info (kudos to Neal Ala for both providing specs and testing the release). Fix bug in exc section of pl (Thanks, Volker Bartuseck) Dec 12 2008 SF 1.2.19 Change trigger intro'd in 1.2.17 so that you do NOT need access to v$session to run QEM. It will just ignore the environment info when running the trigger. Dec 9 2008 SF 1.2.18 Avoid context strings > 500 characters. Thanks, Mike Pinker! Add ability to specify multiple distinct contexts. Nov 10 2008 SF/FS 1.2.17 Minor fixes from feedback by Filipe Silva, add trigger to populate environmental info. Also, offer toggle to avoid the need to mark an error as unhandled. Jul 12 2008 SF 1.2.16 Avoid VALUE_ERROR exception for sqlcode_name. July 2008 SF 1.2.15 register_oracle_error RAISES an error. Should not do that. Thanks to Filipe de Silva for pointing this out. Also add overloading to return error instance ID. Mar 12 2008 SF 1.2.14 Minor refactorings from feedback by Volker Bartusek. Nov 27 2007 SF 1.2.13 REMOVE DBMS_ERRLOG - only available in 10gR2. Will put in separate utility that will come with QEM. Nov 11 2007 SF 1.2.12 Allow user to show error info in message. Oct 1 2007 SF 1.2.11 * Start tracking version. * Change q$error error code for DUPVAL to -1. * Add raise/register_oracle_error alternative. * Obtain most recent instance ID from package global g_error_instance_raised instead of parsing the Oracle error message. This is set in the session each time an error is registered. Sep 27 2007 SF SF Add dbms_Errlog functionality Sep 26 2007 SF SF Add raise_unaticipated Sep 21 2007 SF SF Add overloadings with different column names err_instance instead of error_instance to support backward compatibility with QCGU generated code. Sep 5 2007 SF SF Add \"not yet implemented\" program */ c_newline CONSTANT CHAR (1) := CHR (10); TYPE weak_refcursor IS REF CURSOR; SUBTYPE maxvarchar2_t IS VARCHAR2 (32767); -- public global constants c_varchar2_max_length CONSTANT PLS_INTEGER := 32767; /* 1.4.0 */ c_varchar2_max_length_db CONSTANT PLS_INTEGER := 4000; c_subst_char CONSTANT VARCHAR2 (1) := '$'; c_error_prefix CONSTANT VARCHAR2 (4) := 'QEM'; c_error_prefix_len CONSTANT PLS_INTEGER := 3; c_error_code_len CONSTANT PLS_INTEGER := 20; c_oracle_separator CONSTANT VARCHAR2 (5) := '-ORA-'; c_oracle_code_len CONSTANT PLS_INTEGER := 5; -- public global user-defined types -- used to return error info back to a calling context TYPE error_info_rt IS RECORD ( code q$error.code%TYPE ,name q$error.name%TYPE ,system_error_code q$error_instance.system_error_code%TYPE ,system_error_message q$error_instance.system_error_message%TYPE ,text q$error_instance.MESSAGE%TYPE ,recommendation q$error.recommendation%TYPE ,error_stack q$error_instance.error_stack%TYPE ,call_stack q$error_instance.call_stack%TYPE ,environment_info q$error_instance.environment_info%TYPE /* Added in 1.3 */ ,id q$error_instance.id%TYPE ,MESSAGE q$error_instance.MESSAGE%TYPE ,error_id q$error_instance.error_id%TYPE ,contexts maxvarchar2_t ,created_on q$error_instance.created_on%TYPE ,created_by q$error_instance.created_by%TYPE ,changed_on q$error_instance.changed_on%TYPE ,changed_by q$error_instance.changed_by%TYPE /* Added in 1.4.0 */ ,error_category_name q$error.error_category_name%TYPE ); TYPE q$log_tc IS TABLE OF q$log%ROWTYPE INDEX BY PLS_INTEGER; TYPE q$error_context_tc IS TABLE OF q$error_context%ROWTYPE INDEX BY PLS_INTEGER; -- Error/warning management c_error CONSTANT CHAR (5) := 'ERROR'; c_info_msg CONSTANT CHAR (4) := 'INFO'; c_warning CONSTANT CHAR (7) := 'WARNING'; c_status_delimiter CONSTANT CHAR (1) := ':'; /* Special NYI exception */ e_not_yet_implemented EXCEPTION; c_not_yet_implemented PLS_INTEGER := -20999; PRAGMA EXCEPTION_INIT (e_not_yet_implemented, -20999); -- public global routines -- ------------------------------------- -- Tracing and DBMS_OUTPUT encapsulation -- ------------------------------------- -- switches output to the console -- NOTE: default is to Table PROCEDURE toscreen; -- switches output to the q$log. table -- NOTE: this is the default destination PROCEDURE totable; PROCEDURE totable (purge_in IN BOOLEAN); -- Send output to file. tofile opens the file; trace off closes the file. PROCEDURE tofile (dir_in IN VARCHAR2 ,file_in IN VARCHAR2 ,overwrite_in IN BOOLEAN DEFAULT TRUE); -- simple DBMS_OUTPUT handling of 255 char limit -- looks for a newline, or chops to fit given length PROCEDURE pl (string_in IN VARCHAR2, length_in IN PLS_INTEGER := 80); -- converts the given boolean value to string, -- then writes it out as above PROCEDURE pl (val IN BOOLEAN); -- as above but concatenates str with the string value of bool -- e.g., 'cursor%found TRUE' PROCEDURE pl (str IN VARCHAR2, bool IN BOOLEAN, len IN PLS_INTEGER := 80); PROCEDURE pl (clob_in IN CLOB, length_in IN PLS_INTEGER := 80); PROCEDURE set_error_trace (onoff_in IN BOOLEAN); -- When error trace is turned on, all errors are also written -- to the log table automatically. PROCEDURE error_trace_on; PROCEDURE error_trace_off; FUNCTION error_trace_enabled RETURN BOOLEAN; /* 1.4.0 control the adding or not of the remaining_context to the text message by default it's on*/ PROCEDURE add_remaining_context_msg_on; PROCEDURE add_remaining_context_msg_off; PROCEDURE set_add_remaining_context_msg (onoff_in IN BOOLEAN); FUNCTION add_remaining_context_enabled RETURN BOOLEAN; -- Controls the trace mechanism via a private global variable -- and sets the trace context filter (defaults to all traces) -- NOTE: tracing defaults to FALSE on package initialization -- only one context can be in force at a time -- and is case-insensitive PROCEDURE set_trace (onoff_in IN BOOLEAN ,context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE ,delimiter_in IN VARCHAR2 DEFAULT ','); -- Clears out the trace contents. Currently just deletes from q$log. PROCEDURE clear_trace; PROCEDURE trace_on (context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE ,delimiter_in IN VARCHAR2 DEFAULT ','); PROCEDURE trace_off (context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE); -- Has tracing been turned on globally? FUNCTION trace_enabled RETURN BOOLEAN; -- Has tracing been turned on globally or for a specific context? -- Context values are compared using UPPER case. FUNCTION trace_enabled (context_in IN q$log.context%TYPE) RETURN BOOLEAN; -- Writes output to the screen or q$log. table -- (see toscreen and totable above) -- If you pass TRUE for force parameter, this program will write to the log -- even if overall tracing is disabled. -- Level results in indentation to help you understand the log output. PROCEDURE trace (context_in IN q$log.context%TYPE ,text_in IN q$log.text%TYPE ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL); PROCEDURE trace (context_in IN q$log.context%TYPE ,number_in IN NUMBER ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL); PROCEDURE trace (context_in IN q$log.context%TYPE ,date_in IN DATE ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL); PROCEDURE trace (context_in IN q$log.context%TYPE ,boolean_in IN BOOLEAN ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL); PROCEDURE trace (context_in IN q$log.context%TYPE ,clob_in IN CLOB ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL); -- writes to the console the trace rows -- given (optionally filtered in any combination): -- trace id, context, text -- may be limited by limit_in -- call stack can also be displayed PROCEDURE show_trace (from_id_in IN q$log.id%TYPE := NULL ,context_like_in IN VARCHAR2 := '%' ,text_like_in IN VARCHAR2 := '%' ,limit_in IN PLS_INTEGER := NULL ,include_call_stack_in IN BOOLEAN := TRUE); ------------------------------------------- -- Error handling and raising functionality ------------------------------------------- -- An error needs to be raised or logged. -- Get a message handle for this particular error instance. -- The \"error message\" id keeps track of both the error -- and message information. This program also serves as -- \"record and go\" and \"go\". In other words, we do not -- raise an error, but it is noted. -- PROCEDURE register_error ( error_code_in IN q$error.code%TYPE ,error_instance_id_out IN OUT q$error_instance.id%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); -- Pass in error name instead of code. PROCEDURE register_error ( error_name_in IN q$error.name%TYPE ,error_instance_id_out IN OUT q$error_instance.id%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); /* 1.3.6 Standardize arg name - this OL no longer needed PROCEDURE register_error ( error_code_in IN q$error.code%TYPE , error_instance_id_out IN OUT q$error_instance.id%TYPE , text_in IN q$error_instance.MESSAGE%TYPE:= NULL , name1_in IN VARCHAR2 DEFAULT NULL , value1_in IN VARCHAR2 DEFAULT NULL , name2_in IN VARCHAR2 DEFAULT NULL , value2_in IN VARCHAR2 DEFAULT NULL , name3_in IN VARCHAR2 DEFAULT NULL , value3_in IN VARCHAR2 DEFAULT NULL , name4_in IN VARCHAR2 DEFAULT NULL , value4_in IN VARCHAR2 DEFAULT NULL , name5_in IN VARCHAR2 DEFAULT NULL , value5_in IN VARCHAR2 DEFAULT NULL , grab_settings_in IN BOOLEAN DEFAULT TRUE ); */ -- Record error (using name or code), but ignore error instance. -- Essentially, this is \"record but do not raise error.\" PROCEDURE register_error ( error_code_in IN q$error.code%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE DEFAULT NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); PROCEDURE register_error ( error_name_in IN q$error.name%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); -- Set a single context value for an error_instance row. -- Should overload this for a variety of datatypes. PROCEDURE add_context ( error_instance_id_in IN q$error_instance.id%TYPE ,NAME_IN IN q$error_context.name%TYPE ,value_in IN q$error_context.VALUE%TYPE ,validate_in IN BOOLEAN DEFAULT TRUE); /* 1.3.6 Do not maintain an overloading only with an arg name change. PROCEDURE add_context (err_instance_id_in IN q$error_instance.id%TYPE , NAME_IN IN q$error_context.name%TYPE , value_in IN q$error_context.VALUE%TYPE , validate_in IN BOOLEAN DEFAULT TRUE );*/ -- Global contexts; not tied to any specific instance. PROCEDURE add_gcontext (NAME_IN IN q$error_context.name%TYPE ,value_in IN q$error_context.VALUE%TYPE); -- Remove all global contexts. -- This is called by mark_q$error_handled as well. PROCEDURE clear_gcontexts; /* Raise specified error, providing minimal message information in a single procedure call. No context information is passed; if message text is not provided then the default with the error is used. */ PROCEDURE raise_error ( error_code_in IN q$error.code%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); PROCEDURE raise_error ( error_name_in IN q$error.name%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); /* 1.3.6 Standardize arg name */ PROCEDURE raise_error_instance ( error_instance_id_in IN q$error_instance.id%TYPE); /* Raise an unanticipated error. */ PROCEDURE raise_unanticipated ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); /* V1.2.11 Allow user to specify that an Oracle error should be registered (but not re-raised) or raised (registered and re-raised). */ PROCEDURE register_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); /* 1.2.15 Return error instance */ /* 1.3.6 Standardize arg name */ PROCEDURE register_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE ,error_instance_id_out OUT q$error_instance.id%TYPE); PROCEDURE raise_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE); /* Get error info for latest error. */ PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,error_info_out OUT error_info_rt ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline); PROCEDURE get_error_info (error_info_out OUT error_info_rt); /* 1.4.0 */ FUNCTION last_error_info RETURN error_info_rt; /* 1.4.0 */ FUNCTION q$error_raised RETURN BOOLEAN; --Simplest format, no composite structures PROCEDURE get_error_info ( code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE); PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE); /* 1.3.6 Standardize arg name */ PROCEDURE get_error_info ( error_message_in IN VARCHAR2 ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE); /* 1.3 Add id and message as OUT arguments */ PROCEDURE get_error_info ( code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline); PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline); /* 1.3.6 Standardize arg name */ PROCEDURE get_error_info ( error_message_in IN VARCHAR2 ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline); /* 1.4.0 */ FUNCTION last_error_id RETURN q$error.id%TYPE; /* 1.4.0 */ FUNCTION last_instance_id RETURN q$error_instance.id%TYPE; /* 1.4.0 */ FUNCTION last_error_category_name RETURN q$error.error_category_name%TYPE; FUNCTION last_code RETURN q$error.code%TYPE; FUNCTION last_name RETURN q$error.name%TYPE; FUNCTION last_text RETURN q$error_instance.MESSAGE%TYPE; FUNCTION last_system_error_code RETURN q$error_instance.system_error_code%TYPE; FUNCTION last_system_error_message RETURN q$error_instance.system_error_message%TYPE; PROCEDURE show_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE); PROCEDURE show_errors_after ( date_in IN DATE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE); PROCEDURE show_errors_with_message ( text_in IN VARCHAR2 ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE); PROCEDURE show_errors_with_code ( error_code_in IN PLS_INTEGER ,is_system_error_code_in IN BOOLEAN DEFAULT FALSE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE); PROCEDURE show_errors_for ( where_clause_in IN VARCHAR2 ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE); FUNCTION error_clipboard (clear_clipboard_in IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2; FUNCTION error_clipboard_is_full RETURN BOOLEAN; PROCEDURE clear_error_clipboard; PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,error_code_in IN q$error.code%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL); PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,error_name_in IN q$error.name%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL); PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL); PROCEDURE simple_assert ( condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,raise_with_code_in IN PLS_INTEGER DEFAULT -20000); PROCEDURE mark_q$error_handled; -- Added/exposed in 1.1.1 FUNCTION error_instance_from_string (string_in IN VARCHAR2) RETURN PLS_INTEGER; FUNCTION error_instance_from_sqlerrm RETURN PLS_INTEGER; -- 1.2 Get message text only FUNCTION error_message RETURN VARCHAR2; PROCEDURE start_execution (program_name_in IN VARCHAR2 := NULL ,information_in IN VARCHAR2 := NULL); /* Test builder validation: * return a status string composed of type:message * if no prefix, then it is an error */ FUNCTION is_warning (status_in IN VARCHAR2) RETURN BOOLEAN; FUNCTION is_error (status_in IN VARCHAR2) RETURN BOOLEAN; FUNCTION is_info_msg (status_in IN VARCHAR2) RETURN BOOLEAN; PROCEDURE parse_status_string (status_in IN VARCHAR2 ,status_type_out OUT VARCHAR2 ,message_out OUT VARCHAR2); PROCEDURE make_a_warning (status_inout IN OUT VARCHAR2); PROCEDURE make_an_error (status_inout IN OUT VARCHAR2); PROCEDURE make_an_info_msg (status_inout IN OUT VARCHAR2); FUNCTION make_a_warning (status_in IN VARCHAR2) RETURN VARCHAR2; FUNCTION make_an_error (status_in IN VARCHAR2) RETURN VARCHAR2; FUNCTION make_an_info_msg (status_in IN VARCHAR2) RETURN VARCHAR2; FUNCTION full_log_string (context_in IN VARCHAR2 ,text_in IN VARCHAR2 ,level_in IN PLS_INTEGER) RETURN VARCHAR2; FUNCTION log_entries_after (timestamp_in IN DATE) RETURN SYS_REFCURSOR; PROCEDURE not_yet_implemented (program_name_in IN VARCHAR2); /* 1.2.12 Provide switch to force display of \"real\" error in the error message, rather than simply showing the error handle. You should ask for \"raise with message\" when you are not going to use the get_error_info program to extract error information. */ PROCEDURE raise_with_message; /* Only show the error instance handle in the error message. This is the default and assumes that you will be calling get_error_info to extract all the info. */ PROCEDURE raise_with_handle; /* 1.2.17 Specify that you do NOT want the error state cached. This means that you will not have to explicitly mark the error as handled, but this also means that if successive layers in your call stack handle and re-raise your exception with QEM, you will see multiple entries in your error instance table. */ PROCEDURE discard_error_state; /* The default */ PROCEDURE keep_error_state; FUNCTION error_state_discarded RETURN BOOLEAN; /* Remove data from q$error_instance and q$error_context for all errors that were reported earlier than the specified date. Pass NULL (default) to remove all rows. NOTE: This is a pragma autonomous_transaction; procedure. */ PROCEDURE clean_up_errors (older_than_in IN DATE DEFAULT NULL);END q$error_manager;/ q$error_manager包体 q$error_manager.pkb 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311create or replace PACKAGE BODY q$error_manager/*| This program is a part of the Quest Error Manager for Oracle.| This product is freeware and is not supported by Quest.|| www.quest.com?2008 Quest Software, Inc.ALL RIGHTS RESERVED.Redistribution and use of the Quest Error Manager for Oracle software in source and binary forms,with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain (i) the following copyright notice: \"?008 Quest Software, Inc.All rights reserved,\" (ii) this list of conditions, and (iii) the disclaimer below.2. Redistributions in binary form must reproduce (i) the following copyright notice:\"?008 Quest Software, Inc. All rights reserved,\" (ii) this list of conditions, and (iii)the disclaimer below, in the documentation and/or other materials provided with the distribution.3. All advertising materials mentioning features or use of the Quest Error Managerfor Oracle software must display the following acknowledgement:This product includes software developed by Quest Software, Inc. and its contributors.4. Neither the name of Quest Software, Inc. nor the name its affiliates, subsidiariesor contributors may be used to endorse or promote products derived from the Quest ErrorManager for Oracle software without specific prior written permission from Quest Software, Inc.Disclaimer:THIS SOFTWARE IS PROVIDED BY QUEST SOFTWARE, INC. AND ITS CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL QUEST SOFTWARE OR ITSCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTEGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THEQUEST ERROR MANAGER SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/IS c_default_error_code CONSTANT PLS_INTEGER := -20000; g_clipboard maxvarchar2_t; g_clipboard_is_full BOOLEAN DEFAULT FALSE; -- c_trunc_warning VARCHAR2 (100) := '*** WARNING: Information truncated! '; c_warning_len PLS_INTEGER := LENGTH (c_warning); -- -- trace variables g_trace_enabled BOOLEAN := FALSE; g_error_trace_enabled BOOLEAN := TRUE; g_context_like q$log.context%TYPE := NULL; g_include_timestamp BOOLEAN := FALSE; g_multiple_contexts BOOLEAN := FALSE; g_context_list DBMS_SQL.varchar2s; /* Flag to indicate that the error has already been registered and raised within QD, so just propagate it on up the call stack. */ g_error_instance_raised q$error_instance.id%TYPE DEFAULT NULL; -- system settings g_grabbed_settings error_info_rt; /* 1.4.0 flag to register is we want to append the remaining context to text */ g_add_remaining_context_msg BOOLEAN := TRUE; -- Global contexts TYPE gcontext_rt IS RECORD ( name q$error_context.name%TYPE ,VALUE q$error_context.VALUE%TYPE ); TYPE gcontext_tt IS TABLE OF gcontext_rt INDEX BY BINARY_INTEGER; g_gcontexts gcontext_tt; -- current program TYPE runtime_rt IS RECORD ( package_name all_objects.object_name%TYPE ,program_name all_objects.object_name%TYPE ); /* 1.2.14 No longer used g_runtime runtime_rt; -- \"current\" running program */ -- collection of record TYPE runtime_nat IS TABLE OF runtime_rt; -- runtime stack implemented as a dense nested table. -- starts with element 1 and grows upward. g_runtime_nat runtime_nat := runtime_nat (); -- trace target c_screen CONSTANT INTEGER := 0; c_table CONSTANT INTEGER := 1; --1.4.1 c_file CONSTANT INTEGER := 2; g_target INTEGER := c_table; TYPE trace_file_info_rt IS RECORD ( dir maxvarchar2_t ,filename maxvarchar2_t ,lines_written PLS_INTEGER ); g_file_info trace_file_info_rt; -- Every hundred lines written, close and reopen. --1.4.1 c_close_threshold CONSTANT PLS_INTEGER := 100; /* V1.2.11 show/hide setting */ TYPE show_oracle_rt IS RECORD ( show BOOLEAN DEFAULT FALSE ,revert BOOLEAN DEFAULT FALSE ,revert_to BOOLEAN ); g_show_oracle show_oracle_rt; /* Option on how error is raised. */ c_with_handle CONSTANT PLS_INTEGER := -98; c_with_message CONSTANT PLS_INTEGER := +75; g_raise_with PLS_INTEGER := c_with_handle; g_discard_error_state BOOLEAN := FALSE; /* 1.2.12 Provide switch to force display of \"real\" error in the error message, rather than simply showing the error handle. This is needed when user is running tests via the API and is not retrieving errors via get_error_info. */ PROCEDURE raise_with_message IS BEGIN g_raise_with := c_with_message; END raise_with_message; PROCEDURE raise_with_handle IS BEGIN g_raise_with := c_with_handle; END raise_with_handle; FUNCTION raising_with_message RETURN BOOLEAN IS BEGIN RETURN g_raise_with = c_with_message; END raising_with_message; FUNCTION raising_with_handle RETURN BOOLEAN IS BEGIN RETURN g_raise_with = c_with_handle; END raising_with_handle; /* END 1.2.12 changes */ -- Private routines/handy utilities FUNCTION error_backtrace (prefix_with_error_in IN BOOLEAN DEFAULT FALSE) RETURN VARCHAR2 IS l_errortrace maxvarchar2_t := DBMS_UTILITY.format_error_stack; l_backtrace maxvarchar2_t; l_return maxvarchar2_t; BEGIN EXECUTE IMMEDIATE 'BEGIN :val := DBMS_UTILITY.format_error_backtrace; END;' USING OUT l_backtrace; l_return := CASE WHEN prefix_with_error_in THEN l_errortrace || CHR (10) || CHR (10) || l_backtrace ELSE l_backtrace END; RETURN l_return; EXCEPTION WHEN OTHERS THEN RETURN l_errortrace; END error_backtrace; FUNCTION oracle_app_error (code_in IN PLS_INTEGER) RETURN BOOLEAN IS BEGIN RETURN code_in BETWEEN -20999 AND -20000; END oracle_app_error; PROCEDURE validate_oracle_error (code_in IN PLS_INTEGER ,message_out OUT VARCHAR2 ,is_valid_out OUT BOOLEAN) IS l_message maxvarchar2_t; BEGIN /* Take care of special case... */ IF code_in IN (100, -1403) THEN is_valid_out := TRUE; ELSE l_message := SQLERRM (code_in); -- If SQLERRM does not find an entry, it return a string like one of these: -- If the number is negative... -- ORA-NNNNN: Message NNNN not found; product=RDBMS; facility=ORA -- If the number is positive... -- -13000: non-ORACLE exception -- If the positive number is too big, we get numeric overflow. IF l_message LIKE 'ORA-_____: Message%not found;%' OR l_message LIKE '%: non-ORACLE exception%' THEN message_out := NULL; is_valid_out := FALSE; ELSE message_out := l_message; is_valid_out := TRUE; END IF; END IF; EXCEPTION WHEN OTHERS THEN --numeric overflow IF SQLCODE = -1426 THEN message_out := NULL; is_valid_out := FALSE; ELSE RAISE; END IF; END validate_oracle_error; FUNCTION is_valid_oracle_error (code_in IN PLS_INTEGER) RETURN BOOLEAN IS l_message maxvarchar2_t; retval BOOLEAN; BEGIN validate_oracle_error (code_in, l_message, retval); RETURN retval; END is_valid_oracle_error; FUNCTION sqlcode_name (error_code_in IN PLS_INTEGER) RETURN VARCHAR2 IS l_return maxvarchar2_t; /* 1.2.16 all_objects.object_name%TYPE; */ BEGIN l_return := CASE error_code_in WHEN -6511 THEN 'CURSOR_ALREADY_OPEN' WHEN -0001 THEN 'DUP_VAL_ON_INDEX' WHEN -0051 THEN 'TIMEOUT_ON_RESOURCE' WHEN -1001 THEN 'INVALID_CURSOR' WHEN -1012 THEN 'NOT_LOGGED_ON' WHEN -1017 THEN 'LOGIN_DENIED' WHEN 100 THEN 'NO_DATA_FOUND' WHEN -1476 THEN 'ZERO_DIVIDE' WHEN -1722 THEN 'INVALID_NUMBER' WHEN -1422 THEN 'TOO_MANY_ROWS' WHEN -6500 THEN 'STORAGE_ERROR' WHEN -6501 THEN 'PROGRAM_ERROR' WHEN -6502 THEN 'VALUE_ERROR' WHEN -6530 THEN 'ACCESS_INTO_NULL' WHEN -6531 THEN 'COLLECTION_IS_NULL ' WHEN -6532 THEN 'SUBSCRIPT_OUTSIDE_LIMIT' WHEN -6533 THEN 'SUBSCRIPT_BEYOND_COUNT ' WHEN -650 THEN 'ROWTYPE_MISMATCH' WHEN -1410 THEN 'SYS_INVALID_ROWID' WHEN -30625 THEN 'SELF_IS_NULL' WHEN -6592 THEN 'CASE_NOT_FOUND' WHEN -1725 THEN 'USERENV_COMMITSCN_ERROR' WHEN -6548 THEN 'NO_DATA_NEEDED' ELSE UPPER ( SUBSTR (SQLERRM (error_code_in) ,INSTR (SQLERRM (error_code_in), ':') + 2)) END; RETURN l_return; END sqlcode_name; FUNCTION sqlcode_text (error_code_in IN PLS_INTEGER) RETURN VARCHAR2 IS BEGIN IF error_code_in IN (100, -1403) THEN RETURN 'No data found'; ELSE RETURN SQLERRM (error_code_in); END IF; END sqlcode_text; -- MOve from q$error_qp to remove dependency on package. FUNCTION q$error_qp_onerow (id_in IN q$error.id%TYPE) RETURN q$error%ROWTYPE IS CURSOR onerow_cur IS SELECT * FROM q$error WHERE id = id_in; onerow_rec q$error%ROWTYPE; BEGIN OPEN onerow_cur; FETCH onerow_cur INTO onerow_rec; CLOSE onerow_cur; RETURN onerow_rec; END q$error_qp_onerow; FUNCTION q$error_instance_qp_onerow (id_in IN q$error_instance.id%TYPE) RETURN q$error_instance%ROWTYPE IS CURSOR onerow_cur IS SELECT * FROM q$error_instance WHERE id = id_in; onerow_rec q$error_instance%ROWTYPE; BEGIN OPEN onerow_cur; FETCH onerow_cur INTO onerow_rec; CLOSE onerow_cur; RETURN onerow_rec; END q$error_instance_qp_onerow; PROCEDURE mark_q$error_raised (id_in IN q$error_instance.id%TYPE) IS BEGIN /* 1.2.17 Do not maintain state if user has requested this non-default behavior. That is, immediately mark it as handled. */ IF error_state_discarded () THEN mark_q$error_handled (); ELSE g_error_instance_raised := id_in; END IF; END mark_q$error_raised; -- Reset when get_error_info is called. PROCEDURE mark_q$error_handled IS BEGIN g_error_instance_raised := NULL; clear_gcontexts; END mark_q$error_handled; FUNCTION q$error_instance_id_raised RETURN q$error_instance.id%TYPE IS BEGIN RETURN g_error_instance_raised; END q$error_instance_id_raised; FUNCTION q$error_raised RETURN BOOLEAN IS BEGIN RETURN /* 1.4.0 Do not directly reference variables g_error_instance_raised */ q$error_instance_id_raised () IS NOT NULL; END q$error_raised; -- returns the program name at the \"top\" of the runtime stack FUNCTION top_program_name RETURN all_objects.object_name%TYPE IS v_program_name all_objects.object_name%TYPE; BEGIN IF (g_runtime_nat.COUNT > 0) THEN v_program_name := g_runtime_nat (g_runtime_nat.LAST).program_name; END IF; RETURN (v_program_name); END top_program_name; -- PBA 20040518 FUNCTION top_package_name RETURN all_objects.object_name%TYPE IS v_package_name all_objects.object_name%TYPE; BEGIN IF (g_runtime_nat.COUNT > 0) THEN v_package_name := g_runtime_nat (g_runtime_nat.LAST).program_name; END IF; RETURN (v_package_name); END top_package_name; -- PBA 20040518 FUNCTION top_packprog_name RETURN VARCHAR2 IS BEGIN RETURN top_package_name || '.' || top_program_name; END top_packprog_name; -- returns the current runtime context at the \"top\" of the runtime stack FUNCTION top_runtime RETURN runtime_rt IS v_runtime runtime_rt; BEGIN IF (g_runtime_nat.COUNT > 0) THEN v_runtime := g_runtime_nat (g_runtime_nat.LAST); END IF; RETURN (v_runtime); END top_runtime; -- convert Boolean value to String FUNCTION strval (val IN BOOLEAN) RETURN VARCHAR2 IS BEGIN IF val THEN RETURN ('TRUE'); ELSIF NOT val THEN RETURN ('FALSE'); ELSE RETURN ('NULL'); END IF; END strval; -- public routines (exposed in the package header) -- ------------------------------------- -- Tracing and DBMS_OUTPUT encapsulation -- ------------------------------------- -- Tracing and DBMS_OUTPUT encapsulation -- switches output to the console -- NOTE: default is to Table PROCEDURE toscreen IS BEGIN g_target := c_screen; END toscreen; -- switches output to the q$log table -- NOTE: this is the default destination PROCEDURE totable IS BEGIN g_target := c_table; END totable; PROCEDURE totable (purge_in IN BOOLEAN) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN totable (); IF purge_in THEN DELETE FROM q$log; COMMIT; END IF; END totable; PROCEDURE tofile (dir_in IN VARCHAR2 ,file_in IN VARCHAR2 ,overwrite_in IN BOOLEAN DEFAULT TRUE) IS BEGIN g_file_info.dir := dir_in; g_file_info.filename := file_in; g_file_info.lines_written := 0; /* Write to file now occurs in front end when tracing is terminated g_file_info.file_handle := utx_file.fopen (dir_in , file_in , CASE WHEN overwrite_in THEN 'W' ELSE 'A' END , max_linesize => 32767 );*/ /* We still write to the table, and then dump to file later. */ g_target := c_table; END tofile; -- simple DBMS_OUTPUT handling of 255 char limit -- looks for a newline, or chops to fit given length PROCEDURE pl (string_in IN VARCHAR2, length_in IN PLS_INTEGER := 80) IS v_len PLS_INTEGER := LEAST (length_in, 255); v_len2 PLS_INTEGER; v_chr10 PLS_INTEGER; v_str VARCHAR2 (2000); BEGIN IF LENGTH (string_in) > v_len THEN v_chr10 := INSTR (string_in, CHR (10)); IF v_chr10 > 0 AND v_len >= v_chr10 THEN v_len := v_chr10 - 1; v_len2 := v_chr10 + 1; ELSE v_len2 := v_len + 1; END IF; v_str := SUBSTR (string_in, 1, v_len); DBMS_OUTPUT.put_line (v_str); pl (string_in => SUBSTR (string_in, v_len2) ,length_in => length_in); ELSE DBMS_OUTPUT.put_line (string_in); END IF; EXCEPTION /* 1.3.1 Volker Bartuseck fix */ WHEN OTHERS THEN DBMS_OUTPUT.enable (1000000); IF v_str IS NULL THEN DBMS_OUTPUT.put_line (string_in); ELSE DBMS_OUTPUT.put_line (v_str); END IF; END pl; -- converts the given boolean value to string, -- then writes it out as above PROCEDURE pl (val IN BOOLEAN) IS BEGIN pl (strval (val)); END pl; -- as above but concatenates str with the string value of bool -- e.g., 'cursor%found TRUE' PROCEDURE pl (str IN VARCHAR2, bool IN BOOLEAN, len IN PLS_INTEGER := 80) IS BEGIN pl (string_in => str || ' ' || strval (bool), length_in => len); END pl; PROCEDURE pl (clob_in IN CLOB, length_in IN PLS_INTEGER := 80) IS buffer VARCHAR2 (255); amount BINARY_INTEGER := GREATEST (length_in, 255); position INTEGER := 1; BEGIN LOOP DBMS_LOB.read (clob_in ,amount ,position ,buffer); /* Display the buffer contents using the string overloading */ pl (buffer); position := position + amount; END LOOP; EXCEPTION WHEN NO_DATA_FOUND OR VALUE_ERROR THEN pl ('** End of CLOB data **'); END pl; PROCEDURE set_error_trace (onoff_in IN BOOLEAN) IS BEGIN IF onoff_in THEN error_trace_on; ELSE error_trace_off; END IF; END set_error_trace; PROCEDURE error_trace_on IS BEGIN g_error_trace_enabled := TRUE; END error_trace_on; PROCEDURE error_trace_off IS BEGIN g_error_trace_enabled := FALSE; END error_trace_off; FUNCTION error_trace_enabled RETURN BOOLEAN IS BEGIN RETURN g_error_trace_enabled; END error_trace_enabled; /*1.4.0 */ PROCEDURE add_remaining_context_msg_on IS BEGIN g_add_remaining_context_msg := TRUE; END add_remaining_context_msg_on; PROCEDURE add_remaining_context_msg_off IS BEGIN g_add_remaining_context_msg := FALSE; END add_remaining_context_msg_off; PROCEDURE set_add_remaining_context_msg (onoff_in IN BOOLEAN) IS BEGIN IF onoff_in THEN add_remaining_context_msg_on; ELSE add_remaining_context_msg_off; END IF; END set_add_remaining_context_msg; FUNCTION add_remaining_context_enabled RETURN BOOLEAN IS BEGIN RETURN g_add_remaining_context_msg; END add_remaining_context_enabled; /* end 1.4.0 */ FUNCTION string_to_list (string_in IN VARCHAR2, delim_in IN VARCHAR2) RETURN DBMS_SQL.varchar2s IS c_end_of_list CONSTANT PLS_INTEGER := -99; l_item maxvarchar2_t; l_startloc PLS_INTEGER := 1; items_out DBMS_SQL.varchar2s; PROCEDURE add_item (item_in IN VARCHAR2) IS BEGIN IF item_in = delim_in THEN /* We don't put delimiters into the collection. */ NULL; ELSE items_out (items_out.COUNT + 1) := RTRIM (LTRIM (item_in)); END IF; END add_item; PROCEDURE get_next_item (string_in IN VARCHAR2 ,startloc_inout IN OUT PLS_INTEGER ,item_out OUT VARCHAR2) IS l_loc PLS_INTEGER; BEGIN l_loc := INSTR (string_in, delim_in, startloc_inout); IF l_loc = startloc_inout -- Previous item is NULL THEN item_out := NULL; ELSIF l_loc = 0 -- Rest of string is last item THEN item_out := SUBSTR (string_in, startloc_inout); ELSE item_out := SUBSTR (string_in ,startloc_inout ,l_loc - startloc_inout); END IF; IF l_loc = 0 THEN startloc_inout := c_end_of_list; ELSE startloc_inout := l_loc + 1; END IF; END get_next_item; BEGIN IF string_in IS NOT NULL AND delim_in IS NOT NULL THEN LOOP get_next_item (string_in, l_startloc, l_item); add_item (l_item); EXIT WHEN l_startloc = c_end_of_list; END LOOP; END IF; RETURN items_out; END string_to_list; -- Clears out the trace contents. Currently just deletes from q$log. PROCEDURE clear_trace IS BEGIN EXECUTE IMMEDIATE 'truncate table q$log'; EXCEPTION WHEN OTHERS THEN NULL; END clear_trace; PROCEDURE set_trace (onoff_in IN BOOLEAN ,context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE ,delimiter_in IN VARCHAR2 DEFAULT ',') IS BEGIN -- Special commands.... IF onoff_in AND UPPER (context_like_in) = 'QCTO#TOTABLE' THEN totable (purge_in => TRUE); g_trace_enabled := onoff_in; g_context_like := '%'; q$error_manager.trace ( 'set_trace to table via override with context \"%\"' ,g_trace_enabled); g_include_timestamp := include_timestamp_in; ELSE g_trace_enabled := onoff_in; g_context_like := UPPER (NVL (context_like_in, '%')); q$error_manager.trace ( 'set_trace with context \"' || g_context_like || '\"' ,g_trace_enabled); g_include_timestamp := include_timestamp_in; /* Write to file now occurs in front end when tracing is terminated IF NOT g_trace_enabled AND g_target = c_file THEN utx_file.fclose (g_file_info.file_handle); END IF;*/ -- BM 1239 When turning off, set back to table, which is the default. IF NOT g_trace_enabled THEN totable; END IF; END IF; g_multiple_contexts := INSTR (context_like_in, delimiter_in) > 0; IF g_multiple_contexts THEN g_context_list := string_to_list (context_like_in, delimiter_in); END IF; END set_trace; PROCEDURE trace_on (context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE ,delimiter_in IN VARCHAR2 DEFAULT ',') IS BEGIN set_trace (onoff_in => TRUE ,context_like_in => context_like_in ,include_timestamp_in => include_timestamp_in); END trace_on; PROCEDURE trace_off (context_like_in IN q$log.context%TYPE := NULL ,include_timestamp_in IN BOOLEAN DEFAULT FALSE) IS BEGIN set_trace (onoff_in => FALSE ,context_like_in => context_like_in ,include_timestamp_in => include_timestamp_in); END trace_off; FUNCTION trace_enabled RETURN BOOLEAN IS BEGIN RETURN g_trace_enabled; END trace_enabled; FUNCTION context_matches (context_in IN VARCHAR2) RETURN BOOLEAN IS BEGIN FOR indx IN 1 .. g_context_list.COUNT LOOP IF UPPER (context_in) LIKE UPPER (g_context_list (indx)) THEN RETURN TRUE; END IF; END LOOP; RETURN FALSE; END context_matches; FUNCTION trace_enabled (context_in IN q$log.context%TYPE) RETURN BOOLEAN IS l_return BOOLEAN DEFAULT FALSE; BEGIN IF g_trace_enabled THEN /* IF multiple contexts, then check for exact match. */ IF g_multiple_contexts THEN l_return := context_matches (context_in); /* Use wildcarded comparision */ ELSE l_return := (g_context_like IS NULL OR UPPER (context_in) LIKE '%' || g_context_like || '%'); END IF; END IF; RETURN l_return; END trace_enabled; FUNCTION full_log_string (context_in IN VARCHAR2 ,text_in IN VARCHAR2 ,level_in IN PLS_INTEGER) RETURN VARCHAR2 IS /* Cannot be longer than 4000 characters, since it is returned via a query. */ c_timestamp CONSTANT VARCHAR2 (100) := TO_CHAR (SYSDATE, 'HH24:MI:SS-YYYYMMDD') ; l_string maxvarchar2_t; BEGIN l_string := CASE WHEN g_include_timestamp THEN c_timestamp || ' ' ELSE NULL END || context_in; l_string := l_string || ' - ' || SUBSTR (text_in ,1 ,c_varchar2_max_length_db - LENGTH (l_string) - 5); /* Ignore level || CASE WHEN level_in > 0 THEN LPAD (' ', level_in) ELSE NULL END || context_in || ' - ' || text_in;*/ RETURN l_string; EXCEPTION WHEN VALUE_ERROR THEN RETURN SUBSTR (context_in || ' - ' || text_in ,1 ,c_varchar2_max_length); END full_log_string; -- writes output to the screen or q$log table -- (see toscreen and totable above) PROCEDURE trace (context_in IN q$log.context%TYPE ,text_in IN q$log.text%TYPE ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL) IS PRAGMA AUTONOMOUS_TRANSACTION; l_context q$log.context%TYPE; l_text q$log.text%TYPE; BEGIN IF trace_enabled (context_in) OR force_trace_in THEN -- Bypass encapsulation; problems with infinite loop. IF g_target = c_table THEN /* 1.2.18 Avoid context strings > 500 characters */ l_context := CASE WHEN g_include_timestamp THEN TO_CHAR (SYSDATE, 'HH24:MI:SS-YYYYMMDD') || ' ' ELSE NULL END; l_context := SUBSTR (l_context || context_in, 1, 500); l_text := SUBSTR (text_in, 1, c_varchar2_max_length_db); INSERT INTO q$log (id ,context ,text ,call_stack ,created_on ,created_by) VALUES ( q$log_seq.NEXTVAL ,l_context ,l_text ,SUBSTR (DBMS_UTILITY.format_call_stack ,1 ,c_varchar2_max_length_db) ,SYSDATE ,USER); ELSIF g_target = c_screen THEN pl ( string_in => full_log_string (context_in ,text_in ,level_in)); /* Write to file now occurs in front end when tracing is terminated ELSIF g_target = c_file THEN utx_file.put_line (g_file_info.file_handle , full_log_string (context_in, text_in, level_in) ); -- 1.2.3: close and reopen file g_file_info.lines_written := g_file_info.lines_written + 1; IF g_file_info.lines_written >= c_close_threshold THEN utx_file.fclose (g_file_info.file_handle); g_file_info.file_handle := utx_file.fopen (g_file_info.dir , g_file_info.filename , 'A' , max_linesize => 32767 ); g_file_info.lines_written := 0; END IF;*/ END IF; COMMIT; END IF; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END trace; PROCEDURE trace (context_in IN q$log.context%TYPE ,boolean_in IN BOOLEAN ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL) IS BEGIN IF boolean_in THEN trace (context_in ,'TRUE' ,force_trace_in => force_trace_in ,level_in => level_in); ELSIF NOT boolean_in THEN trace (context_in ,'FALSE' ,force_trace_in => force_trace_in ,level_in => level_in); ELSE trace (context_in ,'NULL BOOLEAN' ,force_trace_in => force_trace_in ,level_in => level_in); END IF; END trace; PROCEDURE trace (context_in IN q$log.context%TYPE ,number_in IN NUMBER ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL) IS BEGIN trace (context_in ,TO_CHAR (number_in) ,force_trace_in => force_trace_in ,level_in => level_in); END trace; PROCEDURE trace (context_in IN q$log.context%TYPE ,date_in IN DATE ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL) IS BEGIN trace (context_in ,TO_CHAR (date_in, 'YYYY MM DD HH24:MI:SS') ,force_trace_in => force_trace_in ,level_in => level_in); END trace; PROCEDURE trace (context_in IN q$log.context%TYPE ,clob_in IN CLOB ,force_trace_in IN BOOLEAN DEFAULT FALSE ,level_in IN PLS_INTEGER DEFAULT NULL) IS -- Maximum size set by column restriction buffer VARCHAR2 (4000); amount BINARY_INTEGER := 4000; position INTEGER := 1; l_notnull BOOLEAN := FALSE; BEGIN LOOP DBMS_LOB.read (clob_in ,amount ,position ,buffer); l_notnull := TRUE; trace ( CASE WHEN position = 1 THEN context_in ELSE NULL END ,buffer ,force_trace_in => force_trace_in ,level_in => CASE WHEN position = 1 THEN level_in ELSE 0 END); position := position + amount; END LOOP; EXCEPTION WHEN NO_DATA_FOUND OR VALUE_ERROR THEN -- Make sure that at least the trace message is written. IF NOT l_notnull THEN trace (context_in ,'' ,force_trace_in ,level_in); END IF; END trace; FUNCTION build_where_clause_show_trace ( from_id_in IN q$log.id%TYPE := NULL ,context_like_in IN VARCHAR2 := '%' ,text_like_in IN VARCHAR2 := '%') RETURN VARCHAR2 IS l_where VARCHAR2 (1000); BEGIN IF from_id_in IS NULL THEN l_where := '1 = 1'; ELSE l_where := 'id >= ' || from_id_in; END IF; l_where := l_where || ' AND context LIKE ''%' || context_like_in || '%'' AND text LIKE ''%' || text_like_in || '%'''; RETURN l_where; END build_where_clause_show_trace; -- writes to the console the trace rows -- given (optionally filtered in any combination): -- trace id, context, text -- may be limited by limit_in -- call stack can also be displayed PROCEDURE show_trace (from_id_in IN q$log.id%TYPE := NULL ,context_like_in IN VARCHAR2 := '%' ,text_like_in IN VARCHAR2 := '%' ,limit_in IN PLS_INTEGER := NULL ,include_call_stack_in IN BOOLEAN := TRUE) IS l_where VARCHAR2 (1000); l_entries q$log_tc; l_index PLS_INTEGER; limit_not_reached BOOLEAN := TRUE; FUNCTION all_logs_by (where_clause_in IN VARCHAR2) RETURN q$log_tc IS TYPE weak_rc IS REF CURSOR; allrows_cur weak_rc; --1.4.1 l_rows PLS_INTEGER; retval q$log_tc; BEGIN OPEN allrows_cur FOR 'SELECT ID, CONTEXT, TEXT, CALL_STACK, CREATED_ON, CREATED_BY, CHANGED_ON, CHANGED_BY FROM q$log WHERE ' || where_clause_in; LOOP FETCH allrows_cur INTO retval (retval.COUNT + 1); EXIT WHEN allrows_cur%NOTFOUND; END LOOP; RETURN retval; END all_logs_by; BEGIN l_where := build_where_clause_show_trace ( from_id_in => from_id_in ,context_like_in => context_like_in ,text_like_in => text_like_in); l_entries := all_logs_by (l_where); l_index := l_entries.FIRST; WHILE (l_index IS NOT NULL AND limit_not_reached) LOOP pl ( 'ID-Context-Text = ' || l_entries (l_index).id || '-' || l_entries (l_index).context || '-' || l_entries (l_index).text); IF include_call_stack_in THEN pl (l_entries (l_index).call_stack); END IF; pl ('------------------------------------------------'); l_index := l_entries.NEXT (l_index); limit_not_reached := (l_index <= limit_in OR limit_in IS NULL); END LOOP; END show_trace; ------------------------------------------- -- Error handling and raising functionality ------------------------------------------- PROCEDURE internal_raise (code_in IN PLS_INTEGER, message_in IN VARCHAR2) IS BEGIN -- 4/2004: Keep this trace call in? Offer it as an option? -- 5/2005: Made it optional, based on the trace setting. IF error_trace_enabled THEN trace ( 'ERROR RAISED' , 'Oracle error = ' || code_in || ' - Error instance handle = ' || message_in ,FALSE); END IF; raise_application_error (code_in, message_in); END internal_raise; -- Global contexts; not tied to any specific instance. PROCEDURE add_gcontext (NAME_IN IN q$error_context.name%TYPE ,value_in IN q$error_context.VALUE%TYPE) IS l_name q$error_context.name%TYPE := UPPER (NAME_IN); l_row PLS_INTEGER := g_gcontexts.FIRST; l_matched BOOLEAN := FALSE; BEGIN WHILE (l_row IS NOT NULL AND NOT l_matched) LOOP l_matched := l_name = g_gcontexts (l_row).name; IF NOT l_matched THEN l_row := g_gcontexts.NEXT (l_row); END IF; END LOOP; IF NOT l_matched THEN l_row := g_gcontexts.COUNT + 1; END IF; g_gcontexts (l_row).name := l_name; g_gcontexts (l_row).VALUE := value_in; END add_gcontext; -- Remove all global contexts. -- This is called by mark_q$error_handled as well. PROCEDURE clear_gcontexts IS BEGIN g_gcontexts.delete; END clear_gcontexts; -- Set a single context value PROCEDURE add_context ( error_instance_id_in IN q$error_instance.id%TYPE ,NAME_IN IN q$error_context.name%TYPE ,value_in IN q$error_context.VALUE%TYPE ,validate_in IN BOOLEAN DEFAULT TRUE) IS PRAGMA AUTONOMOUS_TRANSACTION; l_error_instance q$error_instance%ROWTYPE; BEGIN IF NAME_IN IS NOT NULL THEN IF validate_in THEN l_error_instance := q$error_instance_qp_onerow (error_instance_id_in); ELSE l_error_instance.id := error_instance_id_in; END IF; INSERT INTO q$error_context (id ,error_instance_id ,name ,VALUE) VALUES (q$error_context_seq.NEXTVAL ,l_error_instance.id ,NAME_IN ,value_in); COMMIT; END IF; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN ROLLBACK; WHEN OTHERS THEN ROLLBACK; internal_raise ( c_default_error_code , 'Unable to insert QD context for \"' || NAME_IN || '\" with value \"' || value_in || '\" for instance ID ' || l_error_instance.id || ' with Oracle error = ' || SQLCODE); END add_context; PROCEDURE add_context (err_instance_id_in IN q$error_instance.id%TYPE ,NAME_IN IN q$error_context.name%TYPE ,value_in IN q$error_context.VALUE%TYPE ,validate_in IN BOOLEAN DEFAULT TRUE) IS BEGIN add_context (error_instance_id_in => err_instance_id_in ,NAME_IN => NAME_IN ,value_in => value_in ,validate_in => validate_in); END add_context; -- An error needs to be raised or logged. -- Get a message handle for this particular error instance. -- The \"error message\" id keeps track of both the error -- and message information. This program also serves as -- \"record and go\" and \"go\". In other words, we do not -- raise an error, but it is noted. -- PROCEDURE register_error_instance ( error_in IN q$error%ROWTYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,error_instance_id_out IN OUT q$error_instance.id%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL) IS l_error_instance_id q$error_instance.id%TYPE; PROCEDURE add_context_values IS l_row PLS_INTEGER; PROCEDURE add_one (NAME_IN IN VARCHAR2, value_in IN VARCHAR2) IS BEGIN IF NAME_IN IS NOT NULL THEN INSERT INTO q$error_context (id ,error_instance_id ,name ,VALUE) VALUES (q$error_context_seq.NEXTVAL ,l_error_instance_id ,SUBSTR (NAME_IN, 1, c_varchar2_max_length_db) ,SUBSTR (value_in, 1, c_varchar2_max_length_db)); END IF; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN -- Entering another value for the same context. Mention it in the trace. trace ( 'ADD CONTEXT' , 'Duplicate entry for \"' || NAME_IN || '\" with value \"' || value_in || '\" for instance ID ' || l_error_instance_id ,TRUE); END add_one; BEGIN add_one (name1_in, value1_in); add_one (name2_in, value2_in); add_one (name3_in, value3_in); add_one (name4_in, value4_in); add_one (name5_in, value5_in); -- -- Add any global contexts l_row := g_gcontexts.FIRST; WHILE (l_row IS NOT NULL) LOOP add_one (g_gcontexts (l_row).name, g_gcontexts (l_row).VALUE); l_row := g_gcontexts.NEXT (l_row); END LOOP; END add_context_values; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. error_instance_id_out := q$error_instance_id_raised; ELSE SELECT q$error_instance_seq.NEXTVAL INTO l_error_instance_id FROM DUAL; INSERT INTO q$error_instance (id ,error_id ,error_stack ,call_stack ,MESSAGE ,system_error_code ,system_error_message) VALUES ( l_error_instance_id ,error_in.id ,SUBSTR (g_grabbed_settings.error_stack ,1 ,c_varchar2_max_length_db) ,SUBSTR (g_grabbed_settings.call_stack ,1 ,c_varchar2_max_length_db) ,SUBSTR (text_in, 1, c_varchar2_max_length_db) ,SUBSTR (g_grabbed_settings.system_error_code ,1 ,c_varchar2_max_length_db) ,SUBSTR (g_grabbed_settings.system_error_message ,1 ,c_varchar2_max_length_db)); add_context_values; mark_q$error_raised (l_error_instance_id); error_instance_id_out := l_error_instance_id; END IF; END register_error_instance; PROCEDURE grab_settings IS BEGIN g_grabbed_settings.system_error_code := SQLCODE; g_grabbed_settings.system_error_message := DBMS_UTILITY.format_error_stack; g_grabbed_settings.error_stack := error_backtrace; g_grabbed_settings.call_stack := DBMS_UTILITY.format_call_stack; END grab_settings; PROCEDURE register_error ( error_code_in IN q$error.code%TYPE ,error_instance_id_out IN OUT q$error_instance.id%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS PRAGMA AUTONOMOUS_TRANSACTION; FUNCTION error_from_repository (error_code_in IN PLS_INTEGER) RETURN q$error%ROWTYPE IS l_error q$error%ROWTYPE; BEGIN /* If the error code is an Oracle error, do not create an \"undefined\" entry in the error log table. */ BEGIN SELECT id INTO l_error.id FROM q$error WHERE code = error_code_in; EXCEPTION WHEN NO_DATA_FOUND THEN l_error.id := NULL; END; IF l_error.id IS NULL -- an unknown error; record as such THEN SELECT q$error_seq.NEXTVAL INTO l_error.id FROM DUAL; IF is_valid_oracle_error (error_code_in) THEN l_error.name := sqlcode_name (error_code_in); l_error.description := sqlcode_text (error_code_in); INSERT INTO q$error (id ,code ,name ,description ,error_category_name) VALUES (l_error.id ,error_code_in ,l_error.name ,l_error.description ,'Oracle Error'); ELSE l_error.name := SQLERRM (error_code_in); INSERT INTO q$error (id ,code ,name ,description ,error_category_name) VALUES ( l_error.id ,error_code_in ,l_error.name ,'Undefined runtime error: ' || error_code_in || '. You should define this in the error repository.' ,'UNDEFINED'); END IF; l_error := q$error_qp_onerow (l_error.id); END IF; RETURN l_error; END error_from_repository; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. error_instance_id_out := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; register_error_instance ( error_from_repository (error_code_in) ,text_in => text_in ,error_instance_id_out => error_instance_id_out ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in); COMMIT; END IF; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END register_error; /* 1.3.6 Standardize arg name - this OL no longer needed PROCEDURE register_error ( error_code_in IN q$error.code%TYPE , error_instance_id_out IN OUT q$error_instance.id%TYPE , text_in IN q$error_instance.MESSAGE%TYPE:= NULL , name1_in IN VARCHAR2 DEFAULT NULL , value1_in IN VARCHAR2 DEFAULT NULL , name2_in IN VARCHAR2 DEFAULT NULL , value2_in IN VARCHAR2 DEFAULT NULL , name3_in IN VARCHAR2 DEFAULT NULL , value3_in IN VARCHAR2 DEFAULT NULL , name4_in IN VARCHAR2 DEFAULT NULL , value4_in IN VARCHAR2 DEFAULT NULL , name5_in IN VARCHAR2 DEFAULT NULL , value5_in IN VARCHAR2 DEFAULT NULL , grab_settings_in IN BOOLEAN DEFAULT TRUE ) IS BEGIN register_error (error_code_in => error_code_in , error_instance_id_out => err_instance_id_out , text_in => text_in , name1_in => name1_in , value1_in => value1_in , name2_in => name2_in , value2_in => value2_in , name3_in => name3_in , value3_in => value3_in , name4_in => name4_in , value4_in => value4_in , name5_in => name5_in , value5_in => value5_in , grab_settings_in => grab_settings_in ); END; */ PROCEDURE register_error ( error_name_in IN q$error.name%TYPE ,error_instance_id_out IN OUT q$error_instance.id%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS PRAGMA AUTONOMOUS_TRANSACTION; l_error q$error%ROWTYPE; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. error_instance_id_out := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; BEGIN SELECT id INTO l_error.id FROM q$error WHERE name = error_name_in; EXCEPTION WHEN NO_DATA_FOUND THEN l_error.id := NULL; -- Create new error if not present. END; IF l_error.id IS NULL -- an unknown error; record as such THEN SELECT q$error_seq.NEXTVAL INTO l_error.id FROM DUAL; INSERT INTO q$error (id, name, description) VALUES ( l_error.id ,error_name_in ,'Undefined runtime error: ' || error_name_in || '. You should define this in the error repository.'); l_error := q$error_qp_onerow (l_error.id); END IF; register_error_instance ( l_error ,text_in => text_in ,error_instance_id_out => error_instance_id_out ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in); COMMIT; END IF; END register_error; PROCEDURE register_error ( error_code_in IN q$error.code%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS l_error_instance_id q$error_instance.id%TYPE; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. l_error_instance_id := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; register_error (error_code_in => error_code_in ,error_instance_id_out => l_error_instance_id ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => FALSE); END IF; END register_error; PROCEDURE register_error ( error_name_in IN q$error.name%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS l_error_instance_id q$error_instance.id%TYPE; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. l_error_instance_id := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; register_error (error_name_in => error_name_in ,error_instance_id_out => l_error_instance_id ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in); END IF; END register_error; /* V1.2.11 Allow user to specify that Oracle errors should be passed directly in the Oracle error information, and not hidden behind the \"error instance\" information. \"Hide\" (don't show) is the default setting. */ PROCEDURE show_oracle_error ( show_in IN BOOLEAN ,revert_after_raise_in IN BOOLEAN DEFAULT FALSE) IS BEGIN g_show_oracle.show := NVL (show_in, FALSE); IF g_show_oracle.revert THEN g_show_oracle.revert_to := g_show_oracle.show; END IF; g_show_oracle.revert := NVL (revert_after_raise_in, FALSE); END show_oracle_error; PROCEDURE revert_oracle_error IS BEGIN IF g_show_oracle.revert THEN g_show_oracle.show := g_show_oracle.revert_to; END IF; END revert_oracle_error; -- Now raise the error that has been defined. PROCEDURE raise_error_instance ( error_instance_id_in IN q$error_instance.id%TYPE) IS l_error_instance q$error_instance%ROWTYPE; PROCEDURE issue_oracle_raise ( error_instance_in IN q$error_instance%ROWTYPE) IS l_error q$error%ROWTYPE; l_error_code PLS_INTEGER; l_error_info error_info_rt; BEGIN l_error := q$error_qp_onerow (id_in => error_instance_in.error_id); l_error_code := NVL (l_error.code, error_instance_in.system_error_code); /* 1.2.12 If raising with message, load up the error message string. */ IF raising_with_message () THEN get_error_info (error_instance_in.id, l_error_info); internal_raise ( c_default_error_code , 'Error Code = ' || l_error_info.code || CHR (10) || 'Error Message = ' || l_error_info.text || CHR (10) || CHR (10) || 'Error Stack = ' || l_error_info.error_stack || CHR (10) || 'Call Stack = ' || l_error_info.call_stack); ELSIF oracle_app_error (l_error_code) THEN internal_raise (l_error_code, error_instance_in.MESSAGE); -- 1.0.5 Special case needed; cannot use EXCEPTION INIT with -1403 error. ELSIF l_error_code IN (100, -1403) THEN RAISE NO_DATA_FOUND; ELSIF l_error_code = 1 THEN -- Undefined error information. internal_raise ( c_default_error_code ,NVL (error_instance_in.MESSAGE, 'User-defined error')); /* No Oracle error (0). */ ELSIF l_error_code = 0 THEN internal_raise (c_default_error_code ,error_instance_in.MESSAGE); /* Using positive error numbers or we have an error instance with no error code (runtime-defined error). */ ELSIF l_error_code > 0 OR (l_error_code IS NULL AND error_instance_in.id IS NOT NULL) THEN internal_raise ( c_default_error_code ,c_error_prefix || LPAD (TO_CHAR (error_instance_in.id) ,c_error_code_len ,'0') || c_oracle_separator || LPAD ( TO_CHAR ( ABS (error_instance_in.system_error_code)) ,c_oracle_code_len ,'0')); -- We have a message to pass back, at least. ELSIF l_error_code IS NULL AND error_instance_in.MESSAGE IS NOT NULL THEN internal_raise (c_default_error_code ,error_instance_in.MESSAGE); ELSIF is_valid_oracle_error (l_error_code) THEN /* Negative Oracle error. */ EXECUTE IMMEDIATE 'DECLARE ' || ' myexc EXCEPTION; ' || ' PRAGMA EXCEPTION_INIT (myexc, ' || TO_CHAR (l_error_code) || ');' || 'BEGIN ' || ' RAISE myexc;' || 'END;'; ELSE -- Undefined error information. internal_raise ( c_default_error_code , 'Unable to raise error instance ' || error_instance_id_in || ' with error code ' || l_error_code); END IF; END issue_oracle_raise; BEGIN l_error_instance := q$error_instance_qp_onerow (error_instance_id_in); -- Turn back on when we can check individual context values -- to see if they are required. --check_contexts (error_instance_in => l_error_instance); issue_oracle_raise (l_error_instance); END raise_error_instance; /* 1.3.6 Standardize arg name - this OL not needed PROCEDURE raise_error_instance ( error_instance_id_in IN q$error_instance.id%TYPE ) IS BEGIN raise_error_instance (error_instance_id_in => err_instance_id_in); END;*/ PROCEDURE raise_error ( error_code_in IN q$error.code%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS l_error_instance_id q$error_instance.id%TYPE; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. l_error_instance_id := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; register_error (error_code_in => error_code_in ,error_instance_id_out => l_error_instance_id ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => FALSE); END IF; raise_error_instance (error_instance_id_in => l_error_instance_id); END raise_error; PROCEDURE raise_error ( error_name_in IN q$error.name%TYPE ,text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS l_error_instance_id q$error_instance.id%TYPE; BEGIN IF q$error_raised THEN -- Just continue to propagate this error instance. l_error_instance_id := q$error_instance_id_raised; ELSE IF grab_settings_in THEN grab_settings; END IF; register_error (error_name_in => error_name_in ,error_instance_id_out => l_error_instance_id ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => FALSE); END IF; raise_error_instance (error_instance_id_in => l_error_instance_id); END raise_error; PROCEDURE raise_unanticipated ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS BEGIN raise_error (error_code_in => SQLCODE /*'UNANTICIPATED-ERROR'*/ ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => grab_settings_in); END raise_unanticipated; /* V1.2.11 Allow user to specify that an Oracle error should be registered (but not re-raised) or raised (registered and re-raised). */ PROCEDURE register_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS BEGIN /* 1.2.15 Call register, not raise */ register_error (error_code_in => SQLCODE ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => grab_settings_in); END register_oracle_error; /* 1.2.15 Return error instance */ PROCEDURE register_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE ,error_instance_id_out OUT q$error_instance.id%TYPE) IS BEGIN register_error (error_code_in => SQLCODE ,error_instance_id_out => error_instance_id_out ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => grab_settings_in); END register_oracle_error; PROCEDURE raise_oracle_error ( text_in IN q$error_instance.MESSAGE%TYPE := NULL ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,grab_settings_in IN BOOLEAN DEFAULT TRUE) IS BEGIN raise_error (error_code_in => SQLCODE ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => grab_settings_in); END raise_oracle_error; -- Retrieval of error information PROCEDURE parse_message (string_in IN VARCHAR2 ,error_instance_id_out OUT PLS_INTEGER ,extra_out OUT VARCHAR2) IS l_startloc PLS_INTEGER := INSTR (string_in, ':'); --1.4.1 l_endloc PLS_INTEGER; l_errmsg q$error.description%TYPE := LTRIM (SUBSTR (string_in, l_startloc + 1)); BEGIN /* V1.2.11 Get latest instance from global package variable. If that is not available, then fall back on the error message; in any case try to extract the extra text. */ IF /* 1.2.14 Do not directly reference variables g_error_instance_raised */ q$error_instance_id_raised () IS NOT NULL THEN error_instance_id_out := q$error_instance_id_raised (); ELSE IF SUBSTR (LTRIM (l_errmsg), 1, c_error_prefix_len) = c_error_prefix THEN error_instance_id_out := TO_NUMBER ( SUBSTR (l_errmsg ,c_error_prefix_len + 1 ,c_error_code_len)); ELSE error_instance_id_out := NULL; END IF; END IF; extra_out := SUBSTR (l_errmsg, c_error_prefix_len + c_error_code_len + 1); END parse_message; FUNCTION error_instance_from_string (string_in IN VARCHAR2) RETURN PLS_INTEGER IS l_errmsg q$error.description%TYPE; retval PLS_INTEGER; BEGIN parse_message (string_in, retval, l_errmsg); RETURN retval; END error_instance_from_string; FUNCTION error_instance_from_sqlerrm RETURN PLS_INTEGER IS --1.4.1 l_loc PLS_INTEGER; --1.4.1 l_errmsg q$error.description%TYPE; --1.4.1 retval PLS_INTEGER; BEGIN RETURN error_instance_from_string (DBMS_UTILITY.format_error_stack); END error_instance_from_sqlerrm; PROCEDURE substitute_strings ( error_message_inout IN OUT q$error.substitute_string%TYPE ,recommendation_inout IN OUT q$error.recommendation%TYPE ,contexts_inout IN OUT q$error_context_tc) IS l_row PLS_INTEGER; --1.4.1 retval q$error.substitute_string%TYPE; PROCEDURE replace_all_occurrences ( context_in IN q$error_context%ROWTYPE ,context_row_in IN PLS_INTEGER) IS -- Maintain format of string while doing replacements on -- on named strings case IN sensitively. c_upper_subst_name q$error_context.name%TYPE := c_subst_char || UPPER (context_in.name); c_len PLS_INTEGER := LENGTH (context_in.name); l_loc PLS_INTEGER; l_found BOOLEAN DEFAULT FALSE; BEGIN IF error_message_inout IS NOT NULL THEN LOOP l_loc := INSTR (UPPER (error_message_inout) ,c_upper_subst_name ,1); EXIT WHEN l_loc = 0; l_found := TRUE; error_message_inout := SUBSTR (error_message_inout, 1, l_loc - 1) || context_in.VALUE || SUBSTR (error_message_inout, l_loc + c_len + 1); END LOOP; END IF; IF recommendation_inout IS NOT NULL THEN LOOP l_loc := INSTR (UPPER (recommendation_inout) ,c_upper_subst_name ,1); EXIT WHEN l_loc = 0; l_found := TRUE; recommendation_inout := SUBSTR (recommendation_inout, 1, l_loc - 1) || context_in.VALUE || SUBSTR (recommendation_inout, l_loc + c_len + 1); END LOOP; END IF; IF l_found THEN contexts_inout.delete (context_row_in); END IF; END replace_all_occurrences; BEGIN IF error_message_inout IS NOT NULL OR recommendation_inout IS NOT NULL THEN l_row := contexts_inout.FIRST; WHILE (l_row IS NOT NULL) LOOP replace_all_occurrences (contexts_inout (l_row), l_row); l_row := contexts_inout.NEXT (l_row); END LOOP; END IF; END substitute_strings; PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,error_info_out OUT error_info_rt ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline) IS l_error_instance q$error_instance%ROWTYPE; l_error q$error%ROWTYPE; PROCEDURE subst_for_msg_help_strings IS l_contexts q$error_context_tc; PROCEDURE add_to_text (new_string_in IN VARCHAR2) IS BEGIN IF new_string_in IS NOT NULL THEN error_info_out.text := error_info_out.text || CASE WHEN error_info_out.text IS NOT NULL THEN context_delimiter_in ELSE NULL END || new_string_in; END IF; END add_to_text; PROCEDURE add_to_context (new_string_in IN VARCHAR2) IS BEGIN IF new_string_in IS NOT NULL THEN error_info_out.contexts := CASE WHEN error_info_out.contexts IS NOT NULL THEN error_info_out.contexts || context_delimiter_in || new_string_in ELSE new_string_in END; END IF; END add_to_context; PROCEDURE add_remaining_contexts IS l_row PLS_INTEGER; BEGIN l_row := l_contexts.FIRST; IF l_row IS NOT NULL THEN -- Insert a blank line. add_to_context (' '); WHILE (l_row IS NOT NULL) LOOP add_to_context ( l_contexts (l_row).name || ' = ' || l_contexts (l_row).VALUE); l_row := l_contexts.NEXT (l_row); END LOOP; END IF; --1.0.4 If string gets too big then just pass back what we've got. EXCEPTION WHEN VALUE_ERROR THEN NULL; END add_remaining_contexts; BEGIN error_info_out.text := l_error_instance.MESSAGE; -- Use context values for substitution, then add any contexts -- that were not used at the end of the string. SELECT * BULK COLLECT INTO l_contexts FROM q$error_context WHERE error_instance_id = l_error_instance.id; substitute_strings (l_error.substitute_string ,l_error.recommendation ,l_contexts); error_info_out.recommendation := l_error.recommendation; IF error_info_out.text IS NULL AND l_error.substitute_string IS NULL THEN -- Fall back on the generic descriptoin. error_info_out.text := l_error.description; ELSE add_to_text (l_error.substitute_string); END IF; add_remaining_contexts; /* 1.4.0 option to disgard the remaining_context in the text*/ IF add_remaining_context_enabled () THEN BEGIN add_to_text (error_info_out.contexts); EXCEPTION WHEN VALUE_ERROR THEN add_to_text ( SUBSTR ( error_info_out.contexts ,1 , c_varchar2_max_length - LENGTH (error_info_out.text) - 2)); END; END IF; END subst_for_msg_help_strings; BEGIN IF error_instance_id_in IS NULL THEN -- Fall back on current error information. error_info_out.code := SQLCODE; error_info_out.name := 'Oracle Error'; error_info_out.system_error_code := SQLCODE; error_info_out.system_error_message := DBMS_UTILITY.format_error_stack; error_info_out.text := DBMS_UTILITY.format_error_stack; ELSE l_error_instance := q$error_instance_qp_onerow (error_instance_id_in); l_error := q$error_qp_onerow (l_error_instance.error_id); -- error_info_out.code := l_error.code; error_info_out.name := l_error.name; /* 1.4.0 */ error_info_out.error_category_name := l_error.error_category_name; -- subst_for_msg_help_strings; error_info_out.system_error_code := l_error_instance.system_error_code; error_info_out.system_error_message := l_error_instance.system_error_message; error_info_out.call_stack := l_error_instance.call_stack; /* 1.3.1 If an assertion, then error stack is empty. */ error_info_out.error_stack := NVL (l_error_instance.error_stack ,l_error_instance.call_stack); error_info_out.environment_info := l_error_instance.environment_info; /* 1.3 Add id and message, also environment info. */ error_info_out.id := l_error_instance.id; error_info_out.error_id := l_error_instance.error_id; error_info_out.created_on := l_error_instance.created_on; error_info_out.created_by := l_error_instance.created_by; error_info_out.changed_on := l_error_instance.changed_on; error_info_out.changed_by := l_error_instance.changed_by; error_info_out.MESSAGE := l_error_instance.MESSAGE; -- All done, revert to \"no error\" state. mark_q$error_handled; END IF; END get_error_info; PROCEDURE get_error_info (error_info_out OUT error_info_rt) IS l_message maxvarchar2_t := DBMS_UTILITY.format_error_stack; l_extra q$error.description%TYPE; l_error_instance_id q$error_instance.id%TYPE; BEGIN parse_message (l_message, l_error_instance_id, l_extra); IF l_error_instance_id IS NOT NULL THEN get_error_info (error_instance_id_in => l_error_instance_id ,error_info_out => error_info_out); ELSE -- Of the form ORA-NNNNN: Message error_info_out.code := SUBSTR (l_message, 4, INSTR (l_message, ':') - 4); error_info_out.name := 'System Error'; error_info_out.text := l_message; error_info_out.system_error_code := error_info_out.code; error_info_out.system_error_message := error_info_out.text; END IF; -- All done, revert to \"no error\" state. mark_q$error_handled; END get_error_info; /* 1.4.0 */ FUNCTION last_error_info RETURN error_info_rt IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error; END last_error_info; PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline) IS l_error error_info_rt; BEGIN get_error_info (error_instance_id_in => error_instance_id_in ,error_info_out => l_error ,context_delimiter_in => context_delimiter_in); -- -- All this logic repeated in next overloading! code_out := l_error.code; name_out := l_error.name; text_out := l_error.text; recommendation_out := l_error.recommendation; system_error_code_out := l_error.system_error_code; system_error_message_out := l_error.system_error_message; error_stack_out := l_error.error_stack; call_stack_out := l_error.call_stack; environment_info_out := l_error.environment_info; contexts_out := l_error.contexts; id_out := l_error.id; message_out := l_error.MESSAGE; END get_error_info; /* 1.4.0 */ FUNCTION last_error_id RETURN q$error.id%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.error_id; END last_error_id; /* 1.4.0 */ FUNCTION last_instance_id RETURN q$error_instance.id%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.id; END last_instance_id; /* 1.4.0 */ FUNCTION last_error_category_name RETURN q$error.error_category_name%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.error_category_name; END last_error_category_name; FUNCTION last_code RETURN q$error.code%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.code; END last_code; FUNCTION last_name RETURN q$error.name%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.name; END last_name; FUNCTION last_text RETURN q$error_instance.MESSAGE%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.text; END last_text; FUNCTION last_system_error_code RETURN q$error_instance.system_error_code%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.system_error_code; END last_system_error_code; FUNCTION last_system_error_message RETURN q$error_instance.system_error_message%TYPE IS l_error error_info_rt; BEGIN get_error_info (l_error); RETURN l_error.system_error_message; END last_system_error_message; PROCEDURE get_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE) IS l_id q$error_instance.id%TYPE; l_message q$error_instance.MESSAGE%TYPE; l_contexts maxvarchar2_t; BEGIN get_error_info (error_instance_id_in => error_instance_id_in ,code_out => code_out ,name_out => name_out ,text_out => text_out ,system_error_code_out => system_error_code_out ,system_error_message_out => system_error_message_out ,recommendation_out => recommendation_out ,error_stack_out => error_stack_out ,call_stack_out => call_stack_out ,environment_info_out => environment_info_out ,id_out => l_id ,message_out => l_message ,contexts_out => l_contexts); END get_error_info; /* 1.3.6 Standardize arg name */ PROCEDURE get_error_info ( error_message_in IN VARCHAR2 ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline) IS l_error error_info_rt; l_extra q$error.description%TYPE; l_error_instance_id q$error_instance.id%TYPE; BEGIN parse_message (error_message_in, l_error_instance_id, l_extra); IF l_error_instance_id IS NOT NULL THEN get_error_info (error_instance_id_in => l_error_instance_id ,error_info_out => l_error ,context_delimiter_in => context_delimiter_in); code_out := l_error.code; name_out := l_error.name; text_out := l_error.text; recommendation_out := l_error.recommendation; system_error_code_out := l_error.system_error_code; system_error_message_out := l_error.system_error_message; error_stack_out := l_error.error_stack; call_stack_out := l_error.call_stack; id_out := l_error.id; message_out := error_message_in; environment_info_out := l_error.environment_info; contexts_out := l_error.contexts; ELSE -- Of the form ORA-NNNNN: Message code_out := SUBSTR (error_message_in ,4 ,INSTR (error_message_in, ':') - 4); name_out := 'System Error'; text_out := error_message_in; system_error_code_out := code_out; system_error_message_out := text_out; id_out := NULL; message_out := error_message_in; END IF; -- All done, revert to \"no error\" state. mark_q$error_handled; END get_error_info; /* 1.3.6 Standardize arg name */ PROCEDURE get_error_info ( error_message_in IN VARCHAR2 ,code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE) IS l_id q$error_instance.id%TYPE; l_message q$error_instance.MESSAGE%TYPE; l_contexts maxvarchar2_t; BEGIN get_error_info (error_message_in => DBMS_UTILITY.format_error_stack ,code_out => code_out ,name_out => name_out ,text_out => text_out ,system_error_code_out => system_error_code_out ,system_error_message_out => system_error_message_out ,recommendation_out => recommendation_out ,error_stack_out => error_stack_out ,call_stack_out => call_stack_out ,environment_info_out => environment_info_out ,id_out => l_id ,message_out => l_message ,contexts_out => l_contexts); END get_error_info; PROCEDURE get_error_info ( code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE) IS BEGIN get_error_info (error_message_in => DBMS_UTILITY.format_error_stack ,code_out => code_out ,name_out => name_out ,text_out => text_out ,system_error_code_out => system_error_code_out ,system_error_message_out => system_error_message_out ,recommendation_out => recommendation_out ,error_stack_out => error_stack_out ,call_stack_out => call_stack_out ,environment_info_out => environment_info_out); END get_error_info; /* 1.3 Add id and message as OUT arguments */ PROCEDURE get_error_info ( code_out OUT q$error.code%TYPE ,name_out OUT q$error.name%TYPE ,text_out OUT q$error_instance.MESSAGE%TYPE ,system_error_code_out OUT q$error_instance.system_error_code%TYPE ,system_error_message_out OUT q$error_instance.system_error_message%TYPE ,recommendation_out OUT q$error.recommendation%TYPE ,error_stack_out OUT q$error_instance.error_stack%TYPE ,call_stack_out OUT q$error_instance.call_stack%TYPE ,environment_info_out OUT q$error_instance.environment_info%TYPE ,id_out OUT q$error_instance.id%TYPE ,message_out OUT q$error_instance.MESSAGE%TYPE ,contexts_out OUT VARCHAR2 ,context_delimiter_in IN VARCHAR2 DEFAULT c_newline) IS BEGIN get_error_info (error_message_in => DBMS_UTILITY.format_error_stack ,code_out => code_out ,name_out => name_out ,text_out => text_out ,system_error_code_out => system_error_code_out ,system_error_message_out => system_error_message_out ,recommendation_out => recommendation_out ,error_stack_out => error_stack_out ,call_stack_out => call_stack_out ,environment_info_out => environment_info_out ,id_out => id_out ,message_out => message_out ,context_delimiter_in => context_delimiter_in ,contexts_out => contexts_out); END get_error_info; -- 1.2 Get message text only FUNCTION error_message RETURN VARCHAR2 IS l_extra q$error.description%TYPE; l_error_instance_id q$error_instance.id%TYPE; l_error error_info_rt; BEGIN parse_message (DBMS_UTILITY.format_error_stack ,l_error_instance_id ,l_extra); get_error_info (error_instance_id_in => l_error_instance_id ,error_info_out => l_error); RETURN NVL (l_error.text, l_error.system_error_message); END error_message; FUNCTION error_clipboard (clear_clipboard_in IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 IS l_clipboard maxvarchar2_t; BEGIN l_clipboard := g_clipboard; IF clear_clipboard_in THEN clear_error_clipboard; END IF; RETURN l_clipboard; END error_clipboard; FUNCTION error_clipboard_is_full RETURN BOOLEAN IS BEGIN RETURN g_clipboard_is_full; /* IF g_clipboard IS NULL THEN RETURN FALSE; ELSE RETURN (c_warning LIKE g_clipboard || '%'); END IF; */ END error_clipboard_is_full; PROCEDURE clear_error_clipboard IS BEGIN g_clipboard := NULL; g_clipboard_is_full := FALSE; END clear_error_clipboard; PROCEDURE add_to_error_clipboard (str IN VARCHAR2) IS BEGIN IF g_clipboard IS NULL THEN g_clipboard := str; ELSE g_clipboard := g_clipboard || CHR (10) || str; END IF; END add_to_error_clipboard; PROCEDURE show_error_info ( error_instance_id_in IN q$error_instance.id%TYPE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS code_out q$error.code%TYPE; name_out q$error.name%TYPE; text_out q$error_instance.MESSAGE%TYPE; system_error_code_out q$error_instance.system_error_code%TYPE; system_error_message_out q$error_instance.system_error_message%TYPE; recommendation_out q$error.recommendation%TYPE; error_stack_out q$error_instance.error_stack%TYPE; call_stack_out q$error_instance.call_stack%TYPE; environment_info_out q$error_instance.environment_info%TYPE; PROCEDURE pl (str IN VARCHAR2) IS already_full EXCEPTION; BEGIN IF error_clipboard_is_full THEN RAISE already_full; END IF; IF copy_to_clipboard_in THEN add_to_error_clipboard (str); ELSE q$error_manager.pl (str); END IF; EXCEPTION WHEN already_full THEN NULL; WHEN VALUE_ERROR THEN g_clipboard_is_full := TRUE; IF LENGTH (g_clipboard) > (c_varchar2_max_length - c_warning_len) THEN g_clipboard := c_trunc_warning || SUBSTR (g_clipboard ,1 ,c_varchar2_max_length - c_warning_len); ELSE g_clipboard := c_trunc_warning || g_clipboard; END IF; END pl; PROCEDURE pl_header (str IN VARCHAR2) IS BEGIN pl ('================================================='); pl (str); END pl_header; PROCEDURE show_contexts IS l_context q$error_context_tc; l_row PLS_INTEGER; BEGIN SELECT * BULK COLLECT INTO l_context FROM q$error_context WHERE error_instance_id = error_instance_id_in; l_row := l_context.FIRST; IF l_row IS NOT NULL THEN pl (' > Error context information:'); WHILE (l_row IS NOT NULL) LOOP pl (' Context = \"' || l_context (l_row).name || '\"'); pl (' Value = \"' || l_context (l_row).VALUE || '\"'); l_row := l_context.NEXT (l_row); END LOOP; END IF; END show_contexts; BEGIN get_error_info (error_instance_id_in => error_instance_id_in ,code_out => code_out ,name_out => name_out ,text_out => text_out ,system_error_code_out => system_error_code_out ,system_error_message_out => system_error_message_out ,recommendation_out => recommendation_out ,error_stack_out => error_stack_out ,call_stack_out => call_stack_out ,environment_info_out => environment_info_out); pl_header ( 'Error report for error instance ' || error_instance_id_in); pl (' > Code-Name: ' || code_out || '-' || name_out); pl (' > Text: ' || text_out); pl (' > System error code: ' || system_error_code_out); pl (' > System error message: ' || system_error_message_out); pl (' > Recommendation: ' || recommendation_out); pl (' > Error stack: ' || error_stack_out); pl (' > Call stack: ' || call_stack_out); IF environment_info_out IS NOT NULL THEN pl_header (' > Environmental Information'); pl (' ' || environment_info_out); END IF; show_contexts; END show_error_info; PROCEDURE show_errors (cursor_in IN weak_refcursor ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS l_error_id PLS_INTEGER; BEGIN LOOP FETCH cursor_in INTO l_error_id; EXIT WHEN cursor_in%NOTFOUND; show_error_info (l_error_id ,copy_to_clipboard_in => copy_to_clipboard_in); EXIT WHEN error_clipboard_is_full; END LOOP; END show_errors; PROCEDURE show_errors_after ( date_in IN DATE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS l_cursor weak_refcursor; BEGIN OPEN l_cursor FOR SELECT id FROM q$error_instance WHERE created_on >= date_in ORDER BY id; show_errors (l_cursor, copy_to_clipboard_in => copy_to_clipboard_in); CLOSE l_cursor; END show_errors_after; PROCEDURE show_errors_with_message ( text_in IN VARCHAR2 ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS l_cursor weak_refcursor; BEGIN OPEN l_cursor FOR SELECT id FROM q$error_instance WHERE INSTR (UPPER (MESSAGE), UPPER (text_in)) > 0 ORDER BY id; show_errors (l_cursor, copy_to_clipboard_in => copy_to_clipboard_in); CLOSE l_cursor; END show_errors_with_message; PROCEDURE show_errors_with_code ( error_code_in IN PLS_INTEGER ,is_system_error_code_in IN BOOLEAN DEFAULT FALSE ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS l_cursor weak_refcursor; BEGIN IF is_system_error_code_in THEN OPEN l_cursor FOR SELECT id FROM q$error_instance WHERE system_error_code = error_code_in ORDER BY id; ELSE OPEN l_cursor FOR SELECT i.id FROM q$error_instance i, q$error e WHERE i.error_id = e.id AND code = error_code_in ORDER BY i.id; END IF; show_errors (l_cursor, copy_to_clipboard_in => copy_to_clipboard_in); CLOSE l_cursor; END show_errors_with_code; PROCEDURE show_errors_for ( where_clause_in IN VARCHAR2 ,copy_to_clipboard_in IN BOOLEAN DEFAULT FALSE) IS l_cursor weak_refcursor; BEGIN OPEN l_cursor FOR 'SELECT ID FROM q$error_instance WHERE ' || where_clause_in || ' ORDER BY ID'; show_errors (l_cursor, copy_to_clipboard_in => copy_to_clipboard_in); CLOSE l_cursor; END show_errors_for; PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,error_code_in IN q$error.code%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL) IS BEGIN IF condition_in IS NULL OR NOT condition_in THEN trace ('ASSERTION FAILURE', text_in, FALSE); raise_error (error_code_in => error_code_in ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => TRUE); END IF; END assert; PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,error_name_in IN q$error.name%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL) IS BEGIN IF condition_in IS NULL OR NOT condition_in THEN trace ('ASSERT', text_in, TRUE); raise_error (error_name_in => error_name_in ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => TRUE); END IF; END assert; PROCEDURE assert (condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL) IS BEGIN IF condition_in IS NULL OR NOT condition_in THEN trace ('ASSERT', text_in, TRUE); raise_error (error_name_in => 'ASSERTION-FAILURE' ,text_in => text_in ,name1_in => name1_in ,value1_in => value1_in ,name2_in => name2_in ,value2_in => value2_in ,name3_in => name3_in ,value3_in => value3_in ,name4_in => name4_in ,value4_in => value4_in ,name5_in => name5_in ,value5_in => value5_in ,grab_settings_in => TRUE); END IF; END assert; PROCEDURE simple_assert ( condition_in IN BOOLEAN ,text_in IN q$error_instance.MESSAGE%TYPE ,name1_in IN VARCHAR2 DEFAULT NULL ,value1_in IN VARCHAR2 DEFAULT NULL ,name2_in IN VARCHAR2 DEFAULT NULL ,value2_in IN VARCHAR2 DEFAULT NULL ,name3_in IN VARCHAR2 DEFAULT NULL ,value3_in IN VARCHAR2 DEFAULT NULL ,name4_in IN VARCHAR2 DEFAULT NULL ,value4_in IN VARCHAR2 DEFAULT NULL ,name5_in IN VARCHAR2 DEFAULT NULL ,value5_in IN VARCHAR2 DEFAULT NULL ,raise_with_code_in IN PLS_INTEGER DEFAULT -20000) IS l_text maxvarchar2_t := text_in; PROCEDURE add_nv (text_inout IN OUT VARCHAR2 ,nm_in IN VARCHAR2 ,val_in IN VARCHAR2) IS BEGIN text_inout := text_inout || CASE WHEN nm_in IS NULL THEN NULL ELSE CHR (10) || nm_in || ' = ' || val_in END; END add_nv; BEGIN IF condition_in IS NULL OR NOT condition_in THEN add_nv (l_text, name1_in, value1_in); add_nv (l_text, name2_in, value2_in); add_nv (l_text, name3_in, value3_in); add_nv (l_text, name4_in, value4_in); add_nv (l_text, name5_in, value5_in); raise_application_error (raise_with_code_in, l_text); END IF; END simple_assert; PROCEDURE start_execution (program_name_in IN VARCHAR2 := NULL ,information_in IN VARCHAR2 := NULL) IS BEGIN NULL; END start_execution; /* Test builder validation: * return a status string composed of type:message * if no prefix, then it is an error */ FUNCTION is_warning (status_in IN VARCHAR2) RETURN BOOLEAN IS BEGIN RETURN status_in LIKE c_warning || '%'; END is_warning; FUNCTION is_error (status_in IN VARCHAR2) RETURN BOOLEAN IS BEGIN RETURN status_in LIKE c_error || '%'; END is_error; FUNCTION is_info_msg (status_in IN VARCHAR2) RETURN BOOLEAN IS BEGIN RETURN status_in LIKE c_info_msg || '%'; END is_info_msg; PROCEDURE parse_status_string (status_in IN VARCHAR2 ,status_type_out OUT VARCHAR2 ,message_out OUT VARCHAR2) IS l_loc PLS_INTEGER := INSTR (status_in, c_status_delimiter); BEGIN IF l_loc = 0 THEN status_type_out := c_error; message_out := status_in; ELSE status_type_out := SUBSTR (status_in, 1, l_loc - 1); message_out := SUBSTR (status_in, l_loc + 1); END IF; END parse_status_string; FUNCTION make_a_string (header_in IN VARCHAR2, status_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN header_in || c_status_delimiter || status_in || CASE WHEN trace_enabled THEN CHR (10) || CHR (10) || error_backtrace ELSE NULL END; END make_a_string; FUNCTION make_an_info_msg (status_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN make_a_string (c_info_msg, status_in); END make_an_info_msg; FUNCTION make_a_warning (status_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN make_a_string (c_warning, status_in); END make_a_warning; FUNCTION make_an_error (status_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN make_a_string (c_error, status_in); END make_an_error; PROCEDURE make_a_warning (status_inout IN OUT VARCHAR2) IS BEGIN status_inout := make_a_warning (status_inout); END make_a_warning; PROCEDURE make_an_error (status_inout IN OUT VARCHAR2) IS BEGIN status_inout := make_an_error (status_inout); END make_an_error; PROCEDURE make_an_info_msg (status_inout IN OUT VARCHAR2) IS BEGIN status_inout := make_an_info_msg (status_inout); END make_an_info_msg; FUNCTION log_entries_after (timestamp_in IN DATE) RETURN SYS_REFCURSOR IS CV SYS_REFCURSOR; BEGIN IF trace_enabled THEN trace ('log_entries_after for date', timestamp_in, TRUE); END IF; OPEN CV FOR SELECT full_log_string (context, text, NULL) FROM q$log ql /* WHERE BM2066 return all rows for now ql.created_on >= timestamp_in*/ ORDER BY ql.id; RETURN CV; END log_entries_after; PROCEDURE not_yet_implemented (program_name_in IN VARCHAR2) IS BEGIN DBMS_OUTPUT.put_line ( 'Callstack that found its way to \"' || program_name_in || '\"'); DBMS_OUTPUT.put_line (DBMS_UTILITY.format_call_stack); raise_application_error ( c_not_yet_implemented , 'Program named \"' || program_name_in || '\" has not yet been implemented.' || ' Enable SERVEROUTPUT to see callstack for this program call.'); END not_yet_implemented; PROCEDURE discard_error_state IS BEGIN g_discard_error_state := TRUE; /*fds: 1.4.1 - discard_error_state: clear state already stored */ g_error_instance_raised := NULL; clear_gcontexts; END discard_error_state; PROCEDURE keep_error_state IS BEGIN g_discard_error_state := FALSE; END keep_error_state; FUNCTION error_state_discarded RETURN BOOLEAN IS BEGIN RETURN g_discard_error_state; END error_state_discarded; PROCEDURE clean_up_errors (older_than_in IN DATE DEFAULT NULL) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN DELETE FROM q$error_instance WHERE older_than_in IS NULL OR created_on <= older_than_in; COMMIT; END clean_up_errors;END q$error_manager;/ 自定义错误 qem$define_errors.sql 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155/*| This program is a part of the Quest Error Manager for Oracle.| This product is freeware and is not supported by Quest.|| You may copy and change the data in these statements to meet| your own application-specific requirements. That is, you can| define your own errors in this script.|| www.quest.com| | Copyright, Quest Software, Inc, 2007| All rights reserved*/DECLARE c_support CONSTANT VARCHAR2 (32767) := '<PUT IN YOUR OWN \"CONTACT SUPPORT\" MESSAGE HERE>'; PROCEDURE ins ( error_category_name_in IN VARCHAR2 , code_in IN NUMBER DEFAULT NULL , NAME_IN IN VARCHAR2 DEFAULT NULL , description_in IN VARCHAR2 DEFAULT NULL , substitute_string_in IN VARCHAR2 DEFAULT NULL , recommendation_in IN VARCHAR2 DEFAULT NULL ) IS BEGIN INSERT INTO q$error (ID, error_category_name, code , NAME, description, substitute_string , recommendation ) VALUES (q$error_seq.NEXTVAL, error_category_name_in, code_in , NAME_IN, description_in, substitute_string_in , recommendation_in ); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN /* Running install over existing install. Just ignore. */ NULL; END ins;BEGIN ins (error_category_name_in => 'GENERAL ADMINISTRATION' , code_in => 10000001 , NAME_IN => 'ASSERTION-FAILURE' , description_in => 'This exception is raised when you:* call q$error_manager.assert* pass it a Boolean expression that evaluates to FALSE or NULL* and do not provide a specific error code or name to be raised for you.There are no pre-defined context values used in the error message. Instead, if you set any name-value pairs when you call q$error_manager.assert, these will be displayed.' , recommendation_in => '' , substitute_string_in => 'The Boolean expression provided evaluated to FALSE or NULL. The condition checked by the assertion program has failed.' ); ins (error_category_name_in => 'GENERAL ADMINISTRATION' , code_in => 10000003 , NAME_IN => 'DEPRECATED-FUNCTIONALITY' , description_in => 'The user tried to run a program that has been deprecated.' , recommendation_in => 'This is NOT a user error. ' || c_support , substitute_string_in => 'The application attempted to run a program named \"$program_name\" that has been deprecated; that is, it is no longer available and should not be executed.' ); ins (error_category_name_in => 'GENERAL ADMINISTRATION' , code_in => 10000000 , NAME_IN => 'NOEXCEPTION' , description_in => 'No exception has been raised.' , recommendation_in => '' , substitute_string_in => c_support ); ins (error_category_name_in => 'GENERAL ADMINISTRATION' , code_in => 10000002 , NAME_IN => 'UNANTICIPATED-ERROR' , description_in => 'This exception should be used in a PL/SQL WHEN OTHERS clause or other similar ''catch all'' handler for errors. You could not in advance know that this error was raised, and therefore could not handle it specifically. Instead, you will pass as much information possible and hope that the user or tester can figure out the problem.' , recommendation_in => 'If you cannot figure out the cause of the difficulty, ' || c_support , substitute_string_in => 'An unanticipated error has occurred. Please review the information below and see if it will help you resolve the problem.' ); ins (error_category_name_in => 'DML ERRORS' , code_in => -2290 , NAME_IN => 'CHECK-CONSTRAINT-FAILURE' , description_in => 'A check constraint failed.' , recommendation_in => 'Determine the logic of the constraint and change the data so that it will not fail.' , substitute_string_in => 'The constaint named $constaint_name, defined on table $table_name and owned by $owner, failed. This means that a rule that was defined on this table was violated in the process of inserting or updating a row of data.' ); ins (error_category_name_in => 'DML ERRORS' , code_in => -1400 , NAME_IN => 'COLUMN-CANNOT-BE-NULL' , description_in => 'The column has been defined to be NOT NULL, yet a DML operation attempted to set it to NULL.' , recommendation_in => 'If you can change the value to something that is not blank, do so. If this is not under your control, ' || c_support , substitute_string_in => 'You have tried to set the column $COLUMN_NAME of table $OWNER.$TABLE_NAME to NULL or a blank line; this is not allowed.' ); /* V1.2.11 Error number is changed to -1. Remove existing row. */ DELETE FROM q$error WHERE name = 'DUPLICATE-VALUE'; ins (error_category_name_in => 'DML ERRORS' , code_in => -1 , NAME_IN => 'DUPLICATE-VALUE' , description_in => 'A duplicate value on index error (ORA-00001) was raised for an insert or update' , recommendation_in => 'This means that either the primary key is a repeated value, which is almost certainly a software error, or you are repeating a value in a column that must be unique. For example, in many applications the NAME will be unique. Perhaps you entered a name that is already in use.' , substitute_string_in => 'We tried to insert or update a row of data, and ran into the following problem: at least one of the values are already in the database and you are not allowed to have duplicates.This error occurred on the table named $table_name, which is owned by $owner. The name of this unique constraint is $constraint_name. In some cases, we may be able to also display the columns and values that triggered the conflict. If available, they follow below.' ); ins (error_category_name_in => 'DML ERRORS' , code_in => -2266 , NAME_IN => 'EXISTING-FKY-REFERENCE' , description_in => 'An DROP TABLE, TRUNCATE TABLE or DELETE statement attempted to remove a row in which the primary key or unique values are referenced by the foreign key value in another table.' , recommendation_in => c_support , substitute_string_in => 'We were unable to remove a row from table $owner.$table because it contains a primary key or unique value that is referenced by the foreign key value in another table.' ); ins (error_category_name_in => 'DML ERRORS' , code_in => -24381 , NAME_IN => 'FORALL-INSERT-FAILURE' , description_in => 'An error occurred while performing a bulk insert operation.' , recommendation_in => '' , substitute_string_in => 'An attempt to perform a FORALL on table $TABLE_NAME failed. The incoming array had $ROW_COUNT'' rows in it. The last action taken during that procedure''s execution was:$PROGRESS_INDICATOR' ); ins (error_category_name_in => 'DML ERRORS' , code_in => -2291 , NAME_IN => 'INTEGRITY-CONSTRAINT-FAILURE' , description_in => 'An integrity constraint (primary key, foreign key, etc.) failed.' , recommendation_in => 'Make sure that you are entering data correctly. If that seems to be the case, ' || c_support , substitute_string_in => 'The constraint named $constraint_name, defined on table $table_name and owned by $owner, failed.' ); ins (error_category_name_in => 'DML ERRORS' , code_in => 10010008 , NAME_IN => 'SEQUENCE-GENERATION-FAILURE' , description_in => 'An attempt to generate a sequence with the next primary key function resulted in failure.' , recommendation_in => 'Make sure the sequence exists and that you have the authority necessary (SELECT) to obtain the next value from this sequence.' , substitute_string_in => 'An attempt to obtain the next sequence value from $sequence failed.' ); COMMIT;END;/ 卸载 qem$uninstall.sql 123456789101112131415161718192021222324252627282930313233/*| This program is a part of the Quest Error Manager for Oracle.| This product is freeware and is not supported by Quest.| | www.quest.com| | Copyright, Quest Software, Inc, 2007| All rights reserved*/DROP SEQUENCE q$error_seq/DROP SEQUENCE q$log_seq/DROP SEQUENCE q$error_context_seq/DROP SEQUENCE q$error_instance_seq/DROP TABLE q$error_context/DROP TABLE q$error_instance/DROP TABLE q$error/DROP TABLE q$log/DROP PACKAGE q$error_manager/","tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://betgar.github.io/tags/Oracle/"},{"name":"PLSQL","slug":"PLSQL","permalink":"https://betgar.github.io/tags/PLSQL/"},{"name":"qem","slug":"qem","permalink":"https://betgar.github.io/tags/qem/"}]},{"title":"Oracle 绑定变量(bind variable)","date":"2018-04-16T12:00:00.000Z","path":"2018/04/16/oracle-bind-variable/","text":"Oracle 绑定变量(bind variable) 转载: http://www.ecdoer.com/post/oracle-dynamic-sql.html 【动态SQL定义】动态SQL是指在PL/SQL块中,可以根据不同参数拼接不同的SQL字符串,即执行前不能确定该SQL是什么(如表名、字段名或条件值未知)。 【动态SQL与静态SQL区别】1)静态SQL是确定的,在执行前已经完成编译(随PL/SQL块一起完成了编译),执行时数据库直接执行编译好的SQL;而动态SQL是不确定的,是在程序运行时才编译并执行(不随PL/SQL块编译时编译)。 2)静态SQL一次编译,多次调用,使用相同的执行计划。动态SQL每次运行均要先对其编译,即多次调用则需要多次编译。 3)静态SQL使用相同的执行计划,对于确定的任务而言,静态SQL更具有高效性,但缺乏灵活性;动态SQL使用了不同的执行计划,效率不如静态SQL,但能够解决复杂的问题。 4)动态SQL容易产生SQL注入,为数据库安全带来隐患。 【动态SQL用途】1)处理PL/SQL块中不能处理的DDL或DCL语句; 2)处理PL/SQL块中带参数具有不确定性的SQL(如表名、字段名或条件值作为变量); 3)PL/SQL中静态SQL出现性能瓶颈; 【动态SQL语法】123456789101112131415161718192021execute immediate 'sql';--区别于静态SQL变量赋值的INTO位置,select col into var from table;execute immediate 'sql_select' into var_1, var_2;execute immediate 'sql' using bind_var_1,bind_var_2;execute immediate 'sql_select' into var_1, var_2 using bind_var_1,bind_var_2;-- returning子句在insert之后返回新加的值,update之后返回修改后的值,delete返回删除前的值execute immediate 'sql_dml' returning into var_1;--批量动态SQL,即在动态SQL中使用BULK子句,或使用游标变量时在fetch中使用BULK,或在FORALL语句中使用BULK子句来实现execute immediate 'sql' bulk collect into var_array;--动态REF游标,不同于静态游标声明方式open cursor_name for ‘sql’ using bind_var_1; 【动态SQL举例】123456789/*动态SQL执行DDL语句(不能跟using)*/declare sql_statement varchar2(100); table_name varchar2(20);begin table_name := 'emp'; sql_statement := 'truncate table ' || table_name; execute immediate sql_statement;end; –注:变量不能放在引号内,否则会解析成文本而不是变量,但若在DML语句的绑定变量,则需要在引号内,拼SQL时一定要注意关键字后的空格,如truncate table之后是有一个空格的 1234567891011121314151617181920212223/*动态SELECT语句(不能跟returning)*//*SELECT中表名是变量*/declare sql_stat varchar2(100); v_tab varchar2(20);begin v_tab := 'emp'; sql_stat := 'select sal from '|| v_tab || ' where empno = 1'; dbms_output.put_line(sql_stat); execute immediate sql_stat;end;--注:注意from后面和where前面是有空格的/*SELECT INTO变量赋值*/declare sql_stat varchar2(100); v_sal number(6, 2);begin sql_stat := 'select sal from emp where empno = :1'; execute immediate sql_stat into v_sal using 1; dbms_output.put_line(v_sal);end; –注:INTO的位置,不像静态SQL一样在字段名后,而是在execute immediate时才加,且关键字在using之前;另外,注意区分有returning子句的,returning子句在SQL文本和execute immediate子句中均有出现,但此时INTO的位置在using之后。 1234567891011121314/*动态SQL执行DML语句(含returning子句)*/declare v_sal number(6, 2); sql_stat varchar2(100); v_eno number(2);begin v_eno := 1; sql_stat := 'update emp set sal = sal * (1 + :percent / 100) where empno = :2 returning sal into :3'; execute immediate sql_stat using &1, v_eno returning into v_sal; commit; dbms_output.put_line('new salary: ' || v_sal);end; –注:动态SQL内的结尾不加分号“;”,但PL/SQL的语句结尾加“;”,且冒号后的占位符是字母或数字完全没有影响,using的变量值可以是绑定变量、或变量或常量均可。如:占位符:percent对应绑定变量&1,虽然名字完全不同,但也不影响,另外,占位符:2对应了变量v_eno,也是完全不同。即,按顺序占位,与占位符名无关。按顺序绑定变量,与绑定变量名无关。给占位符或绑定变量命名只是为了程序的可读性。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273declare type cur_totalvaue_type is ref cursor; cur_totalvaue cur_totalvaue_type; sql_stat VARCHAR2(100); record_totalvalue t_threeyear_hour%rowtype;begin sql_stat := 'select * from t_threeyear_hour t where t.time = :1'; open cur_totalvaue for sql_stat using to_date('20130209', 'yyyymmdd'); loop fetch cur_totalvaue into record_totalvalue; exit when cur_totalvaue%NOTFOUND; dbms_output.put_line(record_totalvalue.time || ' is ' || record_totalvalue.totalvalue); end loop; close cur_totalvaue;end;/*批量动态SQL(BULK COLLECT INTO )*/declare type ename_table_type is table of emp.ename%type index by binary_integer; type sal_table_type is table of emp.sal%type index by binary_integer; ename_table ename_table_type; sal_table sal_table_type; sql_stat varchar2(120); v_percent number := &percent; v_dno number := &dno;begin sql_stat := 'update emp set sal = sal * (1 + :percent / 100)' || ' where deptno = :dno' || ' returning ename, sal into :name, :salary'; execute immediate sql_stat using v_percent, v_dno returning bulk collect into ename_table, sal_table; for i in 1 .. ename_table.count loop dbms_output.put_line('employee ' || ename_table(i) || ' salary is: ' || sal_table(i)); end loop;end;/*动态游标+BULK*/declare type cur_emp_type is ref cursor; cur_emp cur_emp_type; type ename_table_type is table of emp.ename%type index by binary_integer; ename_table ename_table_type; sql_stat varchar2(120);begin sql_stat := 'select ename from emp where deptno = :dno'; open cur_emp for sql_stat using &dno; fetch cur_emp bulk collect into ename_table; for i in 1 .. ename_table.count loop dbms_output.put_line('employee name is ' || ename_table(i)); end loop; close cur_emp;end;/*FORALL+BULK(仅支持DML)*/declare type ename_table_type is table of tb2.ename%type; type sal_table_type is table of tb2.sal%type; ename_table ename_table_type; sal_table sal_table_type; sql_stat varchar2(100);begin ename_table := ename_table_type('blake', 'ford', 'miller'); --为复合类型赋值 sql_stat := 'update tb2 set sal = sal * 1.1 where ename = :1 returning sal into :2'; forall i in 1 .. ename_table.count execute immediate sql_stat using ename_table(i) returning bulk collect into sal_table; for j in 1 .. ename_table.count loop dbms_output.put_line('the new salary is ' || sal_table(j) || ' for ' || ename_table(j)); end loop;end; 【绑定变量】Oracle会自动把循环中带参数SQL语句转换为采用绑定变量方式执行,以减少硬解析和latch竞争。所以,很多时候我们并不需要专门去注意在PL/SQL块中使用绑定变量,Oracle会自动帮我们完成这个动作。至于SQLPLUS中使用绑定变量,其实我们日常也较少使用SQLPLUSE,故也无需专门注意该问题。 1234567-- sqlplus中使用绑定变量variable x number(4);exec :x := 1;select * from emp t where empno = :x; –注:必须先使用关键字variable声明变量,再使用exec给绑定变量赋值(变量前带冒号),最后使用绑定变量。 绑定变量效率高的原因 Oracle中,对于一个提交的sql语句,存在两种可选的解析过程,一种叫做硬解析,一种叫做软解析。当一个sql语句提交后,oracle会首先检查一下共享缓冲池(shared pool)里有没有与之完全相同的语句,如果有的话只须执行软分析即可,否则就得进行硬分析。 硬解析需要经解析,制定执行路径、优化访问计划等许多的步骤。硬解释不仅仅耗费大量的cpu,更重要的是会占据重要闩(latch)资源,严重的影响系统规模扩大(即限制了系统的并发行),而且引起的问题不能通过增加内存条和cpu的数量来解决。之所以这样是因为闩是为了顺序访问以及修改一些内存区域而设置的,这些内存区域是不能被同时修改。 若Oracle在shared pool中查找相同SQL语句的过程中,SQL语句使用了绑定变量(bind variable),那么就是比较SQL语句的静态部分,前面我们已经知道,静态部分是有限的,很容易就能够缓存在内存里,从而找到相同的SQL语句的概率很高。如果没有使用绑定变量,则就是比较SQL语句的静态部分和动态部分,而动态部分的变化是无限的,因此这样的SQL语句很难被缓存在shared pool里。毕竟内存是有限的,不可能把所有的动态部分都缓存在sharedpool里,即便能够缓存,管理这样一个无限大的shared pool也是不可能完成的任务。不使用绑定变量导致的直接结果就是,找到相同的SQL语句的概率很低,导致必须完整的解析SQL语句,也就导致消耗更多的资源。 绑定变量的使用环境 由于在OLTP中,SQL语句大多是比较简单或者操作的结果集都很小。当一个表上创建了索引,那么这种极小结果集的操作使用索引最合适,并且几乎所有的SQL的执行计划的索引都会被选择,因为这种情况下,索引可能只需要扫描几个数据块就可以定位到数据,而全表扫描将会相当耗资源。因此,这种情况下,即使每个用户的谓词条件不一样,执行计划也是一样的,就是都用索引来访问数据,基本不会出现全表扫描的情况。在这种执行计划几乎唯一的情况下,使用绑定变量来代替谓词常量是合适的。 注意,在OLTP中使用绑定变量一定要注意,变量的类型要与表字段类型一样,否则若造成了隐式转换,索引将失效而采用全表扫描,此时会造成系统性能的极大下降。 在OLAP系统中,SQL的操作就复杂很多,OLAP数据库上大多数时候运行的一些报表SQL,这些SQL经常会用到聚合查询(如:groupby),而且结果集也是非常庞大,在这种情况下,索引并不是必然的选择,甚至有时候全表扫描的性能会更优于索引,即使相同的SQL,如果谓词不同,执行计划都可能不同。让Oracle对每条SQL做硬分析,确切的知道谓词条件的值,这对执行计划的选择至关重要,这样做的原因是为了得到一个最优的执行计划。在OLAP系统中,系统的资源基本上是用于做大的SQL查询,和查询比起来SQL解析消耗的资源显得微不足道,SQL硬分析的代价是可以忽略的。因此让Oracle确切地知道谓词的数值至关重要,它直接决定了SQL执行计划的选择,所以在OLAP系统完全没有必要绑定变量,那样很可能带来负面影响,比如导致SQL选择错误的执行,这个代价有时是灾难性的。 【文章参考】 http://blog.csdn.net/leshami/article/details/6118010 http://czmmiao.iteye.com/blog/1489625","tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://betgar.github.io/tags/Oracle/"},{"name":"PLSQL","slug":"PLSQL","permalink":"https://betgar.github.io/tags/PLSQL/"}]},{"title":"plsql-forall","date":"2018-04-10T16:00:00.000Z","path":"2018/04/11/plsql-forall/","text":"PL/SQL FORALL forall语法 1234567891011121314151617181920212223242526DECLARE V_INPUT_STR VARCHAR2(1000); V_INSER_SQL VARCHAR2(1000); V_STR_LIST DBMS_SQL.VARCHAR2S;BEGIN V_INPUT_STR := '2003130, 2003131, 2003132, 2003116, 2001100,,2001101,'; V_STR_LIST := TEST_STRING_TO_LIST(V_INPUT_STR, ',', FALSE); FORALL V_INDEX IN V_STR_LIST.FIRST .. V_STR_LIST.LAST INSERT INTO GT_KM_IMP (ITEMCODE) VALUES (:ITEMCODE) USING V_STR_LIST (V_INDEX);END;DECLARE V_INPUT_STR VARCHAR2(1000); V_INSER_SQL VARCHAR2(1000); V_STR_LIST DBMS_SQL.VARCHAR2S;BEGIN V_INPUT_STR := '2003130, 2003131, 2003132, 2003116, 2001100,,2001101,'; V_STR_LIST := TEST_STRING_TO_LIST(V_INPUT_STR, ',', FALSE); FORALL V_INDEX IN INDICES OF V_STR_LIST BETWEEN V_STR_LIST.FIRST AND V_STR_LIST.LAST INSERT INTO GT_KM_IMP (ITEMCODE) VALUES (V_STR_LIST(V_INDEX));END;","tags":[]},{"title":"解析URI中的query string","date":"2018-03-24T12:00:00.000Z","path":"2018/03/24/parse-uri-string-into-key-value-collection-in-java/","text":"解析URI中的query string 解析URL中的query string成集合. 前端可以配合使用$.param生成序列化字符串。 JavaScript解析URI中的query string原生解析 查找指定name的参数值,只会匹配第一个找到的。 123456789function getParameterByName(name, url) { if (!url) url = window.location.href; name = name.replace(/[\\[\\]]/g, \"\\\\$&\"); var regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\\+/g, \" \"));} qs.js库解析query string qs.js支持解析 query string,也支持序列化query string,而且qs支持解析query string中的Object和Aarry,前提是按照qs的支持的格式书写。 parse 12var obj = qs.parse('a=c');assert.deepEqual(obj, { a: 'c' }); stringifying 12var str = qs.stringify(obj);assert.equal(str, 'a=c'); Java解析编码过的字符串1234567891011121314public static Map<String, List<String>> splitQuery(URL url, String charset) throws UnsupportedEncodingException { final Map<String, List<String>> queryPairs = new LinkedHashMap<String, List<String>>(); final String[] pairs = url.getQuery().split(\"&\"); for (String pair : pairs) { final int idx = pair.indexOf(\"=\"); final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), charset) : pair; if (!queryPairs.containsKey(key)) { queryPairs.put(key, new LinkedList<String>()); } final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), \"UTF-8\") : null; queryPairs.get(key).add(value); } return queryPairs;} JDK 8 version 123456789101112131415public Map<String, List<String>> splitQuery(URL url) { if (Strings.isNullOrEmpty(url.getQuery())) { return Collections.emptyMap(); } return Arrays.stream(url.getQuery().split(\"&\")) .map(this::splitQueryParameter) .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList())));}public SimpleImmutableEntry<String, String> splitQueryParameter(String it) { final int idx = it.indexOf(\"=\"); final String key = idx > 0 ? it.substring(0, idx) : it; final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null; return new SimpleImmutableEntry<>(key, value);} common-httpclients.jar 123456789import org.apache.hc.client5.http.utils.URLEncodedUtilspublic static void main(String[] args) { String url = \"http://www.example.com/something.html?one=1&two=2&three=3&three=3a\"; List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), \"UTF-8\"); for (NameValuePair param : params) { System.out.println(param.getName() + \" : \" + param.getValue()); }} Spring Framework 123456789public static void main(String[] args) { String uri = \"http://youhost.com/test?param1=abc&param2=def&param2=ghi\"; MultiValueMap<String, String> parameters = UriComponentsBuilder.fromUriString(uri).build().getQueryParams(); List<String> param1 = parameters.get(\"param1\"); List<String> param2 = parameters.get(\"param2\"); System.out.println(\"param1: \" + param1.get(0)); System.out.println(\"param2: \" + param2.get(0) + \",\" + param2.get(1));} 参考链接Parse a URI String into Name-Value Collection","tags":[{"name":"qs","slug":"qs","permalink":"https://betgar.github.io/tags/qs/"}]},{"title":"在Oracle和Java中使用Base64","date":"2018-03-13T12:00:00.000Z","path":"2018/03/13/how-to-use-base64-between-oracle-and-java/","text":"在Oracle和Java中使用Base64Oracle UTL_RAW:处理二进制的数据包。 UTL_ENCODE:处理编码包。 UTL_ENCODE.BASE64_ENCODE使用 步骤: 先用UTL_RAW.CAST_TO_RAW把varchar2转换为raw类型. 使用UTL_ENCODE.BASE64_ENCODE编码. 使用UTL_RAW.CAST_TO_VARCHAR2从raw(二进制类型)转换为varchar2类型转换. 注意: raw类型也可以进行传递,但是Java中要使用Byte进行解析。 12select utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw('{\"count\": 1000}'))) from dual; UTL_ENCODE.BASE64_DECODE使用 1select utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw('e1wiY291bnRcIjogMTAwMH0='))) from dual; 指定字符编码 12UTL_ENCODE.TEXT_ENCODE(string, charset);UTL_ENCODE.TEXT_DECODE(string, charset); Java BASE64Decoder BASE64Encoder EncodeUtil:工具类 BASE64Encoder使用 123456789101112public static String encodeString(String plainText, String charSet) { BASE64Encoder encoder = new BASE64Encoder(); try { return encoder.encode(plainText.getBytes(charSet)); } catch(UnsupportedEncodingException e) { throw new EncodeException(\"E0208.0013\", EcpI18nUtil.localizedString(\"E0208.0013\", \"\\u4E0D\\u652F\\u6301\\u7684\\u5B57\\u7B26\\u96C6\"), e, new Object[0]); } } BASE64Decoder使用 123456789101112public static String decodeString(String encodedText, String charSet) { BASE64Decoder decoder = new BASE64Decoder(); try { return new String(decoder.decodeBuffer(encodedText), charSet); } catch(Exception e) { throw new EncodeException(\"E0208.0013\", EcpI18nUtil.localizedString(\"E0208.0013\", \"\\u89E3\\u7801\\u5931\\u8D25\"), e, new Object[0]); } } Java中byte和String相互转换 123456// String -> byteString str = \"count\";byte[] bytes = str.getBytes(\"GBK\");// byte -> StringString str2 = new String(bytes, 'GBK'); 字符集 编码和解码都可以指定字符集。如果字符串中有中文 编码之后解码会出现乱码,因为Ecp工具类默认使用GBK字符集编码和解码。 注意 如果希望服务端Java编码之后可以在数据库过程中进行解码(反之亦然),请不要在关键业务数据中出现中文 。","tags":[{"name":"Base64","slug":"Base64","permalink":"https://betgar.github.io/tags/Base64/"}]},{"title":"jQuery cookie插件使用例子","date":"2018-02-26T12:00:00.000Z","path":"2018/02/26/jquery-cookie-plug-api/","text":"jQuery cookie插件使用例子 记录一下jQuery-cookie的使用方法。 使用 jQuery.cookie使用非常的简单,只有一个构造函数。 构造函数1234567891011121314/* @param {string} name - cookie键. * @param {string} value - cookie值. * @param {Object} options - cookie的控制参数 * @param {Number|Date} options.expires * 期望什么时候cookie过期,从当前时间计算(浏览器时间)。 * 如果expires为null或者被忽略,则cookie在当前session有效,退出浏览器失效。 * * @param {string} options.path - 设置cookie的页面路径;默认设置页面。 * @param {string} options.domain - cookie的域地址 (default: 默认创建cookie页面). * @param {Boolean} options.secure - 设置给cookie之后,传递cookie给服务端 * 需要使用安全协议(例如:https) * @type undefined */$.cookie('the_cookie', 'the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true}); 创建cookie 设置cookie 12// Set the value of a cookie.$.cookie('the_cookie', 'the_value'); 使用options 12// Create a cookie with all available options.$.cookie('the_cookie', 'the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true}); 删除cookie 12345// Delete a cookie by passing null as value.$.cookie('the_cookie', null);// 注意版本$.removeCookie('the_cookie', options); 读取cookie 获取cookie 1$.cookie('the_cookie'); source-code123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117/*! * jQuery Cookie Plugin v1.4.1 * https://github.com/carhartl/jquery-cookie * * Copyright 2013 Klaus Hartl * Released under the MIT license */(function (factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS factory(require('jquery')); } else { // Browser globals factory(jQuery); }}(function ($) { var pluses = /\\+/g; function encode(s) { return config.raw ? s : encodeURIComponent(s); } function decode(s) { return config.raw ? s : decodeURIComponent(s); } function stringifyCookieValue(value) { return encode(config.json ? JSON.stringify(value) : String(value)); } function parseCookieValue(s) { if (s.indexOf('\"') === 0) { // This is a quoted cookie as according to RFC2068, unescape... s = s.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, '\\\\'); } try { // Replace server-side written pluses with spaces. // If we can't decode the cookie, ignore it, it's unusable. // If we can't parse the cookie, ignore it, it's unusable. s = decodeURIComponent(s.replace(pluses, ' ')); return config.json ? JSON.parse(s) : s; } catch(e) {} } function read(s, converter) { var value = config.raw ? s : parseCookieValue(s); return $.isFunction(converter) ? converter(value) : value; } var config = $.cookie = function (key, value, options) { // Write if (value !== undefined && !$.isFunction(value)) { options = $.extend({}, config.defaults, options); if (typeof options.expires === 'number') { var days = options.expires, t = options.expires = new Date(); t.setTime(+t + days * 864e+5); } return (document.cookie = [ encode(key), '=', stringifyCookieValue(value), options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', options.secure ? '; secure' : '' ].join('')); } // Read var result = key ? undefined : {}; // To prevent the for loop in the first place assign an empty array // in case there are no cookies at all. Also prevents odd result when // calling $.cookie(). var cookies = document.cookie ? document.cookie.split('; ') : []; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); var name = decode(parts.shift()); var cookie = parts.join('='); if (key && key === name) { // If second argument (value) is a function it's a converter... result = read(cookie, value); break; } // Prevent storing a cookie that we couldn't decode. if (!key && (cookie = read(cookie)) !== undefined) { result[name] = cookie; } } return result; }; config.defaults = {}; $.removeCookie = function (key, options) { if ($.cookie(key) === undefined) { return false; } // Must not alter options, thus extending a fresh object... $.cookie(key, '', $.extend({}, options, { expires: -1 })); return !$.cookie(key); };}));","tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://betgar.github.io/tags/jQuery/"},{"name":"cookie","slug":"cookie","permalink":"https://betgar.github.io/tags/cookie/"}]},{"title":"SQL style guide","date":"2018-02-26T12:00:00.000Z","path":"2018/02/26/sqlstyle.guide/","text":"SQL style guideOverviewYou can use this set of guidelines, fork them or make your own - thekey here is that you pick a style and stick to it. To suggest changesor fix bugs please open an issue or pull request on GitHub. These guidelines are designed to be compatible with Joe Celko’s SQL ProgrammingStyle book to make adoption for teams who have already read that bookeasier. This guide is a little more opinionated in some areas and in others alittle more relaxed. It is certainly more succinct where Celko’s bookcontains anecdotes and reasoning behind each rule as thoughtful prose. It is easy to include this guide in Markdown format as a part of aproject’s code base or reference it here for anyone on the project to freelyread—much harder with a physical book. SQL style guide by Simon Holywell is licensed under a Creative CommonsAttribution-ShareAlike 4.0 International License.Based on a work at http://www.sqlstyle.guide. GeneralDo Use consistent and descriptive identifiers and names. Make judicious use of white space and indentation to make code easier to read. Store ISO-8601 compliant time and date information(YYYY-MM-DD HH:MM:SS.SSSSS). Try to use only standard SQL functions instead of vendor specific functions forreasons of portability. Keep code succinct and devoid of redundant SQL—such as unnecessary quoting orparentheses or WHERE clauses that can otherwise be derived. Include comments in SQL code where necessary. Use the C style opening /* andclosing */ where possible otherwise precede comments with -- and finishthem with a new line. 123SELECT file_hash -- stored ssdeep hash FROM file_system WHERE file_name = '.vimrc'; 12345/* Updating the file record after writing to the file */UPDATE file_system SET file_modified_date = '1980-02-22 13:19:01.00000', file_size = 209732 WHERE file_name = '.vimrc'; Avoid CamelCase—it is difficult to scan quickly. Descriptive prefixes or Hungarian notation such as sp_ or tbl. Plurals—use the more natural collective term where possible instead. For examplestaff instead of employees or people instead of individuals. Quoted identifiers—if you must use them then stick to SQL92 double quotes forportability (you may need to configure your SQL server to support this dependingon vendor). Object oriented design principles should not be applied to SQL or databasestructures. Naming conventionsGeneral Ensure the name is unique and does not exist as areserved keyword. Keep the length to a maximum of 30 bytes—in practice this is 30 charactersunless you are using multi-byte character set. Names must begin with a letter and may not end with an underscore. Only use letters, numbers and underscores in names. Avoid the use of multiple consecutive underscores—these can be hard to read. Use underscores where you would naturally include a space in the name (firstname becomes first_name). Avoid abbreviations and if you have to use them make sure they are commonlyunderstood. 12SELECT first_name FROM staff; Tables Use a collective name or, less ideally, a plural form. For example (in order ofpreference) staff and employees. Do not prefix with tbl or any other such descriptive prefix or Hungariannotation. Never give a table the same name as one of its columns and vice versa. Avoid, where possible, concatenating two table names together to create the nameof a relationship table. Rather than cars_mechanics prefer services. Columns Always use the singular name. Where possible avoid simply using id as the primary identifier for the table. Do not add a column with the same name as its table and vice versa. Always use lowercase except where it may make sense not to such as proper nouns. Aliasing or correlations Should relate in some way to the object or expression they are aliasing. As a rule of thumb the correlation name should be the first letter of each wordin the object’s name. If there is already a correlation with the same name then append a number. Always include the AS keyword—makes it easier to read as it is explicit. For computed data (SUM() or AVG()) use the name you would give it were ita column defined in the schema. 1234SELECT first_name AS fn FROM staff AS s1 JOIN students AS s2 ON s2.mentor_id = s1.staff_num; 12SELECT SUM(s.monitor_tally) AS monitor_total FROM staff AS s; Stored procedures The name must contain a verb. Do not prefix with sp_ or any other such descriptive prefix or Hungariannotation. Uniform suffixesThe following suffixes have a universal meaning ensuring the columns can be readand understood easily from SQL code. Use the correct suffix where appropriate. _id—a unique identifier such as a column that is a primary key. _status—flag value or some other status of any type such aspublication_status. _total—the total or sum of a collection of values. _num—denotes the field contains any kind of number. _name—signifies a name such as first_name. _seq—contains a contiguous sequence of values. _date—denotes a column that contains the date of something. _tally—a count. _size—the size of something such as a file size or clothing. _addr—an address for the record could be physical or intangible such asip_addr. Query syntaxReserved wordsAlways use uppercase for the reserved keywordslike SELECT and WHERE. It is best to avoid the abbreviated keywords and use the full length ones whereavailable (prefer ABSOLUTE to ABS). Do not use database server specific keywords where an ANSI SQL keyword alreadyexists performing the same function. This helps to make code more portable. 123SELECT model_num FROM phones AS p WHERE p.release_date > '2014-09-30'; White spaceTo make the code easier to read it is important that the correct compliment ofspacing is used. Do not crowd code or remove natural language spaces. SpacesSpaces should be used to line up the code so that the root keywords all end onthe same character boundary. This forms a river down the middle making it easy forthe readers eye to scan over the code and separate the keywords from theimplementation detail. Rivers are bad in typography, but helpful here. 12345SELECT f.average_height, f.average_diameter FROM flora AS f WHERE f.species_name = 'Banksia' OR f.species_name = 'Sheoak' OR f.species_name = 'Wattle'; Notice that SELECT, FROM, etc. are all right aligned while the actual columnnames and implementation specific details are left aligned. Although not exhaustive always include spaces: before and after equals (=) after commas (,) surrounding apostrophes (') where not within parentheses or with a trailingcomma or semicolon. 1234SELECT a.title, a.release_date, a.recording_date FROM albums AS a WHERE a.title = 'Charcoal Lane' OR a.title = 'The New Danger'; Line spacingAlways include newlines/vertical space: before AND or OR after semicolons to separate queries for easier reading after each keyword definition after a comma when separating multiple columns into logical groups to separate code into related sections, which helps to ease the readability oflarge chunks of code. Keeping all the keywords aligned to the righthand side and the values left alignedcreates a uniform gap down the middle of query. It makes it much easier to scanthe query definition over quickly too. 123INSERT INTO albums (title, release_date, recording_date)VALUES ('Charcoal Lane', '1990-01-01 01:01:01.00000', '1990-01-01 01:01:01.00000'), ('The New Danger', '2008-01-01 01:01:01.00000', '1990-01-01 01:01:01.00000'); 123UPDATE albums SET release_date = '1990-01-01 01:01:01.00000' WHERE title = 'The New Danger'; 12345SELECT a.title, a.release_date, a.recording_date, a.production_date -- grouped dates together FROM albums AS a WHERE a.title = 'Charcoal Lane' OR a.title = 'The New Danger'; IndentationTo ensure that SQL is readable it is important that standards of indentationare followed. JoinsJoins should be indented to the other side of the river and grouped with a newline where necessary. 123456789SELECT r.last_name FROM riders AS r INNER JOIN bikes AS b ON r.bike_vin_num = b.vin_num AND b.engines > 2 INNER JOIN crew AS c ON r.crew_chief_last_name = c.last_name AND c.chief = 'Y'; SubqueriesSubqueries should also be aligned to the right side of the river and then laidout using the same style as any other query. Sometimes it will make sense to havethe closing parenthesis on a new line at the same character position as it’sopening partner—this is especially true where you have nested subqueries. 1234567891011SELECT r.last_name, (SELECT MAX(YEAR(championship_date)) FROM champions AS c WHERE c.last_name = r.last_name AND c.confirmed = 'Y') AS last_championship_year FROM riders AS r WHERE r.last_name IN (SELECT c.last_name FROM champions AS c WHERE YEAR(championship_date) > '2008' AND c.confirmed = 'Y'); Preferred formalisms Make use of BETWEEN where possible instead of combining multiple statementswith AND. Similarly use IN() instead of multiple OR clauses. Where a value needs to be interpreted before leaving the database use the CASEexpression. CASE statements can be nested to form more complex logical structures. Avoid the use of UNION clauses and temporary tables where possible. If theschema can be optimised to remove the reliance on these features then it mostlikely should be. 12345678SELECT CASE postcode WHEN 'BN1' THEN 'Brighton' WHEN 'EH1' THEN 'Edinburgh' END AS city FROM office_locations WHERE country = 'United Kingdom' AND opening_time BETWEEN 8 AND 9 AND postcode IN ('EH1', 'BN1', 'NN1', 'KW1') Create syntaxWhen declaring schema information it is also important to maintain humanreadable code. To facilitate this ensure the column definitions are ordered andgrouped where it makes sense to do so. Indent column definitions by four (4) spaces within the CREATE definition. Choosing data types Where possible do not use vendor specific data types—these are not portable andmay not be available in older versions of the same vendor’s software. Only use REAL or FLOAT types where it is strictly necessary for floatingpoint mathematics otherwise prefer NUMERIC and DECIMAL at all times. Floatingpoint rounding errors are a nuisance! Specifying default values The default value must be the same type as the column—if a column is declareda DECIMAL do not provide an INTEGER default value. Default values must follow the data type declaration and come before anyNOT NULL statement. Constraints and keysConstraints and their subset, keys, are a very important component of anydatabase definition. They can quickly become very difficult to read and reasonabout though so it is important that a standard set of guidelines are followed. Choosing keysDeciding the column(s) that will form the keys in the definition should be acarefully considered activity as it will effect performance and data integrity. The key should be unique to some degree. Consistency in terms of data type for the value across the schema and a lowerlikelihood of this changing in the future. Can the value be validated against a standard format (such as one published byISO)? Encouraging conformity to point 2. Keeping the key as simple as possible whilst not being scared to use compoundkeys where necessary. It is a reasoned and considered balancing act to be performed at the definitionof a database. Should requirements evolve in the future it is possible to makechanges to the definitions to keep them up to date. Defining constraintsOnce the keys are decided it is possible to define them in the system usingconstraints along with field value validation. General Tables must have at least one key to be complete and useful. Constraints should be given a custom name excepting UNIQUE, PRIMARY KEYand FOREIGN KEY where the database vendor will generally supply sufficientlyintelligible names automatically. Layout and order Specify the primary key first right after the CREATE TABLE statement. Constraints should be defined directly beneath the column they correspond to.Indent the constraint so that it aligns to the right of the column name. If it is a multi-column constraint then consider putting it as close to bothcolumn definitions as possible and where this is difficult as a last resortinclude them at the end of the CREATE TABLE definition. If it is a table level constraint that applies to the entire table then itshould also appear at the end. Use alphabetical order where ON DELETE comes before ON UPDATE. If it make senses to do so align each aspect of the query on the same characterposition. For example all NOT NULL definitions could start at the samecharacter position. This is not hard and fast, but it certainly makes the codemuch easier to scan and read. Validation Use LIKE and SIMILAR TO constraints to ensure the integrity of stringswhere the format is known. Where the ultimate range of a numerical value is known it must be written as arange CHECK() to prevent incorrect values entering the database or the silenttruncation of data too large to fit the column definition. In the least itshould check that the value is greater than zero in most cases. CHECK() constraints should be kept in separate clauses to ease debugging. Example12345678CREATE TABLE staff ( PRIMARY KEY (staff_num), staff_num INT(5) NOT NULL, first_name VARCHAR(100) NOT NULL, pens_in_drawer INT(2) NOT NULL, CONSTRAINT pens_in_drawer_range CHECK(pens_in_drawer >= 1 AND pens_in_drawer < 100)); Designs to avoid Object oriented design principles do not effectively translate to relationaldatabase designs—avoid this pitfall. Placing the value in one column and the units in another column. The columnshould make the units self evident to prevent the requirement to combinecolumns again later in the application. Use CHECK() to ensure valid data isinserted into the column. EAV (Entity Attribute Value) tables—use a specialist product intended forhandling such schema-less data instead. Splitting up data that should be in one table across many because of arbitraryconcerns such as time-based archiving or location in a multi-nationalorganisation. Later queries must then work across multiple tables with UNIONrather than just simply querying one table. AppendixReserved keyword referenceA list of ANSI SQL (92, 99 and 2003), MySQL 3 to 5.x, PostgreSQL 8.1, MS SQL Server 2000, MS ODBC and Oracle 10.2 reserved keywords. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825AABORTABSABSOLUTEACCESSACTIONADAADDADMINAFTERAGGREGATEALIASALLALLOCATEALSOALTERALWAYSANALYSEANALYZEANDANYAREARRAYASASCASENSITIVEASSERTIONASSIGNMENTASYMMETRICATATOMICATTRIBUTEATTRIBUTESAUDITAUTHORIZATIONAUTO_INCREMENTAVGAVG_ROW_LENGTHBACKUPBACKWARDBEFOREBEGINBERNOULLIBETWEENBIGINTBINARYBITBIT_LENGTHBITVARBLOBBOOLBOOLEANBOTHBREADTHBREAKBROWSEBULKBYCCACHECALLCALLEDCARDINALITYCASCADECASCADEDCASECASTCATALOGCATALOG_NAMECEILCEILINGCHAINCHANGECHARCHAR_LENGTHCHARACTERCHARACTER_LENGTHCHARACTER_SET_CATALOGCHARACTER_SET_NAMECHARACTER_SET_SCHEMACHARACTERISTICSCHARACTERSCHECKCHECKEDCHECKPOINTCHECKSUMCLASSCLASS_ORIGINCLOBCLOSECLUSTERCLUSTEREDCOALESCECOBOLCOLLATECOLLATIONCOLLATION_CATALOGCOLLATION_NAMECOLLATION_SCHEMACOLLECTCOLUMNCOLUMN_NAMECOLUMNSCOMMAND_FUNCTIONCOMMAND_FUNCTION_CODECOMMENTCOMMITCOMMITTEDCOMPLETIONCOMPRESSCOMPUTECONDITIONCONDITION_NUMBERCONNECTCONNECTIONCONNECTION_NAMECONSTRAINTCONSTRAINT_CATALOGCONSTRAINT_NAMECONSTRAINT_SCHEMACONSTRAINTSCONSTRUCTORCONTAINSCONTAINSTABLECONTINUECONVERSIONCONVERTCOPYCORRCORRESPONDINGCOUNTCOVAR_POPCOVAR_SAMPCREATECREATEDBCREATEROLECREATEUSERCROSSCSVCUBECUME_DISTCURRENTCURRENT_DATECURRENT_DEFAULT_TRANSFORM_GROUPCURRENT_PATHCURRENT_ROLECURRENT_TIMECURRENT_TIMESTAMPCURRENT_TRANSFORM_GROUP_FOR_TYPECURRENT_USERCURSORCURSOR_NAMECYCLEDATADATABASEDATABASESDATEDATETIMEDATETIME_INTERVAL_CODEDATETIME_INTERVAL_PRECISIONDAYDAY_HOURDAY_MICROSECONDDAY_MINUTEDAY_SECONDDAYOFMONTHDAYOFWEEKDAYOFYEARDBCCDEALLOCATEDECDECIMALDECLAREDEFAULTDEFAULTSDEFERRABLEDEFERREDDEFINEDDEFINERDEGREEDELAY_KEY_WRITEDELAYEDDELETEDELIMITERDELIMITERSDENSE_RANKDENYDEPTHDEREFDERIVEDDESCDESCRIBEDESCRIPTORDESTROYDESTRUCTORDETERMINISTICDIAGNOSTICSDICTIONARYDISABLEDISCONNECTDISKDISPATCHDISTINCTDISTINCTROWDISTRIBUTEDDIVDODOMAINDOUBLEDROPDUALDUMMYDUMPDYNAMICDYNAMIC_FUNCTIONDYNAMIC_FUNCTION_CODEEACHELEMENTELSEELSEIFENABLEENCLOSEDENCODINGENCRYPTEDENDEND-EXECENUMEQUALSERRLVLESCAPEESCAPEDEVERYEXCEPTEXCEPTIONEXCLUDEEXCLUDINGEXCLUSIVEEXECEXECUTEEXISTINGEXISTSEXITEXPEXPLAINEXTERNALEXTRACTFALSEFETCHFIELDSFILEFILLFACTORFILTERFINALFIRSTFLOATFLOAT4FLOAT8FLOORFLUSHFOLLOWINGFORFORCEFOREIGNFORTRANFORWARDFOUNDFREEFREETEXTFREETEXTTABLEFREEZEFROMFULLFULLTEXTFUNCTIONFUSIONGGENERALGENERATEDGETGLOBALGOGOTOGRANTGRANTEDGRANTSGREATESTGROUPGROUPINGHANDLERHAVINGHEADERHEAPHIERARCHYHIGH_PRIORITYHOLDHOLDLOCKHOSTHOSTSHOURHOUR_MICROSECONDHOUR_MINUTEHOUR_SECONDIDENTIFIEDIDENTITYIDENTITY_INSERTIDENTITYCOLIFIGNOREILIKEIMMEDIATEIMMUTABLEIMPLEMENTATIONIMPLICITININCLUDEINCLUDINGINCREMENTINDEXINDICATORINFILEINFIXINHERITINHERITSINITIALINITIALIZEINITIALLYINNERINOUTINPUTINSENSITIVEINSERTINSERT_IDINSTANCEINSTANTIABLEINSTEADINTINT1INT2INT3INT4INT8INTEGERINTERSECTINTERSECTIONINTERVALINTOINVOKERISISAMISNULLISOLATIONITERATEJOINKKEYKEY_MEMBERKEY_TYPEKEYSKILLLANCOMPILERLANGUAGELARGELASTLAST_INSERT_IDLATERALLEADINGLEASTLEAVELEFTLENGTHLESSLEVELLIKELIMITLINENOLINESLISTENLNLOADLOCALLOCALTIMELOCALTIMESTAMPLOCATIONLOCATORLOCKLOGINLOGSLONGLONGBLOBLONGTEXTLOOPLOW_PRIORITYLOWERMMAPMATCHMATCHEDMAXMAX_ROWSMAXEXTENTSMAXVALUEMEDIUMBLOBMEDIUMINTMEDIUMTEXTMEMBERMERGEMESSAGE_LENGTHMESSAGE_OCTET_LENGTHMESSAGE_TEXTMETHODMIDDLEINTMINMIN_ROWSMINUSMINUTEMINUTE_MICROSECONDMINUTE_SECONDMINVALUEMLSLABELMODMODEMODIFIESMODIFYMODULEMONTHMONTHNAMEMOREMOVEMULTISETMUMPSMYISAMNAMENAMESNATIONALNATURALNCHARNCLOBNESTINGNEWNEXTNONO_WRITE_TO_BINLOGNOAUDITNOCHECKNOCOMPRESSNOCREATEDBNOCREATEROLENOCREATEUSERNOINHERITNOLOGINNONCLUSTEREDNONENORMALIZENORMALIZEDNOSUPERUSERNOTNOTHINGNOTIFYNOTNULLNOWAITNULLNULLABLENULLIFNULLSNUMBERNUMERICOBJECTOCTET_LENGTHOCTETSOFOFFOFFLINEOFFSETOFFSETSOIDSOLDONONLINEONLYOPENOPENDATASOURCEOPENQUERYOPENROWSETOPENXMLOPERATIONOPERATOROPTIMIZEOPTIONOPTIONALLYOPTIONSORORDERORDERINGORDINALITYOTHERSOUTOUTEROUTFILEOUTPUTOVEROVERLAPSOVERLAYOVERRIDINGOWNERPACK_KEYSPADPARAMETERPARAMETER_MODEPARAMETER_NAMEPARAMETER_ORDINAL_POSITIONPARAMETER_SPECIFIC_CATALOGPARAMETER_SPECIFIC_NAMEPARAMETER_SPECIFIC_SCHEMAPARAMETERSPARTIALPARTITIONPASCALPASSWORDPATHPCTFREEPERCENTPERCENT_RANKPERCENTILE_CONTPERCENTILE_DISCPLACINGPLANPLIPOSITIONPOSTFIXPOWERPRECEDINGPRECISIONPREFIXPREORDERPREPAREPREPAREDPRESERVEPRIMARYPRINTPRIORPRIVILEGESPROCPROCEDURALPROCEDUREPROCESSPROCESSLISTPUBLICPURGEQUOTERAID0RAISERRORRANGERANKRAWREADREADSREADTEXTREALRECHECKRECONFIGURERECURSIVEREFREFERENCESREFERENCINGREGEXPREGR_AVGXREGR_AVGYREGR_COUNTREGR_INTERCEPTREGR_R2REGR_SLOPEREGR_SXXREGR_SXYREGR_SYYREINDEXRELATIVERELEASERELOADRENAMEREPEATREPEATABLEREPLACEREPLICATIONREQUIRERESETRESIGNALRESOURCERESTARTRESTORERESTRICTRESULTRETURNRETURNED_CARDINALITYRETURNED_LENGTHRETURNED_OCTET_LENGTHRETURNED_SQLSTATERETURNSREVOKERIGHTRLIKEROLEROLLBACKROLLUPROUTINEROUTINE_CATALOGROUTINE_NAMEROUTINE_SCHEMAROWROW_COUNTROW_NUMBERROWCOUNTROWGUIDCOLROWIDROWNUMROWSRULESAVESAVEPOINTSCALESCHEMASCHEMA_NAMESCHEMASSCOPESCOPE_CATALOGSCOPE_NAMESCOPE_SCHEMASCROLLSEARCHSECONDSECOND_MICROSECONDSECTIONSECURITYSELECTSELFSENSITIVESEPARATORSEQUENCESERIALIZABLESERVER_NAMESESSIONSESSION_USERSETSETOFSETSSETUSERSHARESHOWSHUTDOWNSIGNALSIMILARSIMPLESIZESMALLINTSOMESONAMESOURCESPACESPATIALSPECIFICSPECIFIC_NAMESPECIFICTYPESQLSQL_BIG_RESULTSQL_BIG_SELECTSSQL_BIG_TABLESSQL_CALC_FOUND_ROWSSQL_LOG_OFFSQL_LOG_UPDATESQL_LOW_PRIORITY_UPDATESSQL_SELECT_LIMITSQL_SMALL_RESULTSQL_WARNINGSSQLCASQLCODESQLERRORSQLEXCEPTIONSQLSTATESQLWARNINGSQRTSSLSTABLESTARTSTARTINGSTATESTATEMENTSTATICSTATISTICSSTATUSSTDDEV_POPSTDDEV_SAMPSTDINSTDOUTSTORAGESTRAIGHT_JOINSTRICTSTRINGSTRUCTURESTYLESUBCLASS_ORIGINSUBLISTSUBMULTISETSUBSTRINGSUCCESSFULSUMSUPERUSERSYMMETRICSYNONYMSYSDATESYSIDSYSTEMSYSTEM_USERTABLETABLE_NAMETABLESTABLESAMPLETABLESPACETEMPTEMPLATETEMPORARYTERMINATETERMINATEDTEXTTEXTSIZETHANTHENTIESTIMETIMESTAMPTIMEZONE_HOURTIMEZONE_MINUTETINYBLOBTINYINTTINYTEXTTOTOASTTOPTOP_LEVEL_COUNTTRAILINGTRANTRANSACTIONTRANSACTION_ACTIVETRANSACTIONS_COMMITTEDTRANSACTIONS_ROLLED_BACKTRANSFORMTRANSFORMSTRANSLATETRANSLATIONTREATTRIGGERTRIGGER_CATALOGTRIGGER_NAMETRIGGER_SCHEMATRIMTRUETRUNCATETRUSTEDTSEQUALTYPEUESCAPEUIDUNBOUNDEDUNCOMMITTEDUNDERUNDOUNENCRYPTEDUNIONUNIQUEUNKNOWNUNLISTENUNLOCKUNNAMEDUNNESTUNSIGNEDUNTILUPDATEUPDATETEXTUPPERUSAGEUSEUSERUSER_DEFINED_TYPE_CATALOGUSER_DEFINED_TYPE_CODEUSER_DEFINED_TYPE_NAMEUSER_DEFINED_TYPE_SCHEMAUSINGUTC_DATEUTC_TIMEUTC_TIMESTAMPVACUUMVALIDVALIDATEVALIDATORVALUEVALUESVAR_POPVAR_SAMPVARBINARYVARCHARVARCHAR2VARCHARACTERVARIABLEVARIABLESVARYINGVERBOSEVIEWVOLATILEWAITFORWHENWHENEVERWHEREWHILEWIDTH_BUCKETWINDOWWITHWITHINWITHOUTWORKWRITEWRITETEXTX509XORYEARYEAR_MONTHZEROFILLZONE","tags":[{"name":"SQL","slug":"SQL","permalink":"https://betgar.github.io/tags/SQL/"}]},{"title":"NPM的正确使用姿势","date":"2018-01-08T12:00:00.000Z","path":"2018/01/08/how-to-use-utldtree/","text":"utldtree utldtree.sql是Oracle 提供的一个分析依赖的sql文件。 路径:%ORACLE_CLIENT%\\RDBMS\\ADMIN 因为没有包含在数据库标准包中,所以需要自己手动在数据库中执行utldtree下。 utldtree创建的objects: 序列:deptree_seq 12> create sequence deptree_seq cache 200> 表:deptree_temptab 12345678> create table deptree_temptab> (> object_id number,> referenced_object_id number,> nest_level number,> seq# number > )> 视图:deptree,ideptree 过程:deptree_fill 12> create or replace procedure deptree_fill (type char, schema char, name char)> 生成依赖关系步骤生成依赖关系12345678-- 1.生成依赖关系-- 说明:每一次调用会“清空”表DEPTREE_TEMPTAB-- 参数说明:-- type:对象类型(table,view,procedure...)-- schema:当前用户名称-- name:对象名称(待查询的被人依赖的对象)CALL DEPTREE_FILL('PROCEDURE', 'EXCEL', 'CONCAT'); 查询依赖关系结果112345-- 2. 查询依赖关系结果-- 说明:-- 这个视图附带嵌套层次SELECT * FROM DEPTREE D ORDER BY D.SEQ#; 查询依赖关系结果212345-- 3. 查询依赖关系结果-- 说明:-- 步骤:3和2一样,只是展示方式不同SELECT * FROM IDEPTREE; 动态语句中的object依赖无法分析 动态的SQL语句中的依赖关系是无法分析出来。所以建议少用动态语句。 动态语句解决方法:ASK TOM上提供了间接的方法。 12345678910111213141516SQL> 1 CREATE or replace PROCEDURE testproc IS 2 i PLS_INTEGER; 3 var_table varchar2(7):='TESTTAB'; -- 创建一个间接依赖,但是在过程中没有使用。4 cursor dummy is select * from TESTTAB; 5 BEGIN 6 execute immediate 'SELECT COUNT(*) FROM '||var_table||'' INTO i; 7 dbms_output.put_line(TO_CHAR(i)); 8* END testproc; 9 / Procedure created. Elapsed: 00:00:00.09 SQL> exec dbms_utility.get_dependency('TABLE', 'DWHADMIN','TESTTAB'); 引用how to find the dependencies of table?","tags":[{"name":"PLSQL","slug":"PLSQL","permalink":"https://betgar.github.io/tags/PLSQL/"},{"name":"utldtree","slug":"utldtree","permalink":"https://betgar.github.io/tags/utldtree/"}]},{"title":"Oracle 管理用户对象依赖关系","date":"2018-01-08T12:00:00.000Z","path":"2018/01/08/how-to-find-schema-object-dependencies/","text":"Oracle 管理用户对象依赖关系 背景: 项目组使用很多的procedure作为取数接口,现在需要重构底层取数过程,但是无法找出“底层取数procedure”被哪些“逻辑对象”(procedure, function…etc.)依赖。无法评估影响的范围,Java服务端可以通过调用层次分析出,但是直接在数据库端,进行调用时就很难分析。 通过查找官方文档,找到两个方法,现在分享给大家。 方法: utldtree.sql DBMS_UTILITY.get_dependency(type, schema, object_name); utldtree utldtree.sql是Oracle 提供的一个分析依赖的sql文件。 路径:%ORACLE_CLIENT%\\RDBMS\\ADMIN 因为没有包含在数据库标准包中,所以需要自己手动在数据库中执行utldtree下。 utldtree创建的objects: 序列:deptree_seq 12> create sequence deptree_seq cache 200> 表:deptree_temptab 12345678> create table deptree_temptab> (> object_id number,> referenced_object_id number,> nest_level number,> seq# number > )> 视图:deptree,ideptree 过程:deptree_fill 12> create or replace procedure deptree_fill (type char, schema char, name char)> utldtree生成依赖关系步骤生成依赖关系12345678-- 1.生成依赖关系-- 说明:每一次调用会“清空”表DEPTREE_TEMPTAB-- 参数说明:-- type:对象类型(table,view,procedure...)-- schema:当前用户名称-- name:对象名称(待查询的被人依赖的对象)CALL DEPTREE_FILL('PROCEDURE', 'FMIS0200', 'GET_ZWCS_DATA_KM'); 查询依赖关系结果112345-- 2. 查询依赖关系结果-- 说明:-- 这个视图附带嵌套层次SELECT * FROM DEPTREE D ORDER BY D.SEQ#; 查询依赖关系结果212345-- 3. 查询依赖关系结果-- 说明:-- 步骤:3和2一样,只是展示方式不同SELECT * FROM IDEPTREE; DBMS_UTILITY.get_dependency oracle 11g R1开始提供管理object dependencies关系. 12345678910111213-- sql window 执行sql语句-- 参数说明:-- type:对象类型-- schema:模式名称(用户名称)-- name:object的对象名称call dbms_utility.get_dependency('PROCEDURE', 'LALAL', 'TEST');-- output tab页面查看结果/*DEPENDENCIES ON LALAL.TEST------------------------------------------------------------------...*/ 动态语句中的object依赖无法分析 动态的SQL语句中的依赖关系是无法分析出来。所以建议少用动态语句。 动态语句解决方法:ASK TOM上提供了间接的方法。 在创建procedure或者function中在declare部分,创建一个显示依赖。 1234567891011121314 CREATE or replace PROCEDURE testproc IS i PLS_INTEGER; var_table varchar2(7):='TESTTAB'; -- 创建一个“直接”依赖,但是在过程中没有使用dummy变量。 cursor dummy is select * from TESTTAB; BEGIN -- 动态语句依赖的object(table对象) execute immediate 'SELECT COUNT(*) FROM '||var_table||'' INTO i; dbms_output.put_line(TO_CHAR(i)); END testproc; / -- 查询依赖exec dbms_utility.get_dependency('TABLE', 'DWHADMIN','TESTTAB'); 虽然“动态语句依赖”在依赖关系中无法分析,但是在procedure的declare部分显示依赖了TESTTAB,所以使用Oracle的依赖管理还是可以找到“依赖关系”。 12345> -- 声明“游标”,oracle是只会分配“游标的储存空间”> -- 不会真正执行sql语句取数,如果你怕影响性能,可以加上where条件,取数为0条记录即可。> -- 详情可以查看where does the Cursor stores the rows?> cursor dummy is select * from TESTTAB; > 显示依赖,但是不使用dummy变量,这是不好的实践。好的实践是要消灭动态SQL语句,因为动态带来了极大的维护成本。 附录-utldtree.sql(utldtree is stand for utility deep tree) 使用command window 执行它。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138Rem Rem $Header: utldtree.sql,v 1.2 1992/10/26 16:24:44 RKOOI Stab $ Rem Rem Copyright (c) 1991 by Oracle Corporation Rem NAMERem deptree.sql - Show objects recursively dependent on given objectRem DESCRIPTIONRem This procedure, view and temp table will allow you to see allRem objects that are (recursively) dependent on the given object.Rem Note: you will only see objects for which you have permission.Rem Examples:Rem execute deptree_fill('procedure', 'scott', 'billing');Rem select * from deptree order by seq#;RemRem execute deptree_fill('table', 'scott', 'emp');Rem select * from deptree order by seq#;RemRem execute deptree_fill('package body', 'scott', 'accts_payable');Rem select * from deptree order by seq#;RemRem A prettier way to display this information thanRem select * from deptree order by seq#;Rem isRem select * from ideptree;Rem This shows the dependency relationship via indenting. NoticeRem that no order by clause is needed with ideptree.Rem RETURNSRem Rem NOTESRem Run this script once for each schema that needs this utility.Rem Rem MODIFIED (MM/DD/YY)Rem rkooi 10/26/92 - owner -> schema for SQL2 Rem glumpkin 10/20/92 - Renamed from DEPTREE.SQL Rem rkooi 09/02/92 - change ORU errors Rem rkooi 06/10/92 - add rae errors Rem rkooi 01/13/92 - update for sys vs. regular user Rem rkooi 01/10/92 - fix ideptree Rem rkooi 01/10/92 - Better formatting, add ideptree view Rem rkooi 12/02/91 - deal with cursors Rem rkooi 10/19/91 - Creation drop sequence deptree_seq/create sequence deptree_seq cache 200 /* cache 200 to make sequence faster *//drop table deptree_temptab/create table deptree_temptab( object_id number, referenced_object_id number, nest_level number, seq# number )/create or replace procedure deptree_fill (type char, schema char, name char) is obj_id number;begin delete from deptree_temptab; commit; select object_id into obj_id from all_objects where owner = upper(deptree_fill.schema) and object_name = upper(deptree_fill.name) and object_type = upper(deptree_fill.type); insert into deptree_temptab values(obj_id, 0, 0, 0); insert into deptree_temptab select object_id, referenced_object_id, level, deptree_seq.nextval from public_dependency connect by prior object_id = referenced_object_id start with referenced_object_id = deptree_fill.obj_id;exception when no_data_found then raise_application_error(-20000, 'ORU-10013: ' || type || ' ' || schema || '.' || name || ' was not found.');end;/drop view deptree/set echo onREM This view will succeed if current user is sys. This view shows REM which shared cursors depend on the given object. If the currentREM user is not sys, then this view get an error either about lackREM of privileges or about the non-existence of table x$kglxs.set echo offcreate view sys.deptree (nested_level, type, schema, name, seq#)as select d.nest_level, o.object_type, o.owner, o.object_name, d.seq# from deptree_temptab d, dba_objects o where d.object_id = o.object_id (+)union all select d.nest_level+1, 'CURSOR', '<shared>', '"'||c.kglnaobj||'"', d.seq#+.5 from deptree_temptab d, x$kgldp k, x$kglob g, obj$ o, user$ u, x$kglob c, x$kglxs a where d.object_id = o.obj# and o.name = g.kglnaobj and o.owner# = u.user# and u.name = g.kglnaown and g.kglhdadr = k.kglrfhdl and k.kglhdadr = a.kglhdadr /* make sure it is not a transitive */ and k.kgldepno = a.kglxsdep /* reference, but a direct one */ and k.kglhdadr = c.kglhdadr and c.kglhdnsp = 0 /* a cursor *//set echo onREM This view will succeed if current user is not sys. This viewREM does *not* show which shared cursors depend on the given object.REM If the current user is sys then this view will get an error REM indicating that the view already exists (since prior view createREM will have succeeded).set echo offcreate view deptree (nested_level, type, schema, name, seq#)as select d.nest_level, o.object_type, o.owner, o.object_name, d.seq# from deptree_temptab d, all_objects o where d.object_id = o.object_id (+)/drop view ideptree/create view ideptree (dependencies)as select lpad(' ',3*(max(nested_level))) || max(nvl(type, '<no permission>') || ' ' || schema || decode(type, NULL, '', '.') || name) from deptree group by seq# /* So user can omit sort-by when selecting from ideptree *// 引用how to find the dependencies of table? where does the Cursor stores the rows? 6-schema object dependencies (Oracle Database Concepts, 11g Release 1)","tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://betgar.github.io/tags/Oracle/"},{"name":"PLSQL","slug":"PLSQL","permalink":"https://betgar.github.io/tags/PLSQL/"}]},{"title":"excel的concatenate函数","date":"2017-12-28T12:00:00.000Z","path":"2017/12/28/excel-function-concatenate/","text":"Excel的concatenate函数 Excel中的字符串连接函数,类似一些concat函数。 限制:最多支持255个参数连接。 注意:连接字符时,每一个字符当成一个元素看待。 栗子: 12// 连接单元格,B2,C2,D2内容CONCATENATE(B2,C2,D2) concatenate生成sql语句 如果想把Excel,然后想导入数据库,这种方式比较方便。 字符串和数字类型 字符串和数字类型的连接方式 栗子: 12345678// 字符串:B2// 数字: C2,D2CONCATENATE("INSERT INTO TEST () VALUES(", '", B2, "'", ",", C2, ",", D2, ");")// 单元格栏输入:A2:A200// A2:A200 :能够选中A2-A200的单元格// 公式栏输入:公式// 输入公式,CTRL+ENTER,A2:A200都会填充一样的公式。","tags":[{"name":"Excel","slug":"Excel","permalink":"https://betgar.github.io/tags/Excel/"}]},{"title":"PL/SQL注释风格","date":"2017-12-21T12:00:00.000Z","path":"2017/12/21/plsql-comment-style/","text":"PL/SQL注释风格PL/SQL Comment Style 常常纠结程序的编程风格和注释的风格。PL/SQL中的编程风格也是各种流派,所以我就看了下Oracle内置函数的编程风格和注释风格,自己总结了下,记录下来,当作参考。 Document Style12345678910111213141516------------------ 主题(大写)---- 正文开始-- 啦啦啦啦-- 第一段落结束.-- -- 第二段开始-- 啦啦啦-- 第二段结束。------------------- 主题1-- -- 正文开始-- 正文结束。 Procedure Example 存储过程栗子 1234567891011121314procedure waitany(name out varchar2, message out varchar2, status out integer, timeout in number default maxwait);-- 描述过程的作用...-- 接着说...-- 输入参数:-- 第一个参数:...-- 第一个参数参数描述...-- 第N个参数: ....-- 输出参数(返回值):-- 第一个参数: ...-- 异常处理:...-- 处理描述... 123456789101112131415161718192021222324252627282930procedure waitany(name out varchar2, message out varchar2, status out integer, timeout in number default maxwait); -- Wait for an alert to occur for any of the alerts for which this -- session is registered. Although probably unusual, the same session -- that waits for the alert may also first signal the alert. In this -- case remember to commit after the signal and prior to the wait. -- Otherwise a lock request exception (status 4) will occur. This -- call always performs a commit. -- Input parameters: -- timeout -- The maximum time to wait for an alert. If no alert occurs before -- timeout seconds, then this call will return with status of 1. -- Output parameters: -- name -- The name of the alert that occurred, in uppercase. -- message -- The message associated with the alert. This is the message -- provided by the 'signal' call. Note that if multiple signals -- on this alert occurred before the waitany call, then the message -- will correspond to the most recent signal call. Messages from -- prior signal calls will be discarded. -- status -- 0 - alert occurred -- 1 - timeout occurred -- Errors raised: -- -20000, ORU-10024: there are no alerts registered. -- Cause: You must register an alert before waiting. -- DBMS_ALERT Example oracle中的DBMS_ALERT栗子。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285create or replace package sys.dbms_alert is ------------ -- OVERVIEW -- -- This package provides support for the asynchronous (as opposed to -- polling) notification of database events. By appropriate use of -- this package and database triggers, an application can cause itself -- to be notified whenever values of interest in the database are -- changed. -- -- For example, suppose a graphics tool is displaying a graph of some -- data from a database table. The graphics tool can, after reading and -- graphing the data, wait on a database alert ('dbms_alert.waitone') -- covering the data just read. The tool will automatically wake up when -- the data is changed by any other user. All that is required is that a -- trigger be placed on the database table which then performs a signal -- ('dbms_alert.signal') whenever the trigger is fired. -- -- Alerts are transaction based. This means that the waiting session -- does not get alerted until the transaction signalling the alert commits. -- -- There can be any number of concurrent signallers of a given alert, and -- there can be any number of concurrent waiters on a given alert. -- -- A waiting application will be blocked in the database and cannot do -- any other work. -- -- Most of the calls in the package, except for 'signal', do commits. -- ----------- -- EXAMPLE -- -- Suppose the application wishes to graph average salaries, say by -- department, for all employees. So the application needs to know -- whenever 'emp' is changed. The application would look like this: -- -- dbms_alert.register('emp_table_alert'); -- readagain: -- <read the emp table and graph it> -- dbms_alert.waitone('emp_table_alert', :message, :status); -- if status = 0 then goto readagain; else <error condition> -- -- The 'emp' table would have a trigger similar to the following: -- -- create trigger emptrig after insert or update or delete on emp -- begin -- dbms_alert.signal('emp_table_alert', 'message_text'); -- end; -- -- When the application is no longer interested in the alert, it does -- dbms_alert.remove('emp_table_alert'); -- This is important since it reduces the amount of work required by -- the alert signaller. -- -- If a session exits (or dies) while there exist registered alerts, -- they will eventually be cleaned up by future users of this package. -- -- The above example guarantees that the application will always see -- the latest data, although it may not see every intermediate value. -------------- -- VARIATIONS -- -- The application can register for multiple events and can then wait for -- any of them to occur using the 'waitany' call. -- -- An application can also supply an optional 'timeout' parameter to the -- 'waitone' or 'waitany' calls. A 'timeout' of 0 returns immediately -- if there is no pending alert. -- -- The signalling session can optionally pass a message which will be -- received by the waiting session. -- -- Alerts may be signalled more often than the corresponding application -- 'wait' calls. In such cases the older alerts are discaded. The -- application always gets the latest alert (based on transaction commit -- times). -- -- If the application does not require transaction based alerts, then the -- 'dbms_pipe' package may provide a useful alternative -- -- If the transaction is rolled back after the call to 'dbms_alert.signal', -- no alert will occur. -- -- It is possible to receive an alert, read the data, and find that no -- data has changed. This is because the data changed after the *prior* -- alert, but before the data was read for that *prior* alert. -------------------------- -- IMPLEMENTATION DETAILS -- -- In most cases the implementation is event-driven, i.e., there are no -- polling loops. There are two cases where polling loops can occur: -- -- 1) Parallel mode. If your database is running parallel mode then -- a polling loop is required to check for alerts from another -- instance. The polling loop defaults to one second and is settable -- by the 'set_defaults' call. -- 2) Waitany call. If you use the 'waitany' call, and a signalling -- session does a signal but does not commit within one second of the -- signal, then a polling loop is required so that this uncommitted -- alert does not camouflage other alerts. The polling loop begins -- at a one second interval and exponentially backs off to 30 second -- intervals. -- -- This package uses the dbms_lock package (for synchronization between -- signallers and waiters) and the dbms_pipe package (for asynchronous -- event dispatching). ------------------------------------------------------- -- INTERACTION WITH MULTI-THREADED AND PARALLEL SERVER -- -- When running with the parallel server AND multi-threaded server, a -- multi-threaded (dispatcher) "shared server" will be bound to a -- session (and therefore not shareable) during the time a session has -- any alerts "registered", OR from the time a session "signals" an -- alert until the time the session commits. Therefore, applications -- which register for alerts should use "dedicated servers" rather than -- connecting through the dispatcher (to a "shared server") since -- registration typically lasts for a long time, and applications which -- cause "signals" should have relatively short transactions so as not -- to tie up "shared servers" for too long. ------------ -- SECURITY -- -- Security on this package may be controlled by granting execute on -- this package to just those users or roles that you trust. You may -- wish to write a cover package on top of this one which restricts -- the alertnames used. Execute privilege on this cover package can -- then be granted rather than on this package. ------------- -- RESOURCES -- -- This package uses one database pipe and two locks for each alert a -- session has registered. --------------------- -- SPECIAL CONSTANTS -- maxwait constant integer := 86400000; -- 1000 days -- The maximum time to wait for an alert (essentially forever). ---------------------------- -- PROCEDURES AND FUNCTIONS -- procedure set_defaults(sensitivity in number); -- Set various defaults for this package. -- Input parameters: -- sensitivity -- In case a polling loop is required (see "Implementation Details" -- above), this is the time to sleep between polls. Deafult is 5 sec. -- procedure register(name in varchar2, cleanup in boolean default TRUE); -- Register interest in an alert. A session may register interest in -- an unlimited number of alerts. Alerts should be de-registered when -- the session no longer has any interest (see 'remove'). This call -- always performs a 'commit'. -- Input parameters: -- name -- The name of the alert in which this session is interested. -- WARNING: Alert names beginning with 'ORA$' are reserved for use for -- products provided by Oracle Corporation. Name must be 30 bytes -- or less. The name is case-insensitive. -- cleanup -- This specifies whether we should perform cleanup of any orphaned -- pipes that may exist and are used by the dbms_alert package. This -- cleanup is only performed on the first call to "register" for each -- package instantiation. The default for the parameter is TRUE. -- procedure remove(name in varchar2); -- Remove alert from registration list. Do this when the session is no -- longer interested in an alert. Removing an alert is important -- since it will reduce the amount of work done by signalers of the alert. -- If a session dies without removing the alert, that alert will -- eventually (but not immediately) be cleaned up. This call always -- performs a commit. -- Input parameters: -- name -- The name of the alert to be removed from registration list. The -- name is case-insensitive. -- procedure removeall; -- Remove all alerts for this session from registration list. Do this -- when the session is no longer interested in any alerts. Removing -- alerts is important since it will reduce the amount of work done -- by signalers of the alert. If a session dies without removing all -- of its alerts, the alerts will eventually (but not immediately) -- be cleaned up. This call always performs a commit. -- -- This procedure is called automatically upon first reference to this -- package during a session. Therefore no alerts from prior sessions -- which may have terminated abnormally can affect this session. procedure waitany(name out varchar2, message out varchar2, status out integer, timeout in number default maxwait); -- Wait for an alert to occur for any of the alerts for which this -- session is registered. Although probably unusual, the same session -- that waits for the alert may also first signal the alert. In this -- case remember to commit after the signal and prior to the wait. -- Otherwise a lock request exception (status 4) will occur. This -- call always performs a commit. -- Input parameters: -- timeout -- The maximum time to wait for an alert. If no alert occurs before -- timeout seconds, then this call will return with status of 1. -- Output parameters: -- name -- The name of the alert that occurred, in uppercase. -- message -- The message associated with the alert. This is the message -- provided by the 'signal' call. Note that if multiple signals -- on this alert occurred before the waitany call, then the message -- will correspond to the most recent signal call. Messages from -- prior signal calls will be discarded. -- status -- 0 - alert occurred -- 1 - timeout occurred -- Errors raised: -- -20000, ORU-10024: there are no alerts registered. -- Cause: You must register an alert before waiting. -- procedure waitone(name in varchar2, message out varchar2, status out integer, timeout in number default maxwait); -- Wait for specified alert to occur. If the alert was signalled since -- the register or last waitone/waitany, then this call will return -- immediately. The same session that waits for the alert may also -- first signal the alert. In this case remember to commit after the -- signal and prior to the wait. Otherwise a lock request exception -- (status 4) will occur. This call always performs a commit. -- Input parameters: -- name -- The name of the alert to wait for. The name is case-insensitive. -- timeout -- The maximum time to wait for this alert. If no alert occurs before -- timeout seconds, then this call will return with status of 1. -- If the named alert has not been registered then the this call -- will return after the timeout period expires. -- Output parameters: -- message -- The message associated with the alert. This is the message -- provided by the 'signal' call. Note that if multiple signals -- on this alert occurred before the waitone call, then the message -- will correspond to the most recent signal call. Messages from -- prior signal calls will be discarded. The message may be up to -- 1800 bytes. -- status -- 0 - alert occurred -- 1 - timeout occurred -- procedure signal(name in varchar2, message in varchar2); -- Signal an alert. -- Input parameters: -- name -- Name of the alert to signal. The effect of the signal call only -- occurs when the transaction in which it is made commits. If the -- transaction rolls back, then the effect of the signal call is as -- if it had never occurred. All sessions that have registered -- interest in this alert will be notified. If the interested sessions -- are currently waiting, they will be awakened. If the interested -- sessions are not currently waiting, then they will be notified the -- next time they do a wait call. Multiple sessions may concurrently -- perform signals on the same alert. However the first session -- will block concurrent sessions until the first session commits. -- Name must be 30 bytes or less. It is case-insensitive. This call -- does not perform a commit. -- message -- Message to associate with this alert. This will be passed to -- the waiting session. The waiting session may be able to avoid -- reading the database after the alert occurs by using the -- information in this message. The message must be 1800 bytes or less.end; 注释模版123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176CREATE OR REPLACE PROCEDURE TEST_PA_COMMENT_STYLE(I_COMP_ID IN VARCHAR2, -- 1 I_MODE_IDS IN VARCHAR2, -- 2 I_OBJ_TYPES IN VARCHAR2, -- 3 I_OBJECTS IN VARCHAR2, -- 4 I_DATE_POINT IN VARCHAR2 -- 5 ) IS ------------------------------- -- TEST_PA_COMMENT_STYLE: -- 功能: -- 根据对方数据添加“清理关键字”, -- 根据“借方”或者“借方”给对方添加关键字. -- 编写人:LJN -- 编写日期:2017-12-26 -- 内部提交(commit): NO -- -- 策略(vpd): -- 时间和单位 -- -- 中间表(temporary table): -- 1. GT_GET_GLDX_SES -- 2. GT_GET_KMTMP_SES ----------------------- -- 输入(IN)参数说明: -- I_OBJ_TYPES: -- 单位id -- I_MODE_IDS: -- 清理方式id,多个逗号分隔. -- I_OBJ_TYPES: -- 管理对象类型id,多个逗号分隔. -- I_OBJECTS: -- 管理对象id. -- I_DATE_POINT: -- 时间点;格式YYYY-MM-DD. --------------- -- 输出(out)参数: -- --------------- -- 异常说明: -- E_IN_PARAM_EMPTY_EX -- 参数为空异常 ----------- -- 变量声明 -- V_SQL: -- SQL语句. ---------- -- 子程序 -- FU_ADD_QUOTES_STR -- 添加英文引号 -- FU_BUILD_IN_CONDITION -- 构建IN条件 ----------- -- 异常定义 -- V_ERR_MSG -- 错误消息. V_SQL VARCHAR2(8000); V_ERR_MSG VARCHAR2(4000); V_ERR_CODE BINARY_INTEGER; E_PARAM_EMPTY_CODE CONSTANT BINARY_INTEGER DEFAULT - 20001; --------------- -- 参数为空异常. E_IN_PARAM_EMPTY_EX EXCEPTION; PRAGMA EXCEPTION_INIT(E_IN_PARAM_EMPTY_EX, -20001); FUNCTION FU_ADD_QUOTES_STR(I_STR IN VARCHAR2, -- 1 I_SEPARATOR IN VARCHAR2 DEFAULT ',' -- 2 ) RETURN VARCHAR2 IS V_ID_STR VARCHAR2(50); V_INDEX1 NUMBER(5) DEFAULT 0; V_INDEX2 NUMBER(5) DEFAULT 1; V_CONDITION VARCHAR2(4000); V_ID VARCHAR2(4000); O_STR VARCHAR2(4000); BEGIN V_ID_STR := I_STR; IF V_ID_STR IS NOT NULL THEN V_INDEX1 := INSTR(V_ID_STR, I_SEPARATOR); WHILE V_INDEX1 > -1 LOOP V_INDEX1 := INSTR(V_ID_STR, I_SEPARATOR, V_INDEX2); IF V_INDEX1 = 0 THEN V_ID := SUBSTR(V_ID_STR, V_INDEX2); V_INDEX1 := -1; ELSE V_ID := SUBSTR(V_ID_STR, V_INDEX2, V_INDEX1 - V_INDEX2); V_INDEX2 := V_INDEX1 + 1; END IF; IF V_CONDITION IS NOT NULL THEN V_CONDITION := V_CONDITION || ','; END IF; V_CONDITION := V_CONDITION || '''' || V_ID || ''''; END LOOP; END IF; O_STR := V_CONDITION; RETURN(O_STR); END FU_ADD_QUOTES_STR; -- END FU_ADD_QUOTES_STR FUNCTION FU_BUILD_IN_CONDITION(I_NUM_STR IN VARCHAR2, -- 1 I_CONDT_STR IN VARCHAR2, -- 2 I_SEPARATOR IN VARCHAR2 DEFAULT ',' --3 ) RETURN VARCHAR2 IS V_CONDITION VARCHAR2(4000); O_STR VARCHAR2(4000); BEGIN V_CONDITION := FU_ADD_QUOTES_STR(I_NUM_STR, I_SEPARATOR); O_STR := I_CONDT_STR || ' IN (' || V_CONDITION || ')'; RETURN(O_STR); END FU_BUILD_IN_CONDITION;BEGIN ------------ -- 参数处理 -- 单位ID IF I_COMP_ID IS NULL THEN RAISE E_IN_PARAM_EMPTY_EX; END IF; -- 清理方式ID IF I_MODE_IDS IS NULL THEN RAISE E_IN_PARAM_EMPTY_EX; END IF; ------------- -- 处理临时表 EXECSQL('DELETE FROM GT_GET_KMTMP_SES'); EXECSQL('DELETE FROM GT_GET_GLDX_SES'); EXECSQL('DELETE FROM HZ_LS_KMHZBQUERY_SEC'); ------------ -- 科目 -- GT_GET_KMTMP_SES -- 确定清理方式科目设置层级 及科目方向(ITEMCODE科目方向, LEAF清理层次) V_SQL := 'INSERT INTO HZ_LS_KMHZBQUERY_SEC (IID, ITEMCODE, LEAF)' || ' SELECT '; --SELECT * FROM XTQLMODE X WHERE X.DWDH='0001' AND NVL(X.TYBZ, 0) = 0 AND X.QLMODEID IN (); ------------ -- 对象类型 ----------- -- 管理对象 ---------——--------- -- 查询出未清理数据: -- 1. qlbz=0EXCEPTION ------------ -- 参数为空 WHEN E_IN_PARAM_EMPTY_EX THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := '参数为空.'; DBMS_STANDARD.RAISE_APPLICATION_ERROR(V_ERR_CODE, V_ERR_MSG); DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE); WHEN OTHERS THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := SQLERRM(SQLCODE); ROLLBACK; -- OTHERS中必须显式回滚 ------------- -- 打印异常栈 DBMS_STANDARD.RAISE_APPLICATION_ERROR(V_ERR_CODE, V_ERR_MSG); DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE); RAISE; -- 继续抛异常END TEST_PA_COMMENT_STYLE;","tags":[{"name":"PLSQL","slug":"PLSQL","permalink":"https://betgar.github.io/tags/PLSQL/"}]},{"title":"JavaScript中Object类型转换到原始类型","date":"2017-11-24T12:00:00.000Z","path":"2017/11/24/object-to-primitive-conversions-in-javascript/","text":"Object-to-Primitive Conversions in JavaScript 这是一篇转载的文章。 Like most object-oriented programming languages, JavaScript provides built-in ways to convert between objects and primitive values, by way of the special toString and valueOf methods. This article will cover the basics of these methods, but then dive into the details of how this stuff really works, bad stuff, performance, and browser support. Types and PrimitivesTo understand this article, you’ll need to understand the difference between primitive and non-primitive values in JavaScript. There are 5 primitive types, which are associated with the various primitive values. Null: The value null. Undefined: The value undefined. Number: All numbers, such as 0 and 3.14. Also NaN, and Infinity. Boolean: The values true and false. String: All strings, such as “foo” and “”. All other values are non-primitive, including arrays, functions, and plain old objects. For completeness, here are the results of the typeof operator, applied to these values: 123456789101112131415typeof null; // \"object\"typeof undefined; // \"undefined\"typeof 0; // \"number\" (typeof NaN is also \"number\")typeof true; // \"boolean\"typeof \"foo\"; // \"string\"typeof {}; // \"object\"typeof function () {}; // \"function\"typeof []; // \"object\" Note: typeof null should not be "object". This is a mistake from the first versions of JavaScript, but it’s really too late to fix. A more sensible type would have been "null", but this is what we’re stuck with.If you’ve got that down, then we’re ready to move on to the basics of toString and valueOf. If you’re already familiar with the basics, feel free to skip ahead to “How it Works”. Basic Usage We’ll be using a simple example population object that holds a country name and a population. Lets code that up. function population(country, pop) { return { country: country, pop: pop };} var america_pop = population(“USA”, 350e6);var mexico_pop = population(“Mexico”, 200e6);var canada_pop = population(“Canada”, 200e6); alert(america_pop); // [object Object] var north_america_pop = america_pop + mexico_pop + canada_pop; alert(north_america_pop); // [object Object][object Object][object Object]This works, but the calls to alert are not very useful. What we’d really like is for the first alert to show ‘[Population “USA” 350000000]’ and the second to show “750000000”. So, let’s code that up next. toString All objects inherit the method toString from Object.prototype, which returns “[object Object]”. However, we can easily override this by providing toString as a method of our object, or its prototype. In this example, we’ll attach it directly to each instance, but feel free to use the prototype instead. function population(country, pop) { return { country: country, pop: pop, toString: function () { return "[Population " + "\\"" + country + "\\" " + pop + "]"; } } } var america_pop = population(“USA”, 350e6);alert(america_pop); // [Population “USA” 350000000]Note: I’m using closure on the country parameter, rather than using this.country. This only works due to how the constructor is set up. If you placed toString on the prototype, you would need to use this.country.valueOf All JavaScript objects also inherit the method valueOf from Object.prototype. By default, this method simply returns the object itself, but is generally overridden to convert an object to a Number, or another primitive value, so it can be used by operators like +. We can do the same thing as above to complete our basic example. 12345678910111213141516171819function population(country, pop) { return { country: country, pop: pop, toString: function () { return \"[Population \" + \"\\\"\" + country + \"\\\" \" + pop + \"]\"; }, valueOf: function () { return pop; } };} 1234567891011var america_pop = population(\"USA\", 350e6);var mexico_pop = population(\"Mexico\", 200e6);var canada_pop = population(\"Canada\", 200e6);alert(america_pop); // [Population \"USA\" 350000000var north_america_pop = america_pop + mexico_pop + canada_pop;alert(north_america_pop); // 750000000 Here we’ve defined the valueOf function of our population object to return the population, which should be a Number. How It WorksAs with most things in JavaScript, the process by which toString gets called is not as simple as you’d think. Let’s explore what happens when alert(america_pop) is called. alert calls GetValue on the reference. This returns the object it points at.alert calls ToString on the value (this is not the same as the object’s toString)ToString calls ToPrimitive on the value, passing the hint String.ToPrimitive calls the object’s internal [[DefaultValue]] method with the hint String.[[DefaultValue]] calls the toString property of the object, with the object as this.The result of toString is a primitive value, so it is returned, all the way up the chain to the ToString method.Since the result is of type String, ToString returns all the way to alert.alert displays the value.While this is a lot, it’s pretty straightforward. However, he key mechanism that needs more explaining is the ToPrimitive function. This function is used to take an arbitrary value and get a corresponding primitive value instead. If the input is already a primitive value then the value will be returned without conversion. However, if the value is non-primitive, then it will call the internal [[DefaultValue]] method to find a default value for the object. [[DefaultValue]] is an internal property of every object. It’s a method that takes an optional hint, which should be either Number or String. If a hint is not provided, it will default to Number unless the object is a Date, in which case it defaults to String (this is silly). After this has been figured out, it will call toString and valueOf, in order, to find a primitive value. This is where the hint comes into play. If the hint is Number, then valueOf will be tried first, but if it’s String then toString will be tried first. Here’s the ensuing process: If the first method exists, and is callable, call it and get the result, otherwise skip to 3.If the result of 1 is a primitive, return it.If the second method exists, and is callable, call it and get the result, otherwise skip to 5.If the result of 3 is a primitive, return it.Throw a TypeError exception.The value that is returned by [[DefaultValue]] is guaranteed to be primitive. If it was not, a TypeError would have been thrown. This also implies that toString and valueOf should return primitives on order to be useful in this context. Confusion About the + Operator Here’s an example with a (possibly) unexpected result: 12345678910var foo = { toString: function () { return \"foo\"; }, valueOf: function () { return 5; }}; 123alert(foo + \"bar\"); // 5baralert([foo, \"bar\"].join(\"\")); // foobar In this context, we’re using the + operator to do string concatenation. But, foo was not converted to a string using toString, it was turned into a number using valueOf, then used for string concatenation. This probably isn’t what we want, but it is how it works. It’s a side-effect of the overloading of the + operator for arithmetic and string concatenation. The +operator has a well-defined process: Evaluate the left-hand side, and get the value. Evaluate the right-hand side, and get the value. Call ToPrimitive on both the left-hand and right-hand sides (without a hint) If either primitive value is a String, then skip to 7. Call ToNumber on both values. Return the sum of the values. Call ToString on both values. Return the concatenation of both values. Since no hint is passed to the ToPrimitive calls, the hint will be defaulted to Number (unless it’s a Date, which defaults to String). This means that our valueOf function will be called, instead of toString. It’s not until after the primitive values are retrieved that the interpreter decides whether it is going to do string concatenation or arithmetic. That’s why our example above returns “5bar” instead of “foobar”. Bad StuffThere is one really bad feature of all this, which is that ToPrimitive does not enforce any type-checking on the return values, other than that they are primitive. This means you can write code like this: 12345678910var foo = { toString: function () { return 5; }, valueOf: function () { return \"foo\"; }}; 12345alert(foo.toString() + 1); // 6 (bad!)alert(foo + 1); // \"foo1\" (no good!)alert(+foo); // NaN (the worst!) The valueOf method can be forgiven for not type-checking, because it is more generic. You’d expect it to be able to return any suitable primitive value. However, the toString method has no such excuse. This is simply a bad feature. You can, of course, mitigate by using String(foo) instead of foo.toString(), which will call toString and then convert that result to a string. But you should not have to do this, or worry about this. Please do not make objects with toString methods that do not return strings. How About Performance?After understanding the complexity that goes into these implicit conversion, I got curious about how that affects performance. So I decided to test the time it takes to perform an [].join(obj) over 1,000,000 iterations in the major browsers. I did one test with the object being implicitly cast to a string, and one where I called the toString method manually (i.e. [].join(obj.toString())). As expected, the explicit call was faster in most cases. Firefox 3.6.2: 874ms vs. 320ms - almost 3x faster.Chrome 5: 94ms vs. 47ms - 2x faster.Opera 10.50: 155ms vs 182ms - a little slower.Safari 4: 409ms vs 280ms - almost 2x faster.Internet Explorer 8: 2856ms vs 2786ms - about the same.Internet Explorer 9 (preview): 645ms vs 633ms - about the same.Note 1: The Firefox, Chrome, Opera, and Safari tests were all run on a Macbook Pro running OS X 10.5. The IE tests were run on a desktop running Windows 7. Run the tests yourself here.Note 2: I chose to use the [].join method because doing so was most likely to avoid any dead-code elimination optimizations in modern browsers. I’ve had trouble with this before, in Firefox. I did try testing with the String() constructor, with similar results in most browsers. Opera was an exception where using the explicit toString was close to 5x faster. In Firefox, the explicit cast was a bit faster, but both cases were about 100x faster than the [].join method (and other browsers), which means the code-path was probably being removed by the dead code eliminator.The takeaway from this performance test is that it’s always best to call your object’s type-conversion methods directly, rather than relying on the interpreter to do the complex series of method calls and comparisons needed to do it automatically. The Opera 10.50 result is very strange, but it’s not particularly slower, so I wouldn’t worry about it. The gains made in other browsers more than make up for the outlier Opera result. How About Browser Support? Like many things in the ECMAScript specification, these processes are complex, and I doubted that all browsers would implement them exactly as specified. So, in that test suite from earlier, I added compliance checks. I was quite surprised to see that all major browsers, including versions of Internet Explorer going back to at least IE 5.5, implement these mechanisms correctly. This is even the case with the awkward handling when developers do things like make toString return a number instead of a string. All browsers handle the code according to the specification. This is great news. But the specification unhelpfully introduced ambiguity in one particular area: the absence of a hint for the ToPrimitive function. Here’s the exact wording: All native ECMAScript objects except Date objects handle the absence of a hint as if the hint Number were given; Date objects handle the absence of a hint as if the hint String were given. Host objects may handle the absence of a hint in some other manner. That the standard explicitly allows browsers to deviate here worried me. Included in that test suite was a check that, in the absence of a hint, Date objects will default to String and Boolean objects will default to Number. All browsers passed this check as well, which means that browser support for all of this functionality seems to be consistent and correct. ConclusionsI hope this was useful in understanding how these mechanisms work in JavaScript. There are three important things to take away from this article: Implement toString and valueOf on your commonly-reused objects. They can help you write clearer, more concise code, and make debugging easier too.All browsers implement object-to-primitive conversion according to the specification, so you can safely consult it for more detail.When performance is important, always try to call your type-conversion methods directly, instead of relying on JavaScript’s implicit calls.You can find the test suite used for this article here if you’re interested in trying to replicate my results. Please let me know if you find contradictory results to what I posted here. Thanks for reading! If you have questions or feedback then leave a comment below or contact me directly. filed under javascript","tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://betgar.github.io/tags/JavaScript/"}]},{"title":"怎么使用Oracle手册","date":"2017-11-23T12:00:00.000Z","path":"2017/11/23/how-to-use-oracle-reference/","text":"怎么使用Oracle手册 介绍一下官网文档模块。这是一片转载的文章。 Concept包含了 oracle 数据库里面的一些基本概念和原理, 比如 数据库逻辑结构, 物理结构, 实例结构, 优化器, 事务等. PDF 460页Reference 包含了动态性能视图, 数据字典, 初始化参数等, 如果有参数不知道意思, 或者 v$视图字段信息模糊, 都可以从这里找到描述, 使用 html版的进去 ctrl+f查找比较快.还包含一些其他比如数据库的硬性限制, 等待事件的名称, 后台进程的描述等. SQL Language Reference这个文档中包含 oracle数据库的sql语法,(不包含plsql), 比如 create table 等. Administrator’s Guide各种管理oracle数据库的场景都在这里, 各种管理表, 索引, 表空间, redo等 Performance tuning guide包含优化相关内容, 介绍优化方法, 数据库实例及sql的优化 Database Administration 页面因为上面 administrator’s guide 已经描述了, 这里列举几个比较重要文档 Net Services Reference文档说明了几个关于监听的重要文件, tnsnames.ora, listener.ora, sqlnet.ora 等 Backup and Recovery User’s Guide描述了 rman 的各种用法 Application Development 页面PL/SQL Packages and Types Reference包括各种 oracle 自建的包和函数的功能, 参数描述. 如果有不了解的包, 可以在这里找到, 比如 dbms_stats. PL/SQL language Referenceplsql 编程的基础概念, 语法等. SQLJ Developer’s GuideSQLJ, JAVA相关的内容 Workspace Manager Developer’s Guide关于 oracle 的 workspace manager 版本化的一些东西 installing and upgrading 页面需要安装 oracle 参照这里. Grid Computing 页面包括 oracle grid infrastructure 集群软件的各种文档 Automatic Storage Management Administrator’s GuideAsm 相关文档 Real Application Clusters Administration and Development Guide包括 RAC 环境下的数据库管理和维护的内容 Clusterware Administration and Deployment Guide主要写了集群软件的各种工具和命令 High Availability 页面包含各种高可用文档 Data Guard Broker 和 Data Guard Concepts and Administration关于 Data guard 文档 Data Warehousing and Business Intelligence 页面数据仓库和商业智能的相关技术 VLDB and Partitioning Guidevery large database, … partition 和 parallel 相关的内容 utilitiesimp, expdp, sql*loader, 外部表, dbv, adrci, logminer unstructured Data and Content Management 页面非结构化数据相关, 比如 object type, xml type, oracle text SecureFiles and Large Objects Developer’s Guide讲述了 11g 中存储 lob 字段使用的 secure file 技术内容 Text Application Developer’s Guide 和 Text Reference全文索引相关","tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://betgar.github.io/tags/Oracle/"}]},{"title":"jQuery的数据缓存模块","date":"2017-05-31T12:00:00.000Z","path":"2017/05/31/the-data-storage-module-of-jquery/","text":"jQuery的数据缓存模块 几乎每一个前端库或者框架都有自己的数据缓存模块,而且这个是框架的基础核心模块。所有的应用模块,都会依赖这个数据缓存模块。jQuery也有自己的数据缓存模块,所以把自己学习的心得分享一下。 属性缓存数据 属性缓存数据,就是使用对象的属性或者element元素的attribute来缓存数据。jQuery(1.x)就是利用这个实现的自己的数据缓存模块的。这种方法也叫属性标记法. element属性attribute标记法 这个写过前端代码的都很熟悉,就是在标签的html上加上自定义属性。 栗子1: 1234<!-- element-attribute.html --><div id=\"demo1\" customize=\"defined by me\"></div><button id=\"getAttrBtn\">getAttr</button><button id=\"setAttrBtn\">setAttr</button> 12345678910111213141516171819202122var $ = function (id) { return document.getElementById(id);};var getAttrBtn = $('getAttrBtn');var setAttrBtn = $('setAttrBtn');/** *标签属性标记法 */getAttrBtn.onclick = function () { var demoEle = $('demo1'), value = demoEle.getAttribute('customize'); window.console.log(value); demoEle.innerHTML = value;};setAttrBtn.onclick = function () { var demoEle = $('demo1'),value; demoEle.setAttribute('customize', 'changed by element attribute'); value = demoEle.getAttribute('customize'); window.console.log(value); demoEle.innerHTML = value;}; 栗子1就是我们通过element标签来保存一些自己的业务数据。 优点:原生;所有浏览器都支持。 缺点:只能保存字符串,不能保存对象和function,这两种类型。 对象属性标记法 对象属性标记,就很好理解了,就是平时我们给JavaScript对象赋值的操作。 栗子2: 1234<!-- element-attribute.html --><div id=\"demo1\" customize=\"defined by me\"></div><button id=\"getAttrBtn\">getAttr</button><button id=\"setAttrBtn\">setAttr</button> 1234567891011121314/***对象属性标记法*/getAttrBtn.ondblclick = function () { var demoEle = $('demo1'), value = demoEle['customize']; window.console.log(value); demoEle.innerHTML = value;};setAttrBtn.ondblclick = function () { var demoEle = $('demo1'),value; demoEle['customize'] = 'changed by object attribute',value = demoEle['customize']; window.console.log(value); demoEle.innerHTML = value;}; 栗子2就是对象属性标记法,原理就是直接使用浏览器中element(宿主元素)进行数据缓存。 优点:JavaScript全类型的数据都支持。 缺点: 浏览器兼容性问题。 object,applet,embed等对象是不能使用这种方法,进行属性缓存数据,因为他们不是标准的浏览器中的对象,属于扩展内容。 window对象如果使用这种方式,变成了全局变量 内存泄漏:内存泄漏是这种方式的最大威胁。 1234567// 内存泄漏栗子:// 最直接的栗子就是demoEle.dep.el的属性指向demoElevar demoEle = $('demo1');demoEle.dep = { el: demoEle};// 这样的循环依赖,很难觉察到,所以demoEle.dep和demoEle都得不到回收,造成内存泄漏。 数据缓存模块实现原理 属性缓存数据的两种方法,各有优缺点,但是对象属性缓存数据 已经非常理想了,只要解决一下内存泄漏 这个问题就很实用了。分享一下jQuery的巧妙设计。 jQuery的设计思路: 数据统一管理(数据储存在一个对象 中) 数据不缓存在具体的element对象属性上,而是在element缓存一个jQueryKey,然后通过jQueryKey存取数据。 jQueryKey:是一个字符串值,不存在循环依赖 解决内存泄漏的问题 。 栗子: 1234<!-- data-storage.html --><div id=\"demo1\"></div><button id=\"getAttrBtn\">getStore</button><button id=\"setAttrBtn\">store</button> 1234567891011121314151617181920212223242526272829function isNumeric(obj) { return !isNaN(parseFloat(obj)) && isFinite(obj);}function Model() {}Model.cacheKey = 'ceche' + (new Date).getTime();Model.cache = {}; // 用来缓存数据Model.uuid = 0;Model.data = function (ele, key, value) { var cacheKey = Model.cacheKey, indexKey = ele[cacheKey]; if (arguments.length === 2) { // get if (isNumeric(indexKey)) { return Model.cache[indexKey][key] === undefined ? null : Model.cache[indexKey][key]; } else { return null; } } else { // set if (isNumeric(indexKey)) { Model.cache[indexKey][key] = value; } else { ele[cacheKey] = indexKey = '' + Model.uuid++; Model.cache[indexKey] = {}; Model.cache[indexKey][key] = value; } } }; 上面的栗子: Model.cacheKey做element的属性,element[Model.cacheKey]的value值是Model.cache的对象key。 element[Model.cacheKey]的value值,只是一个字符串所以,完美解决了内存泄漏 问题。 Model.cache是一个普通的JavaScript对象,所以它可以支持所有的JavaScript的数据类型。解决了在标签的属性上缓存数据,只支持 字符串 的问题。 12// 这就是element对应cache中缓存的整个数据对象Model.cache[element[Model.cacheKey]]; jQuery数据缓存模块源码 jQuery数据缓存模块的部分源码。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364// 下面的就是一些 jQuery 涉及数据存储的操作jQuery.extend({ // 全局的缓存对象 cache: {}, // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. // 如果你尝试给以下元素添加扩展属性,将抛出“无法捕捉”的异常 // 这里声明的几个元素对象是不给于数据绑定的 // applet、embed 和 object 元素是不支持设置 expando 属性的,所以不支持 data 方法 noData: { \"applet\": true, \"embed\": true, // Ban all objects except for Flash (which handle expandos) \"object\": \"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" }, // 检查对象是否已经存储了数据 hasData: function(elem) { elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando]; return !!elem && !isEmptyDataObject(elem); }, // 给 elem(可是DOM,可以是JS对象)添加 key-value 为 name-data 的数据 data: function(elem, name, data) { return internalData(elem, name, data); }, // 移除 elem(可是DOM,可以是JS对象)上 removeData: function(elem, name) { return internalRemoveData(elem, name); }, // For internal use only. // 添加或读取一个仅供内部使用的数据 _data: function(elem, name, data) { return internalData(elem, name, data, true); }, // 删除内部使用的数据数据 _removeData: function(elem, name) { return internalRemoveData(elem, name, true); }, // A method for determining if a DOM node can handle the data expando // 检测一个属性是否可以绑定数据 // nodeType = 1 -- Element // nodeType = 9 -- Document acceptData: function(elem) { // Do not set data on non-element because it will not be cleared (#8335). // if (elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9) { return false; } // if(elem.nodeName){ // noData = jQuery.noData[elem.nodeName.toLowerCase()]; // } var noData = elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]; // nodes accept data unless otherwise specified; rejection can be conditional return !noData || noData !== true && elem.getAttribute(\"classid\") === noData; }}); 来一个对应: jQuery.expando对应Model.cacheKey jQuery.cache对应Model.cache jQuery.data对应Model.data 在jQuery.hasData方法内部看到了: 1jQuery.cache[elem[jQuery.expando]]; // 取数据 1Model.cache[indexKey][key]; // Model和jQuery原理是一样的。 有了Model这个小栗子,就能更好的理解jQuery的data模块实现原理了;当然jQuery的data模块还有很多其它的兼容性处理,防冲突处理。 HTML5的DataSet API HTML5加入了一个关于自定义属性的API,很是方便,虽然非现代浏览器不支持,但是你可以使用jQuery,jQuery已经提供了支持。 栗子: 12<!-- 自定义属性使用 data-* 开头 --><span data-toggle=\"dropdown\" class=\"moduleTitle\" data-options=\"234\">测试</span> 123456789101112// 原生操作// 返回全部以data-开头的自定义属性.var dataSet = document.querySelector('.moduleTitle').dataset; // DOMStringMap// 返回optionsvar dataSet = document.querySelector('.moduleTitle').dataset.options;// 新增或者修改(同步DOM元素的data-options属性)document.querySelector('.moduleTitle').dataset.options = 234;// 删除delete document.querySelector('.moduleTitle').dataset.options jQuery栗子: 12<span data-toggle=\"dropdown\" class=\"moduleTitle\" data-options=\"234\" data-camel-options=\"234\">测试</span> 123456789101112// jQuery操作// 访问所有的缓存数据,包括data-*自定义部分var dataSet = $('.moduleTitle').data();// 查询 (jQuery为了遵守规范自动转换data-*自定义属性为“驼峰”访问方式.)var camelOptions = $('.moduleTitle').data('camelOptions');// 修改 (jQuery使用自己数据缓存模块,不再同步修改data-camel-options属性)$('.moduleTitle').data('camelOptions', 3);$('.moduleTitle').data('camelOptions'); // 3$('.moduleTitle').attr('data-camel-options'); // 234 注意: jQuery.data访问data-自定义属性是有优先级顺序,先从$.cache中获取,如果没有,才尝试自定义属性获取。 jQuery.data方法只能修改自己缓存内的数据,不会修改data-属性的数据;所以千万不要使用jQuery().attr和jQuery.data混合使用操作data-自定义属性。","tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://betgar.github.io/tags/jQuery/"}]},{"title":"使用jQuery UI widget编写有状态的插件","date":"2017-05-07T12:00:00.000Z","path":"2017/05/07/building-stateful-plugins-with-jquery-ui-widget/","text":"使用jQuery UI widget编写有状态的插件 jQuery最好的设计在于两个方面,一是它的API,另外一个是它的插件系统。这也是jQuery这些年异军突起的原因,虽然现在遇到了新的危机,Vue,Angular,React的出现给在现代的浏览器开放带来了新的开发体验。 这两年前端库或者框架更新太快,但是如果你是一个真正的实践者,时间长了你慢慢发现其实很多的前端开发框架都是当时编程思想的不同实现。 本人的JavaScript编程能力,也从最初的面向过程的面条式代码,转变到面向对象和使用一些MVC方式来组织代码。废话不多说,进入正题。 [TOC] 定义插件的步骤 首先插件定义时,为了防止外部影响插件,一般插件定义全部放在闭包内部。 定义插件的闭包 123(function () {// define you plugin})() 基于jQuery来定义插件 123456789101112// 以step插件为例;(function ($) { $.fn.step = function (element, options) { // do something } $.fn.step.prototype = { options: { version: '0.0.1', name: 'step' } };})(jQuery) 使用extend扩展jQuery对象(jQuery也是这样扩展而成的) 123456789;(function ($) { $.extend($.fn, { // 插件属性 options: { version: '0.0.1', name: 'step' } });})(jQuery) 插件定义的大致步骤 举一个bootstrap插件Alert的例子。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 以Alert为例+function ($) { 'use strict'; // ALERT CLASS DEFINITION // ====================== // Alert构造函数 var Alert = function (el) { } // 公共属性 Alert.VERSION = '3.3.7' Alert.TRANSITION_DURATION = 150 Alert.prototype.close = function (e) { } // 备份jQuery的prototype上已经有的alert // 为了后面防冲突处理使用. var old = $.fn.alert $.fn.alert = Plugin $.fn.alert.Constructor = Alert // ALERT NO CONFLICT // ================= // 防冲突处理 $.fn.alert.noConflict = function () { $.fn.alert = old return this } // ALERT PLUGIN DEFINITION // ======================= // DOM-TO-Object的桥接模式 function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.alert') if (!data) $this.data('bs.alert', (data = new Alert(this))) if (typeof option == 'string') data[option].call($this) }) }}(jQuery); 从上面的例子,可以看出定义插件需要如下几个步骤: 定义插件的构造函数。 定义插件的参数(包含公共属性)插件可以通过初始化参数,或者动态的改变参数,来改变自己的状态和行为。 定义插件的行为方法,来具体的控制插件的状态。 插件的公共部分(所有的插件都会涉及,例如:防冲突处理,一般提供noConflict方法;使用DOM-to-Object的桥接模式等)。 栗子:Step插件 1234567891011121314151617181920212223// Step插件(function ($) { /** *@param {Object} options 动态参数 */ function Step (options) { // do something } // 公共属性 Step.VERSION = '0.0.1'; Step.NAME = 'step'; // 原型定义,所有的实例(instance)都会拥有的方法 Step.prototype = { constructor: Step, // 修正构造函数属性的指向 // 插件参数 options : { }, // 很多插件系统的默认初始化方法 _init: function (options) {} };})(jQuery) jQuery UI插件编写的方式 上面总结的定义插件步骤其实还不完整,定义一个插件,我们就要想到,插件实例从无到有,还要从有到无。我们定义的插件,需要有一个生命周期管理,而这个生命周期的管理,其实是所有插件都要处理的,其实它是一个公共部分。既然是公共部分(DRY原则),必须是能够重用,而不用重复这部分代码。 如果你多看几个bootstrap的插件,你会发现几乎每一个插件都有如下的代码: 1234567891011121314 // ALERT PLUGIN DEFINITION // ======================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('plug.name') if (!data) $this.data('plug.name', (data = new Step(this))) if (typeof option == 'string') data[option].call($this) }); }// 这段代码之所以会重复出现,是因为bootstrap的设计者,想减少bootstrap对其它库的依赖。// 这段代码在$.widget.bridge中有非常巧妙的实现(jquery.ui.widget.js或者widget.js)。 使用$.Widget对象扩展控件 jQuery widget factory 帮我们做了很多事情,尤其一些插件公共部分的生命周期管理,都帮我们实现了,所以创建一个jQuery UI组件从widget factory开始。 jQueryUI的widget公共模块,只依赖jQuery,没有和jQueryUI的core模块耦合,是一个纯粹的工厂函数实现,很方便定义自己的ui组件。 举个栗子: 123$.widget('repay.step', { _create: function () {}}) 命名空间(namespace): ‘repay.step’,其中repay是namespace;step是控件名称。ui 命名空间默认留给jquery.ui使用了。 12// 调用$('table').step(); 继续step的例子 12345678<!-- 这个是控件的模版 --> <table name=\"step\" class=\"step\"> <tbody> <tr> <td class=\"text-center\">第一步</td> </tr> </tbody></table> 控件的每一步有三个状态:原始状态,活动状态,焦点状态。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394/** * @date 2017-05-08 * @author lijunnan */(function ($) { var baseClass = 'step', textClass = 'text-center', activeClass = 'active', focusClass = 'js-focus'; $.widget('repay.step', { options: { VERSION: '0.0.1', NAME: 'step', template: '<td></td>' }, /** * 创建控件的时候调用. */ _create: function () { if (!this._haveContainer()) { this._addContainer(); } this.refresh(); this.$container = this.element.find('tr'); }, add: function (name) { $(this.options.template).appendTo(this.$container).text(name); this.refresh(); }, refresh: function () { this.element.addClass(baseClass); this.element.find('td').addClass(textClass); }, activate: function (index, onFocus) { onFocus = typeof onFocus === 'boolean' ? onFocus : true; var classes = onFocus ? activeClass + \" \" + focusClass : activeClass; this.element.find('td').eq(this._constrainedIndex(index)) .addClass(classes); }, isFocus: function (index) { return this.element.find('td').eq(this._constrainedIndex(index)) .hasClass(focusClass); }, isActive: function (index) { return this.element.find('td').eq(this._constrainedIndex(index)) .hasClass(activeClass); }, _haveContainer: function () { return this.element.find('tr').length > 0; }, _addContainer: function () { this.element.append('<tbody><tr></tr></tbody>'); }, _constrainedIndex: function (index) { var $allSteps = this.element.find('td'), lastIndex = $allSteps.length - 1; if (typeof index !== 'number') { index = 0; } index = index < 0 ? 0 : index; index = index > lastIndex ? lastIndex : index; return index; }, _destroy: function () { this.element.remove(); }, next: function () { var activeSelector = '.' + activeClass, focusSelector = '.' + focusClass, focusIndex, $allSteps = this.element.find('td'), maxIndex = $allSteps.length - 1; var $focusNode = $allSteps.filter(activeSelector + focusSelector); if ($focusNode.length === 0) { this.activate(0); this._trigger('start', null, { name: $allSteps.first().text(), index: 0 }); } else { focusIndex = this._constrainedIndex($focusNode.next().index()); $focusNode.removeClass(focusClass) .next('td').addClass(activeClass + ' ' + focusClass); if (focusIndex === maxIndex) { this._trigger('done', null, { name: $focusNode.text(), index: focusIndex }); } } } });})(jQuery); 上面的代码控件已经完成了。 12345678910<!-- 模版1 --> <table name=\"step\" class=\"step\"> <tbody> <tr> <td class=\"text-center\">第1步</td> <td class=\"text-center\">第2步</td> <td class=\"text-center\">第3步</td> </tr> </tbody></table> 控件的初始化 12// 控件的初始化调用$('table').step(); // 这样就创建了step控件实例 控件的方法调用 12345678// 活动状态$('table').step('next');// 调用带参数的方法$('table').step('isActive', 0);// 销毁控件$('table').step('destroy'); 事件 事件是控件的很重要的一部分,事件是控件和外界其它控件解耦的关键。 栗子中next方法: 123456789// 触发的startthis._trigger('start', null, { name: $allSteps.first().text(), index: 0});this._trigger('done', null, { name: $focusNode.text(), index: focusIndex}); this._trigger方法是从jQuery.Widget继承的 , 此方法对触发事件做了特殊处理,所以控件对外发布的事件会变成控件名+事件名 。所以start和done事件,对外发布的事件是stepstart和stepdone。 监听事件 事件对外发布之后,我们可以根据控件发布的事件,做一些其它的业务处理。 1234567$(document).on('stepstart', function (evt, data) { var args = arguments; console.log('stepstart');}).on('stepdone', function () { var args = arguments; console.log('stepdone');}); 小结 step已经完成了一个完整的控件例子的展示,从create控件建立控件到destory控件,整个生命周期的管理。 一个控件最终要的就是几个部分: 初始化渲染(create) 控件的事件(event) 控件的销毁(destroy) 控件的扩展性(extend) 可配置性(configurable):通过option来动态改变控件的状态。 参考资料https://www.smashingmagazine.com/2011/10/essential-jquery-plugin-patterns/ http://www.cnblogs.com/timy/archive/2011/04/01/2001871.html","tags":[{"name":"jQuery","slug":"jQuery","permalink":"https://betgar.github.io/tags/jQuery/"},{"name":"jQuery Widget","slug":"jQuery-Widget","permalink":"https://betgar.github.io/tags/jQuery-Widget/"}]},{"title":"Oracle中被锁定的表","date":"2017-04-18T12:00:00.000Z","path":"2017/04/18/query-oracle-table-locked/","text":"Oracle 中被锁定的表 查询oracle中被锁定的表 12345678-- 相关表SELECT * FROM V$LOCK;SELECT * FROM V$SQLAREA;SELECT * FROM V$SESSION;SELECT * FROM V$PROCESS ;SELECT * FROM V$LOCKED_OBJECT;SELECT * FROM ALL_OBJECTS;SELECT * FROM V$SESSION_WAIT; 查询锁定的对象 查询数据库中锁定的对象 123456-- 可以根据操作系统的用户名查询SELECT B.OWNER, B.OBJECT_NAME, A.SESSION_ID, A.LOCKED_MODE, A.PROCESS, A.OS_USER_NAME FROM V$LOCKED_OBJECT A, DBA_OBJECTS B WHERE B.OBJECT_ID = A.OBJECT_ID -- AND A.OS_USER_NAME = '操作系统用户名称'; 查询Session和SERIAL#序号 123456-- SESSION_ID会话idSELECT B.USERNAME, B.SID, B.SERIAL#, LOGON_TIME FROM V$LOCKED_OBJECT A, V$SESSION B WHERE A.SESSION_ID = B.SID AND A.SESSION_ID IN (404) -- 会话id ORDER BY B.LOGON_TIME; 杀死锁定表的会话(Session)1234567891011121314151617-- 查询被锁定的表-- OS_USER_NAME操作系统用户名称SELECT B.OWNER, B.OBJECT_NAME, A.SESSION_ID, A.LOCKED_MODE, A.PROCESS, A.OS_USER_NAME FROM V$LOCKED_OBJECT A, DBA_OBJECTS B WHERE B.OBJECT_ID = A.OBJECT_ID -- AND A.OS_USER_NAME = 'os';-- 查询Session和SERIAL#序号SELECT B.USERNAME, B.SID, B.SERIAL#, LOGON_TIME FROM V$LOCKED_OBJECT A, V$SESSION B WHERE A.SESSION_ID = B.SID AND A.SESSION_ID IN (404) ORDER BY B.LOGON_TIME; -- 根据Session和SERIAL#序号杀死锁定的会话 ALTER SYSTEM KILL SESSION '404,6797';","tags":[{"name":"Oracle","slug":"Oracle","permalink":"https://betgar.github.io/tags/Oracle/"}]},{"title":"羽毛球赛规则","date":"2017-04-14T12:00:00.000Z","path":"2017/04/14/the-rules-of-badminton/","text":"羽毛球赛规则 赛事规则,像我这种的非常必要了解。 规则首局获胜一方在接下来的一局比赛中先发球。 羽毛球双打比赛还有其它的一些规则,包括: 一局比赛开始应从右发球区发球。 只有接发球员才能接发球;如果他的同伴去接球或被球触及,发球方得一分。 自发球被回击后,由发球方的任何一人击球,然后由接发球方的任何一人击球,如此往返直至死球。 自发球被回击后,运动员可以从网的各自一方任何位置击球。 接发球方违例或因球触及接发球方场区内的地面而成死球,发球方得一分,原双打发球员继续发球。 发球方违例或应球触及发球方场区内的地面而成死球,原双打发球员即失去发球权,对方得分。 双打比赛中,当比分为0或偶数时,球由右发球区对角发向对方场地的右接发球区;当比分为奇数时,球由左发球区对角发向对方场地的左接发球区。双打比赛中,只有当一方连续得分时,发球员必须在右或左发球区交替发球,而接发球方队员的位置不变。其它情况下,选手应站在上一回合的各自发球区不变,以此保证发球员的交替。 羽毛球双打比赛一方每次只有一次发球权。发球方失误不仅丢失发球权也将丢失1分,如果这时得发球权的一方得分为奇数时,则必须是位于左发球区的选手发球,如果此时得发球权的一方得分为偶数时,则必须是位于右发球区的选手发球。 羽毛球双打比赛只有接发球队员才能接发球,若其同伴接发球或被球触及则“违例”,判发球方得分,当发球被回击后,球可由二人中任一人击回,不得连击,如此往返直至死球。 羽毛球双打比赛发球时,发球队员和接发球队员必须站在规定的发球区和接发球区内发球和接发球,他们的同伴站位可以不受限制,但不得妨碍对方。运动员发球和接发球顺序有误,已得比分有效,纠正方位或顺序。","tags":[{"name":"sports","slug":"sports","permalink":"https://betgar.github.io/tags/sports/"}]},{"title":"nbtstat","date":"2017-04-10T12:00:00.000Z","path":"2017/04/10/how-to-use-nbtstat-command-get-ip-address/","text":"nbtstat计算机名称和MAC互转背景 因为个人部署了一个小博客,发布一些经验总结方便自己(顺便方便一下大家)。因为个人的电脑IP地址是动态的,所以经常导致之前分享的地址不能使用,所以经过研究,发下了一个一劳永逸的好办法,唯一要求只需要你记得计算机名称(computer name)。 如果电脑处于休眠状态还需要你记得MAC地址(最好记住这个)。 MAC : D43D7ED1CE05 个人计算机名称 JN 1MAC地址:D43D7ED1CE05 通过计算机名称查询MAC和IP地址1nbtstat -a LIJN 通过IP查询计算机名称和MAC地址1nbtstat -A 10.11.4.11 查看帮助1nbtstat /? # 帮助","tags":[{"name":"cmd","slug":"cmd","permalink":"https://betgar.github.io/tags/cmd/"}]},{"title":"UNIX文件系统的历史","date":"2017-03-29T12:00:00.000Z","path":"2017/03/29/a-history-of-unix-directory-structure/","text":"Filesystem Hierarchy Standard 阮一峰译文 Rob Landley原文 On Tuesday 30 November 2010 15:58:00 David Collier wrote: I see that busybox spreads it’s links over these 4 directories. Is there a simple rule which decides which directory each link livesin….. For instance I see kill is in /bin and killall in /usr/bin…. I don’thave a grip on what might be the logic for that. You know how Ken Thompson and Dennis Ritchie created Unix on a PDP-7 in 1969?Well around 1971 they upgraded to a PDP-11 with a pair of RK05 disk packs (1.5megabytes each) for storage. When the operating system grew too big to fit on the first RK05 disk pack (theirroot filesystem) they let it leak into the second one, which is where all theuser home directories lived (which is why the mount was called /usr). Theyreplicated all the OS directories under there (/bin, /sbin, /lib, /tmp…) andwrote files to those new directories because their original disk was out ofspace. When they got a third disk, they mounted it on /home and relocated allthe user directories to there so the OS could consume all the space on bothdisks and grow to THREE WHOLE MEGABYTES (ooooh!). Of course they made rules about “when the system first boots, it has to come upenough to be able to mount the second disk on /usr, so don’t put things likethe mount command /usr/bin or we’ll have a chicken and egg problem bringingthe system up.” Fairly straightforward. Also fairly specific to v6 unix of 35years ago. The /bin vs /usr/bin split (and all the others) is an artifact of this, a1970’s implementation detail that got carried forward for decades bybureaucrats who never question why they’re doing things. It stopped makingany sense before Linux was ever invented, for multiple reasons: 1) Early system bringup is the provice of initrd and initramfs, which dealswith the “this file is needed before that file” issues. We’ve already got atemporary system that boots the main system. 2) shared libraries (introduced by the Berkeley guys) prevent you fromindependently upgrading the /lib and /usr/bin parts. They two partitions haveto match or they won’t work. This wasn’t the case in 1974, back then theyhad a certain level of independence because everything was statically linked. 3) Cheap retail hard drives passed the 100 megabyte mark around 1990, andpartition resizing software showed up somewhere around there (partition magic3.0 shipped in 1997). Of course once the split existed, some people made other rules to justify it.Root was for the OS stuff you got from upstream and /usr was for your site-local files. Then / was for the stuff you got from AT&T and /usr was for thestuff that your distro like IBM AIX or Dec Ultrix or SGI Irix added to it, and/usr/local was for your specific installation’s files. Then somebody decided/usr/local wasn’t a good place to install new packages, so let’s add /opt!I’m still waiting for /opt/local to show up… Of course given 30 years to fester, this split made some interesting distro-specific rules show up and go away again, such as “/tmp is cleared betweenreboots but /usr/tmp isn’t”. (Of course on Ubuntu /usr/tmp doesn’t exist andon Gentoo /usr/tmp is a symlink to /var/tmp which now has the “not clearedbetween reboots” rule. Yes all this predated tmpfs. It has to do with read-only root filesystems, /usr is always going to be read only in that case and/var is where your writable space is, / is mostly read only except for bitsof /etc which they tried to move to /var but really symlinking /etc to/var/etc happens more often than not…) Standards bureaucracies like the Linux Foundation (which consumed the FreeStandards Group in its’ ever-growing accretion disk years ago) happilydocument and add to this sort of complexity without ever trying to understandwhy it was there in the first place. ‘Ken and Dennis leaked their OS into theequivalent of home because an RK05 disk pack on the PDP-11 was too small” goeswhoosh over their heads. I’m pretty sure the busybox install just puts binaries wherever other versionsof those binaries have historically gone. There’s no actual REASON for any ofit anymore. Personally, I symlink /bin /sbin and /lib to their /usrequivalents on systems I put together. Embedded guys try to understand andsimplify… RobGPLv3: as worthy a successor as The Phantom Menace, as timely as Duke NukemForever, and as welcome as New Coke. On Thursday 09 December 2010 16:45, Rob Landley wrote: The /bin vs /usr/bin split (and all the others) is an artifact of this, a1970’s implementation detail that got carried forward for decades bybureaucrats who never question why they’re doing things. It stopped makingany sense before Linux was ever invented, for multiple reasons:… I’m pretty sure the busybox install just puts binaries wherever other versionsof those binaries have historically gone. There’s no actual REASON for any ofit anymore. Personally, I symlink /bin /sbin and /lib to their /usrequivalents on systems I put together. Embedded guys try to understand andsimplify… Another simplification: I symlink [/usr]/sbin to bin. If a program shouldn’t be runnable by a non-root,it can easily be arranged by suitable mode in the binary. No need to haveseparate directory for that. –vda","tags":[{"name":"FHS","slug":"FHS","permalink":"https://betgar.github.io/tags/FHS/"}]},{"title":"Linux命令学习笔记","date":"2017-03-29T12:00:00.000Z","path":"2017/03/29/the-linux-command-line-learn-note/","text":"Linux命令学习笔记Linux系统目录结构123456789101112131415161718192021222324252627/-- # root |--bin # Essential command binaries |--boot # Static files of the boot loader |--dev # Device files |--etc # Host-specific system configuration |--lib # Essential shared libraries and kernel modules |--media # Mount point for removeable media |--mnt # Mount point for mounting a filesystem temporarily |--opt # Add-on application software packages |--sbin # Essential system binaries |--srv # Data for services provided by this system |--tmp # Temporary files |--usr # Secondary hierarchy |--var # Variable data Linux系统目录简写名称Linux命令缩写含义(Linux short command meaning) pwd: print work directory cd: change directory 123cd ~cd - # 在上一个和当前目录之间切换cd ~user name # 切换到指定的用户的home目录 文件和目录 ls: list cp: copy mv: move mkdir: make directory rm : remove ln : link less: less is more(more也是一个命令.zless和less一样,但是可以读取gzip文件) file 命令 type which whatis alias info man: manual apropos help 重定向 cat: (链接多个文件) head tail sort uniq grep wc 标准重定向 标准输入、输出、错误在shell 内部参考它们为文件描述符 0, 1 和 2。 重定向标准输出12345# 重定向标准输出到文件(这里会重新覆写ls-stdout.txt)ls -l > ls-stdout.txt# 追加到ls-stdout.txtls -l >> ls-stdout.txt 重定向错误 错误文件描述符为:2 12345678# 重定向错误ls -l /bin/usr 2> ls-error.txt# 标准输出和错误输出到同一个文件ls -l /bin/usr > ls-error.txt 2>&1# bash现代版本支持ls -l /bin/usr &> ls-error.txt","tags":[{"name":"linux","slug":"linux","permalink":"https://betgar.github.io/tags/linux/"}]},{"title":"NPM的正确使用姿势","date":"2017-03-04T12:00:00.000Z","path":"2017/03/04/use-npm-with-right-posture/","text":"NPM的正确使用姿势 node package manager是node的包管理器,使用NPM来管理node的模块。 但是因为中国的独特的网络环境,所以给使用NPM带来很多不变。 安装cnpm cnpm是npm的一个模块,它是淘宝(阿里)推出的一个为中国大陆开发者的工具,强烈建议使用cnpm安装项目依赖的插件,它可以帮你简化因为“网络环境”带来的问题。 1npm install -g cnpm 配置npm的registry 国内访问npm的registry很慢,而且有时候还会出现错误,所以配置成淘宝的镜像。 注意 :如果你决定使用cnpm安装依赖模块,可以不用配置npm的registry 1234567891011121314# 配置全局的(永久生效)npm config set registry=\"https://registry.npm.taobao.org\"# 临时(一次性)npm install module_name --registry=\"https://registry.npm.taobao.org\"# 如果公司屏蔽了taobaonpm config set registry=\"https://r.cnpmjs.org\"# 查看registry是否生效npm config get registry# 查看默认的npm所有配置npm config list 配置cnpm的registry 如果你的网络屏蔽了taobao,那就使用https://r.cnpmjs.org 12# 如果没有taobao访问限制,不需要这步配置cnpm config set registry=\"https://r.cnpmjs.org\" cnpm使用 因为cnpm的配置和npm的是完全独立,所以如果你希望单独配置cache,请重新配置。 123> # <path> 是真实路径> cnpm config set cache=\"<path>\"> 现在就可以使用cnpm代替npm操作,但是如果你需要发布一个模块,请切换使用npm来publish。因为cnpm是只读的,所以只能用来方便下载。 引用cnpm-github China-mirror cnpmjs","tags":[{"name":"npm","slug":"npm","permalink":"https://betgar.github.io/tags/npm/"}]},{"title":"fuel-ux-spinner源码","date":"2017-03-04T12:00:00.000Z","path":"2017/03/04/fuel-ux-spinner/","text":"fuel-ux-spinner源码 之前使用到了bootstrap的一个插件,但是没有api,所以直接把源码拷贝出来备份一下。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131/* * Fuel UX Spinner * https://github.com/ExactTarget/fuelux * * Copyright (c) 2012 ExactTarget * Licensed under the MIT license. */!function($) { var Spinner = function(element, options) { this.$element = $(element), this.options = $.extend({}, $.fn.spinner.defaults, options), this.$input = this.$element.find(\".spinner-input\"), this.$element.on(\"keyup\", this.$input, $.proxy(this.change, this)), this.options.hold ? (this.$element.on(\"mousedown\", \".spinner-up\", $.proxy(function() { this.startSpin(!0) }, this)), this.$element.on(\"mouseup\", \".spinner-up, .spinner-down\", $.proxy(this.stopSpin, this)), this.$element.on(\"mouseout\", \".spinner-up, .spinner-down\", $.proxy(this.stopSpin, this)), this.$element.on(\"mousedown\", \".spinner-down\", $.proxy(function() { this.startSpin(!1) }, this))) : (this.$element.on(\"click\", \".spinner-up\", $.proxy(function() { this.step(!0) }, this)), this.$element.on(\"click\", \".spinner-down\", $.proxy(function() { this.step(!1) }, this))), this.switches = { count: 1, enabled: !0 }, this.switches.speed = \"medium\" === this.options.speed ? 300 : \"fast\" === this.options.speed ? 100 : 500, this.lastValue = null, this.render(), this.options.disabled && this.disable() }; Spinner.prototype = { constructor: Spinner, render: function() { var inputValue = this.$input.val(); inputValue ? this.value(inputValue) : this.$input.val(this.options.value), this.$input.attr(\"maxlength\", (this.options.max + \"\").split(\"\").length) }, change: function() { var value = this.$input.val(); value / 1 ? this.options.value = value / 1 : (value = value.replace(/[^0-9]/g, \"\"), this.$input.val(value), this.options.value = value / 1), this.triggerChangedEvent() }, stopSpin: function() { clearTimeout(this.switches.timeout), this.switches.count = 1, this.triggerChangedEvent() }, triggerChangedEvent: function() { var value = this.value(); value !== this.lastValue && (this.lastValue = value, this.$element.trigger(\"changed\", value), this.$element.trigger(\"change\")) }, startSpin: function(t) { if (!this.options.disabled) { var i = this.switches.count; 1 === i ? (this.step(t), i = 1) : i = 3 > i ? 1.5 : 8 > i ? 2.5 : 4, this.switches.timeout = setTimeout($.proxy(function() { this.iterator(t) }, this), this.switches.speed / i), this.switches.count++ } }, iterator: function(e) { this.step(e), this.startSpin(e) }, step: function(e) { var t = this.options.value , i = e ? this.options.max : this.options.min; if (e ? i > t : t > i) { var s = t + (e ? 1 : -1) * this.options.step; (e ? s > i : i > s) ? this.value(i) : this.value(s) } else if (this.options.cycle) { var n = e ? this.options.min : this.options.max; this.value(n) } }, value: function(value) { return !isNaN(parseFloat(value)) && isFinite(value) ? (value = parseFloat(value), this.options.value = value, this.$input.val(value), this) : this.options.value }, disable: function() { this.options.disabled = !0, this.$input.attr(\"disabled\", \"\"), this.$element.find(\"button\").addClass(\"disabled\") }, enable: function() { this.options.disabled = !1, this.$input.removeAttr(\"disabled\"), this.$element.find(\"button\").removeClass(\"disabled\") } }, $.fn.spinner = function(i, s) { var n, a = this.each(function() { var a = $(this) , o = a.data(\"spinner\") , r = \"object\" == typeof i && i; o || a.data(\"spinner\", o = new Spinner(this,r)), \"string\" == typeof i && (n = o[i](s)) }); return void 0 === n ? a : n } , $.fn.spinner.defaults = { value: 1, min: 1, max: 999, step: 1, hold: !0, speed: \"medium\", disabled: !1 }, $.fn.spinner.Constructor = Spinner, $(function() { $(\"body\").on(\"mousedown.spinner.data-api\", \".spinner\", function() { var t = $(this); t.data(\"spinner\") || t.spinner(t.data()) }) })}(window.jQuery);","tags":[{"name":"Bootstrap","slug":"Bootstrap","permalink":"https://betgar.github.io/tags/Bootstrap/"},{"name":"spinner","slug":"spinner","permalink":"https://betgar.github.io/tags/spinner/"}]}]